Skip to content

MCP Apps

MCP Apps give you an interactive, visual interface on top of Bridge Town’s existing tools. When you or your agent calls a Bridge Town tool — list pull requests, schedule a model run, query data — you get the usual structured response. In hosts that support ui:// resources, you also get a rich UI that lets you act directly in the chat.

Apps are progressive enhancement: the same MCP tools produce the same complete responses they always have. Apps add a visual layer; they do not change tool behaviour, add new tools, or alter the text content returned. Bridge Town ships 8 first-party apps.

The 8 built-in apps cover the most-used Bridge Town workflows. Every tool listed below was already part of the tool surface before apps were introduced. Seven apps are result_only — read-oriented widgets that render only the initial tool result. native-sheet-editor is host_proxy: it may call its eight allowlisted tools directly from the iframe to support interactive editing, and it also receives MCP-driven refresh results for rehydration; refresh itself is not iframe-callable. Its allowlist excludes render_native_sheet_cell_explanation: Explain sends a chat prompt, and the agent’s next turn invokes that tool and renders the separate native-sheet-cell-explain app. The result-only native-sheet-viewer uses the same prompt flow and has no iframe-callable tool allowlist.

AppStable / compatibility URIMapped tools
PR Workflowui://apps/pr-workflowcreate_branch, list_branches, diff, create_pull_request, list_pull_requests, get_pull_request, submit_pull_request_review, merge_pull_request, merge_branch, delete_branch
Run Outputui://apps/run-outputrun_model, cancel_run, list_runs, get_run, get_run_output, export_run_output
Query Data Gridui://apps/query-data-gridlist_data_sources, query_data
Google Sheet Plannerui://apps/gsheet-plannerget_google_spreadsheet_metadata, read_google_sheet_ranges
Scenario Compareui://apps/scenario-comparecompare_branches, compare_runs
Native Sheet Editorui://apps/native-sheet-editorget_native_sheet, write_native_sheet_cells, format_native_sheet, edit_native_sheet_tab, delete_native_sheet_row_column, recalculate_native_sheet, duplicate_native_sheet_as_input, discard_staged_edit, refresh_output_sheet_view
Native Sheet Viewerui://apps/native-sheet-viewercreate_output_sheet_view
Native Sheet Cell Explainui://apps/native-sheet-cell-explainrender_native_sheet_cell_explanation

The catalog table uses each app’s stable identity so static discovery and old transcripts remain recognizable. Native Sheet tools and generated Claude/OpenAI profiles use a release-qualified cache key of the form ui://apps/<app-id>?release=sha256:<release_hash>, where <release_hash> is the reviewed HTML bundle digest. The stable URI remains a compatibility alias that resolves to the current release; hosts should use the URI they receive in _meta.ui.resourceUri and resources/list rather than asking users to clear their cache.

When you call a Bridge Town tool, the response always includes the full text/JSON result — that never changes. In hosts that support MCP Apps, a _meta.ui.resourceUri annotation on the tool definition tells the host which app can render an interactive view of the result.

