Skip to main content

Widgets overview

A widget is a JSON file that declares two things:

  1. Where the data comes from — usually an MCP tool call.
  2. How to render it — a tree of components, props, and bindings.

The MyHub dashboard reads the JSON, calls the tool, drops the response into a reactive store, and renders the component tree. When the user clicks something or the data changes, the tree re-renders against the new state.

Powered by Vercel json-render

Widgets are rendered by Vercel's json-render library. It is the canonical implementation of the schema described in this section. Read its docs for the formal grammar; everything in this guide is a plugin-author-friendly view of the same primitives.

External reading: json-render docs · json-render on GitHub

MyHub's widgets-system extends json-render with:

  • A typed dataProvider block (which MCP tool, which params).
  • A bridge to plugin-contributed widget-elements so $computed references can be resolved against your slug-namespaced helpers.
  • A library of opinionated components (Card, Header, Stat, Table, Badge, …).

Anatomy of a widget file

widgets/acme-overdue-invoices.json
{
"id": "acme-overdue-invoices",
"title": "Overdue Invoices",
"description": "All Acme invoices past their due date.",
"category": "finance",
"tags": ["acme", "invoices", "overdue"],
"keywords": ["overdue", "late", "ar", "receivables"],
"connectorsUsed": ["acme-billing"],
"popularity": 0,
"sizing": {
"preferred": { "colSpan": 5, "rowSpan": 4 },
"min": { "colSpan": 4, "rowSpan": 3 },
"max": { "colSpan": 10, "rowSpan": 8 }
},
"dataProvider": {
"mcp": "acme-billing",
"tool": "list_invoices",
"params": { "status": "overdue" }
},
"spec": {
"root": "card",
"elements": { /* … json-render tree … */ }
}
}
FieldPurpose
idStable id. Must match the filename.
title, descriptionShown in the widget gallery and on the dashboard tile.
category, tags, keywordsUsed for search and filtering in the gallery.
connectorsUsedSlugs of MCP servers this widget needs. The widget is hidden if the tenant hasn't installed them.
sizingpreferred is what the agent drops on the dashboard; min/max bound the user's resize.
dataProviderWhich MCP tool runs on mount. Response lands at /<mcp>/<tool>/… in the state store.
spec.rootKey into spec.elements — the entry point of the render tree.
spec.elementsMap of element id → element definition.

Where widgets fit in MyHub

agent ──► proposes a widget id ──► MyHub loads widgets/<id>.json


dataProvider runs (MCP call)


json-render builds the component tree


tile appears on dashboard

The agent doesn't generate JSON on the fly — it picks from the catalog you ship. Generative widgets are a separate path (Anthropic Messages API, Haiku 4.5) and not covered here.

Tutorial path

If you've never built a widget before, run the tutorials in order:

  1. First widget — a static stat card. ~15 min.
  2. Live data — wire it to an MCP tool and render a Table. ~20 min.
  3. Computed and transforms$computed, status tones, $watch. ~30 min.
  4. Composite widget — multi-section layout with header + stats + table + footer. ~45 min.

Then dive into the spec primitives and the components reference for everything the tutorials skipped.