feat(examples/mcp): add gate_board with the MCP Apps extension - #449
Merged
Conversation
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.
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.
The 2026-07-28 spec registered MCP Apps (
io.modelcontextprotocol/ui) as anofficial extension. This teaches the example MCP server to speak it.
What
gate_boardtool carrying_meta.ui.resourceUriui://gate/boardHTML resource rendering the decision historyHosts implementing the extension render the board inline in the conversation.
Hosts without it ignore
_metaand 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
gatestays the source of truth. A window, not a migration: no existingtool, 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
gate tail --format json, not text output (principle 11: the substrateis the contract, text is a human projection)
declared in
_meta.ui.csp, and an example should not grow dependenciesinnerHTML, and the embedded JSONescapes
</, so markup in an action or reason cannot break out of the pageis 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/list→gate_boardcarries_meta.ui.resourceUriresources/list→ui://gate/boardastext/htmlresources/read→ page returned with records embeddedNote for anyone extending this: FastMCP accepts
meta=on@mcp.tool, but thelow-level
types.Tooldoes not —populate_by_nameis off there, someta=is accepted as an extra arg and silently dropped, leaving
_metanull with noerror. Use
_meta=on the low-level API.