MCP Apps
What are MCP Apps?
Section titled “What are 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 75 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 6 first-party apps.
App catalog
Section titled “App catalog”The 6 built-in apps cover the most-used Bridge Town workflows. Every tool listed below was already part of the 75-tool surface before apps were introduced.
| App | Resource URI | Mapped tools |
|---|---|---|
| PR Workflow | ui://apps/pr-workflow | create_branch, list_branches, diff, create_pull_request, list_pull_requests, get_pull_request, review_pull_request, merge_pull_request, merge_branch, delete_branch |
| Run Output | ui://apps/run-output | run, cancel_run, list_runs, get_run, get_run_output, export_run_output |
| Query Data Grid | ui://apps/query-data-grid | list_data_sources, query_data |
| Google Sheet Planner | ui://apps/gsheet-planner | get_spreadsheet_metadata, batch_read_gsheet_ranges |
| Scheduled Runs | ui://apps/scheduled-runs | list_scheduled_runs, create_scheduled_run, update_scheduled_run, delete_scheduled_run |
| Scenario Compare | ui://apps/scenario-compare | compare_branches, compare_runs |
How it works
Section titled “How it works”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 your host:
- Compatible hosts that render
ui://resources display the interactive app alongside the text result. - Claude.ai uses Bridge Town’s Streamable HTTP connector. For Apps-capable
sessions, Bridge Town includes
_meta.ui.resourceUriand exposes theGET /mcpSSE back-channel used by the iframe runtime handshake. If you see text results but no widgets, see Troubleshooting Apps. - All other hosts receive the full text/JSON result as normal; the
_meta.uiannotation is safely ignored.
App bundles
Section titled “App bundles”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.
Troubleshooting Apps
Section titled “Troubleshooting Apps”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. Both conditions must hold:
BRIDGE_TOWN_MCP_APPS_ENABLED=trueon the server, and- The
initializerequest includedextensions["io.modelcontextprotocol/ui"].mimeTypescontaining"text/html;profile=mcp-app".
Perform the session-aware handshake to verify the production path:
# 1. initialize — advertise MCP Apps capability and capture Mcp-Session-Idcurl -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":"debug","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 handshakecurl -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.resourceUricurl -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-Idheader is required afterinitialize. 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 disabled —
BRIDGE_TOWN_MCP_APPS_ENABLEDis nottrueon the deployed server - Client capability not advertised — the
initializerequest omittedextensions["io.modelcontextprotocol/ui"].mimeTypes - Server bug — flag is set and capability was advertised but metadata is missing; check server logs
2. Verify ui:// resources are registered (resources/list)
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/[^"]*"'Six ui://apps/... entries confirm the server-side app bundles are live.
3. Verify app bundle MIME type (resources/read)
Read a specific app resource and confirm the MIME type is
text/html;profile=mcp-app:
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 -o '"mimeType":"[^"]*"'Expected: "mimeType":"text/html;profile=mcp-app". Any other value means the
host won’t recognise the bundle as an App.
4. Verify tool call annotates results (tools/call → structuredContent)
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.
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":{"project_id":"YOUR_PROJECT_ID"}}}' \ | 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?
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.
To force a fresh initialize handshake, remove and re-add the Bridge Town
connector in Claude.ai.
For agent developers
Section titled “For agent developers”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"].mimeTypescontaining"text/html;profile=mcp-app"in yourinitializerequest so Bridge Town includes_meta.ui.resourceUriintools/list.structuredContentis standard tool output for app-mapped tools and is not gated on capability declaration. 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: Omit the extension from
initialize. Bridge Town will suppress_meta.ui.resourceUrifor your session, while tool calls and text responses remain unaffected.
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.
Related
Section titled “Related”- MCP Skills Resources — bundled skill templates (
skills://) - Connecting any MCP Client — full MCP resource surface overview
- Agent Workflow Cookbook — end-to-end worked examples