┌────────────┐ tool call ┌───────────────┐ text result ┌────────────┐
│ AI Agent │ ────────────► │ MCP Server │ ──────────────► │ Agent │
│ │ │ │ │ (always) │
└────────────┘ └───────────────┘ └────────────┘
_meta.ui.resourceUri (on tool definition)
┌─────────────┐
│ App Bundle │ (only if host
│ (ui://) │ supports it)
└─────────────┘

Hosts that do not support ui:// resources simply ignore the _meta.ui annotation. The tool call, text result, and agent reasoning are unaffected — nothing breaks, nothing is hidden.

What you see depends on the specific host surface. Claude chat on claude.ai, Claude Desktop chat, and Cowork do not provide interchangeable evidence even when they share an Anthropic account or client family.

This status is generated from docs-site/src/data/mcp-apps-host-support.json; do not infer one Claude surface’s behavior from another.

Evidence baseline (2026-09-03): deployed source 8cb696f0a81fb4afe7c7068434e88b67acc519d0; catalog 21e339c9414143a70c061b13c43e8c44c0c7abf93b60ac1ff1fd740e3b76923d; Claude profile dddf08f3b9afa4f8b6d12580ad86886c4266f1e610277950d8818940515a93d9. The exact-release live-host gate is pending and remains owned by the release operator.

SurfaceProduction evidenceInteractive Apps statusRecovery limit
Claude chat (claude.ai)Production /health matched release 8cb696f0a81fb4afe7c7068434e88b67acc519d0 and reported all 78 tools plus the catalog hash above; exact-source tests and the server canary proved all eight App bundles, static bindings, and Claude-specific resource metadata. The live-host run did not reach a tool invocation because approved browser control was unavailable.Server delivery is proven; visible rendering of all eight Apps on this release is not yet production-proven. Earlier mixed outcomes must not be promoted to current-release support.A new chat or connector re-add can refresh host metadata, but neither guarantees a widget. Expand the error and capture the app, time, client version, tool result, and whether resources/read occurred. Invalid ui.domain is a server-metadata defect; raw JSON alone is not a stale-session diagnosis.
Claude Desktop chatNo release-bound live Claude Desktop chat session is retained. Release 8cb696f0a81fb4afe7c7068434e88b67acc519d0 proves only the shared deployed server and source contract for canonical App bindings, resources, structured output, and text fallback—not Desktop end-to-end behavior.Desktop tool/fallback behavior and interactive rendering are unverified for this release and must not be inferred from Claude.ai, Cowork, the shared server contract, or a simulator.Restarting Desktop or reloading its MCP configuration only starts a new client session; it does not guarantee App support or rendering. Check the Desktop version, initialize capability, mapped tool binding, and matching resources/read before classifying the failure.
CoworkOn deployed release 4750db4763b3effde73e9864cc6f56e61ada217d, Cowork identified through claude-code 2.1.252, omitted the UI extension, invoked get_native_sheet, and did not read ui://apps/native-sheet-editor. Static binding fix 5b9f2a45a517752d6cd299d9199b758a9b231421 was deployed in 8cb696f0a81fb4afe7c7068434e88b67acc519d0, but no later visible-render receipt exists.Tool execution and fallback are production-proven. Interactive rendering is not production-proven after the binding fix; a raw JSON or text result is a host-path observation, not proof of a stale connector.Reconnecting cannot make an incapable Cowork transport render Apps. Retain the client family/version and the mapped-tool/no-resource-read sequence, continue with the complete fallback, and wait for release-bound operator acceptance before claiming interactive support.

Other hosts receive the full structured and text result as normal. A host that does not implement ui:// rendering can safely ignore the _meta.ui binding.

Each app is a self-contained HTML bundle — no external scripts, no external stylesheets, no CDN dependencies. The entire app is served as a single ui:// resource, keeping rendering fast and avoiding CSP or network-isolation issues in sandboxed environments.

Use these checks to diagnose whether Apps are active server-side and whether the host would render them.

1. Verify Apps are active (full MCP handshake → tools/list_meta.ui.resourceUri)

In production, Bridge Town uses stateful Streamable HTTP so Apps-capable hosts can keep session state and open the SSE server-to-client channel. App tool bindings are static discovery metadata whenever BRIDGE_TOWN_MCP_APPS_ENABLED=true; the initialize-time UI extension is recorded for diagnostics but is not an authorization or server-side admission gate. Hosts that cannot render Apps ignore the binding and use the complete text fallback.

Perform the session-aware handshake to verify the production path:

Terminal window
# 1. initialize — advertise MCP Apps capability and capture Mcp-Session-Id
curl -fsS -D /tmp/bt-mcp-headers -o /tmp/bt-mcp-init \
-X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","clientInfo":{"name":"Anthropic/ClaudeAI","version":"0.0.1"},"capabilities":{"extensions":{"io.modelcontextprotocol/ui":{"mimeTypes":["text/html;profile=mcp-app"]}}}}}'
SESSION_ID=$(awk 'tolower($1)=="mcp-session-id:" {print $2}' /tmp/bt-mcp-headers | tr -d '\r')
# 2. notifications/initialized — complete handshake
curl -fsS -X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. tools/list — check for _meta.ui.resourceUri
curl -fsS -X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| grep -o '"resourceUri":"[^"]*"'

Note: The Mcp-Session-Id header is required after initialize. Without it, stateful production requests cannot see the Apps capability that was negotiated during the session setup.

Expected output (when Apps are active):

"resourceUri":"ui://apps/pr-workflow"
"resourceUri":"ui://apps/run-output"
...

If _meta.ui.resourceUri is absent in production:

  • Server flag disabledBRIDGE_TOWN_MCP_APPS_ENABLED is not true on the deployed server
  • Server bug or stale deployment — the flag is set but static tool metadata is missing; Bridge Town’s scheduled production canary checks this independently of any single session

Whether the client advertised the UI extension is useful diagnostic evidence, but it does not control whether Bridge Town emits the static resource binding.

2. Verify ui:// resources are registered (resources/list)

Terminal window
curl -fsS -X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"resources/list"}' \
| grep -o '"uri":"ui://apps/[^"]*"'

All 8 ui://apps/... entries — one per app in the App catalog table above — confirm the server-side app bundles are live. Compare the exact URI set against the table, not just the count: a duplicate or swapped entry can leave the count unchanged while a real bundle is missing.

3. Verify app bundle MIME type (resources/read)

Read a specific app resource and confirm both the MIME type and contents-level host metadata. To reproduce Claude’s projection, initialize the diagnostic session with clientInfo.name set to Anthropic/ClaudeAI (as the scheduled canary does):

Terminal window
curl -fsS -X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"ui://apps/pr-workflow"}}' \
| grep -Eo '"mimeType":"[^"]*"|"domain":"[^"]*"'

Expected MIME: "text/html;profile=mcp-app". For the canonical production endpoint, Claude’s content block must also contain "domain":"e8410557956977b970535d7e6b4ab13b.claudemcpcontent.com" with no scheme or path. ChatGPT receives its required unique HTTPS origin instead. The domain is optional for unknown clients and is projected from MCP clientInfo only for rendering compatibility, never authorization.

4. Verify tool call annotates results (tools/callstructuredContent)

When Apps are active, tool calls that map to an app include a structuredContent field alongside the text response. structuredContent is standard MCP output for tools that define an output schema — it is not gated on client capability declaration. The text content is always present regardless of host rendering support.

Terminal window
curl -fsS -X POST https://api.bridgetown.builders/mcp \
-H "Authorization: Bearer btk_YOUR_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_runs","arguments":{"model_name":"YOUR_MODEL_NAME"}}}' \
| grep -o '"structuredContent":{[^}]*}'

If structuredContent is present, the server is correctly annotating tool results. Absence means the server flag is off or a server-side bug — client capability declaration is not required for structuredContent in either stateless or stateful transports.

Apps appear active but don’t render in Claude.ai?

First expand any visible error. Unable to reach Bridge Town plus Invalid ui.domain format means the tool and resource-discovery paths worked, but Claude rejected resources/read content metadata. Capture the exact error and result.contents[0]._meta.ui.domain; do not classify it as a capability discovery or SSE failure. The Claude value must match the bare hash hostname above. Only continue to the SSE checks after domain rejection is excluded.

Bridge Town uses stateful Streamable HTTP in production. If tool definitions include _meta.ui.resourceUri but the iframe remains on “Connecting”, confirm the GET /mcp SSE channel is returning HTTP 200 for the same Mcp-Session-Id. A 405 on that channel means the iframe runtime cannot finish the handshake even though bundle discovery succeeded.

Do not classify a raw JSON or plain-text result as a stale session by default. The scheduled canary proves the server-side handshake, resource, metadata, and SSE contract; it does not prove that a particular host fetched or visibly rendered the bundle. Compare the observation with the generated host matrix above, then retain the mapped tool, client name/version, result metadata, matching resources/read (or its absence), visible error, and time.

Starting a new conversation or re-adding a connector is a diagnostic refresh, not a rendering guarantee. If the result remains plain text, use the complete fallback while support determines whether the gap is host capability, resource selection, content metadata, the SSE iframe handshake, or a tool-specific failure.

If you are building a custom MCP host or agent, you can choose to support ui:// resources or ignore them:

  • To support apps: Advertise extensions["io.modelcontextprotocol/ui"].mimeTypes containing "text/html;profile=mcp-app" in your initialize request so conformance and diagnostics reflect your rendering support. Bridge Town’s tool binding is static rather than capability-gated. When a tool definition includes _meta.ui.resourceUri, fetch that resource after the tool call and render the returned HTML in a sandboxed iframe or webview. The HTML is self-contained and safe to render.
  • To ignore apps: Ignore the _meta.ui.resourceUri binding and consume the standard structured and text outputs. Omitting the extension does not change tool behavior or authorize a different surface.

Apps never change the semantics of a tool call. A create_pull_request call returns the same pull request data whether or not the PR Workflow app renders it.