Skip to content

feat(examples/mcp): add gate_board with the MCP Apps extension - #449

Merged
eris-ths merged 1 commit into
mainfrom
feat/mcp-apps-board
Jul 31, 2026
Merged

feat(examples/mcp): add gate_board with the MCP Apps extension#449
eris-ths merged 1 commit into
mainfrom
feat/mcp-apps-board

Conversation

@eris-ths

Copy link
Copy Markdown
Owner

The 2026-07-28 spec registered MCP Apps (io.modelcontextprotocol/ui) as an
official extension. This teaches the example MCP server to speak it.

What

  • gate_board tool carrying _meta.ui.resourceUri
  • matching ui://gate/board HTML resource rendering the decision history

Hosts implementing the extension render the board inline in the conversation.
Hosts without it ignore _meta and get a plain text tool, so nothing breaks —
support is per-client (as of 2026-07-31 the published client matrix lists
Claude web/Desktop, VS Code Copilot, Cursor and ChatGPT among others).

What this is not

gate stays the source of truth. A window, not a migration: no existing
tool, verb, or substrate shape changed. The board is read-only and does the one
thing the CLI cannot — put "who decided what, and why" next to the conversation
that led there.

Details

  • reads gate tail --format json, not text output (principle 11: the substrate
    is the contract, text is a human projection)
  • self-contained HTML, no CDN — MCP Apps cannot reach external origins unless
    declared in _meta.ui.csp, and an example should not grow dependencies
  • substrate values are HTML-escaped before innerHTML, and the embedded JSON
    escapes </, so markup in an action or reason cannot break out of the page
  • the view renders whatever shape arrives over postMessage; the host push format
    is not yet verified, and a viewer that only accepted the assumed shape would
    look empty instead of showing what actually arrived

Verification

Driven over stdio against a real substrate:

  • tools/listgate_board carries _meta.ui.resourceUri
  • resources/listui://gate/board as text/html
  • resources/read → page returned with records embedded

Note for anyone extending this: FastMCP accepts meta= on @mcp.tool, but the
low-level types.Tool does notpopulate_by_name is off there, so meta=
is accepted as an extra arg and silently dropped, leaving _meta null with no
error. Use _meta= on the low-level API.

Add a `gate_board` tool carrying `_meta.ui.resourceUri` and a matching
`ui://gate/board` HTML resource, so hosts implementing the MCP Apps
extension (`io.modelcontextprotocol/ui`, registered as an official
extension in the 2026-07-28 spec) render the decision history inline in
the conversation. Hosts without the extension ignore `_meta` and see a
plain text tool, so nothing breaks.

`gate` stays the source of truth. This is a window, not a migration: no
existing tool, verb, or substrate shape changed. The board is read-only
and does the one thing the CLI cannot -- put "who decided what, and why"
next to the conversation that led there.

Details:
- reads `gate tail --format json`, not text output (principle 11: the
  substrate is the contract, text is a human projection)
- self-contained HTML, no CDN: MCP Apps cannot reach external origins
  unless declared in `_meta.ui.csp`, and an example should not grow deps
- substrate values are HTML-escaped before `innerHTML`, and the embedded
  JSON escapes `</` so markup in an action/reason cannot break the page
- the view renders whatever shape it receives over postMessage; the host
  push format is not yet verified, and a viewer that only accepts the
  shape I assumed would look empty instead of showing what arrived

Verified by driving the server over stdio: tools/list carries
`_meta.ui.resourceUri`, resources/list exposes `ui://gate/board` as
text/html, and resources/read returns the page with records embedded.
@eris-ths
eris-ths merged commit 0c97577 into main Jul 31, 2026
5 checks passed
@eris-ths
eris-ths deleted the feat/mcp-apps-board branch July 31, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant