[draft / experiment] MCP Apps prototype: hyperdx_query structured content + iframe widget#2195
Draft
alex-fedotyev wants to merge 6 commits intomainfrom
Draft
[draft / experiment] MCP Apps prototype: hyperdx_query structured content + iframe widget#2195alex-fedotyev wants to merge 6 commits intomainfrom
alex-fedotyev wants to merge 6 commits intomainfrom
Conversation
- Preapprove @modelcontextprotocol/ext-apps and @modelcontextprotocol/sdk in .yarnrc.yml so the 7-day npmMinimalAgeGate doesn't block recently published versions of the official SDK packages. - Add @hyperdx/mcp-widget to root dev/build/concurrently scripts. - Add jsdom + @types/jsdom to packages/api dev deps for widget tests that load the bundled HTML and assert on the parsed DOM. - packages/api build now invokes the mcp-widget build first so the API ships with a current widget bundle.
…widget
Adds @hyperdx/chart-presenters: tree of pure React presenter components
with no data-fetching, no Mantine, no Jotai, no Next router. Built with
tsup and consumed both by packages/app (DBTimeChart wraps the time-series
presenter with its data hook) and packages/mcp-widget (sandboxed iframe
renders the same component for MCP Apps results).
Constraints:
* No data fetching; callers wire their own.
* No Mantine / Jotai / Next router (would balloon the widget bundle
and break inside MCP Apps sandboxed iframes).
* Recharts and React are peer dependencies.
Currently exports TimeSeriesView (line + stacked-bar). Table, number,
and pie remain inline in the widget for now; they're simple enough that
extraction doesn't pay back yet.
Adds @hyperdx/mcp-widget: a Vite-built single-file HTML bundle that
renders observability query results inline inside MCP-Apps-capable
hosts (Claude Desktop, Cursor, etc.).
How it works:
1. Host fetches the widget resource (URI is advertised on the
hyperdx_query tool via _meta.ui.resourceUri).
2. Host loads the HTML in a sandboxed iframe.
3. Widget completes the App handshake using the official ext-apps SDK
and waits for the host to push the tool result.
4. On tool result, widget extracts structuredContent and renders one
of: line, stacked_bar (via @hyperdx/chart-presenters), table,
search, number, pie.
The bundle is built with vite-plugin-singlefile and inlines all assets
into a single HTML file. No data-fetching of its own; the host pushes
the tool result.
Wires hyperdx_query into the MCP Apps profile so capable hosts render
results inline instead of dumping JSON.
Server side:
* mcpServer.ts registers a single resource (HYPERDX_WIDGET_URI) that
serves the bundled mcp-widget HTML with mimeType
"text/html;profile=mcp-app". The strict mimeType is what hosts
look for to distinguish App resources from arbitrary HTML.
* tools/query/index.ts: tool registration sets _meta["ui/resourceUri"]
(slash-key form, the canonical key Claude Desktop reads) and
_meta.ui.resourceUri (nested form for backwards compat) so any
MCP Apps host can find the widget.
* tools/query/helpers.ts: builds the structuredContent payload
(displayType + config + data + links) the widget renders from.
Also hydrates source fields for raw SQL tiles so $__sourceTable /
$__filters macros expand correctly.
* tools/query/schemas.ts: tightens schema descriptions so the LLM
picks the right displayType and aggFn.
* ui/widget.ts: HTTP route that serves the widget bundle. Reads the
file once at process start, re-reads on dev mtime changes so
`yarn workspace @hyperdx/mcp-widget dev` hot-reloads.
* api-app.ts: mounts the widget route.
This is Phase 0 of the MCP Apps experiment. End-to-end browser
verification was deferred (see agent_docs/mcp-apps.md for state).
The external-API conversion utilities silently dropped `granularity`
(the time-bucket size for line / stacked_bar configs). Without it the
renderer omits the toStartOfInterval column and rows aggregate to a
single point per series, so the MCP query tool (which goes through
this conversion path) couldn't produce time-bucketed output.
Changes:
* externalDashboardTimeChartConfigSchema accepts an optional
granularity string (max 64 chars).
* convertToExternalTileChartConfig forwards granularity for line and
stacked_bar branches.
* convertToInternalTileConfig picks granularity through to the
internal config.
Format is "<number> <unit>" where unit is second, minute, hour, day.
Tests:
* jest.setup.ts: default HYPERDX_APP_PORT so config.ts captures a
valid FRONTEND_URL at module load (without it tests that import
helpers crashed on `new URL("http://localhost:undefined")`).
* queryTool.test.ts: extend existing tests to cover the
structuredContent shape returned for each displayType.
* widget.test.ts: new suite verifying the widget resource is served
with the App profile mimeType, the bundle is non-trivial, the App
handshake string ("ui/initialize") is present, and the tool
advertises both _meta keys (slash-form and nested-form).
Docs:
* agent_docs/mcp-apps.md: developer notes on Phase 0 of the MCP Apps
experiment, why each architectural decision was made, and what
remains for Phase 1+.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 0 of an MCP Apps experiment. The
hyperdx_queryMCP tool returnsstructuredContentplus a_metareference to a sandboxed iframe widget, so MCP-Apps-capable hosts (Claude Desktop, Cursor) render observability results inline instead of dumping JSON into the chat.This is a prototype, not for merge. Pushing it as a Draft so it's easy to review commit-by-commit. End-to-end browser verification was deferred; see
agent_docs/mcp-apps.mdfor the full state.What's here
Six logical commits, in order:
chore(deps): wire MCP Apps SDK and the newmcp-widgetworkspace into root scripts.feat(chart-presenters): new package. Pure React presenters with no Mantine, no Jotai, no Next router. Shared by the dashboard and the MCP widget so both render identically.feat(mcp-widget): new package. Vite-built single-file HTML bundle that runs inside an MCP Apps sandboxed iframe, completes the App handshake using the official@modelcontextprotocol/ext-appsSDK, and renders tool results.feat(api/mcp): server side of the protocol.mcpServer.tsregisters the widget resource withmimeType: text/html;profile=mcp-app.tools/query/index.tsadvertises it on the tool via_meta["ui/resourceUri"](the canonical slash-key form Claude Desktop reads) and_meta.ui.resourceUri(nested form for backwards compat).feat(api/external): exposegranularityon external-API time-chart configs so the conversion path used by MCP doesn't drop the time-bucket size and aggregate every series to a single point.test(api/mcp) + docs: jsdom-backed widget tests,queryToolstructured-content coverage, andagent_docs/mcp-apps.mdwith the full design rationale.How to investigate
agent_docs/mcp-apps.md. Start there.structuredContentlooks like) is documented at the top ofpackages/mcp-widget/src/mcp-app.tsx._metakey conventions, including why both forms ship, are documented inpackages/api/src/mcp/ui/widget.ts.Status
Working:
ext-appsSDK.line/stacked_bar/table/search/number/piedisplay types.Deferred:
Branch hygiene
Branch is 45 commits behind
origin/mainbecause the prototype was started off an older commit. Merge base is on main, so the diff itself is clean. We can rebase before any real merge consideration.