effect/ai: support the MCP Apps extension (`io.modelcontextprotocol/ui`)
I maintainer di solito rispondono entro 1 giorno
@effect-bot ci sta già lavorando.
Dal 5/11/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Need
MCP Apps is the
stable MCP extension that lets a server give the human a visual for a tool call. A tool links to a ui:// HTML
resource. The host renders it in a sandboxed iframe, and the page (the spec calls it the View) talks to the host over
JSON-RPC on postMessage. A tool result serves two readers: structuredContent holds the data for the page and the
agent, and content holds the text for the model and for hosts without Apps. Claude (web and desktop), VS Code,
ChatGPT, Goose and others render MCP Apps.
An Effect MCP server can link a tool to a UI today only by hand: an untyped Tool.Meta record, a hand-written
extensions key, and a content effect that returns a raw ReadResourceResult. The page itself has no Effect
support. Concretely:
- No typed extension announcement,
ui://resource, resource_meta.ui(CSP, permissions, domain, border) or tool
_meta.ui(resourceUri,visibility).registerResourcehas no_metaoption. - No way to check whether the host supports Apps, other than reading
clientCapabilities.extensionsby hand. contentis always the JSON ofstructuredContent.McpServerignores a tool'stoModelOutput, so a tool
can't give the model a text summary while the page gets the data.- A handler can't see the JSON-RPC id of its own
tools/call.hostContext.toolInfo.idis the only standard key
a page has for the call that created it while that call is still running, so a server can't tie a page's polling
calls to the run they belong to. Handlers also can't seeMcp-Session-Id, the scope in which that id is unique
over sessionful HTTP.clientIdis assigned per HTTP POST, so it can't serve as that scope. McpClient.toolkitbinds tools markedvisibility: ["app"]for the model, though the spec says hosts MUST NOT
give them to the agent.McpClientcan't declare the extension.- Nothing for the page: the handshake, tool input and result, host context, calling server tools, display modes,
sizing and teardown.
The motivating consumer is the effect/test MCP server. Its tool results stay Schema'd data the agent acts on, and the
human gets a live view of a run, run reports and snapshot diffs in the host.
Prior art
- #6353 asks for first-class MCP Apps support: a
ui://resource kind and typedTool.Metakeys. This issue covers
that request. - Effect-TS/effect-smol#1440 added the
_metaandextensionspassthrough on MCP schemas, and
Effect-TS/effect-smol#1653 exposed the client capabilities to handlers. Both are what makes MCP Apps possible today. - @lloydrichards's examples (edu_mcp-apps,
interactivethings-data-viz-mcp-lab, and
the helpers posted on #6353) build MCP Apps on the currentMcpServer.makeUiResource,makeUiRenderTooland
makeUiAppToolare the shape the server half below makes first-class. The polling dashboard there is the pattern
the effect/test live view follows. - #8631 (Tasks) and the Skills extension set the pattern for extensions: a public module per extension, a server that
declares the extension once it is in use, andMcpClientoptions grouped underextensions. This proposal follows
it.
Alternatives considered
- Keep hand-writing
_metaand HTML resources. This works for the linkage, but nothing is typed or validated,
the model gets JSON where the spec suggests a summary, and the page side gets no help. - Use
@modelcontextprotocol/ext-appson the page. It is Promise- and zod-based, built on the TypeScript MCP
SDK, and useson*setters that must be registered beforeconnect(). It can't reuse the server'sTool
definitions to type calls and results. - Extend
McpClientto be the page's bridge.McpClientis request/response. The bridge is bidirectional: the
host sends notifications and theui/resource-teardownrequest. It also has its own handshake (ui/initialize). - Vary a tool's
_metaby host. In 2026-07-28,tools/listmust not vary per connection, and a
cacheScope: "public"list may be served to any client. Hosts without Apps already treat a linked tool as a plain
tool, andEnabledWhencovers text-only variants. - Correlate a page with its call through a server-minted handle in the result. The handle arrives only when the
call completes, which is too late for a long-running call. The request id is available from the start.
Solution
Two PRs, opened after #8631 merges because it changes the same files. The server and host parts come first, then the
page module. Both cover the stable 2026-01-26 spec plus the draft's ui/download-file and
ui/notifications/request-teardown. Names below are provisional.
1. Server and host
A new module, effect/ai/McpApps, for the extension itself:
- Schemas and constants:
ResourceMeta(with its CSP and permissions parts),ToolMeta,ClientCapabilities,
EXTENSION_IDandMIME_TYPE. McpApps.registerResourceandMcpApps.resource. The URI must beui://…, or registration fails with
IllegalArgumentError. The MIME typetext/html;profile=mcp-appgoes on the listing and on every content item,
and the resource metadata goes as_meta.uion both.- Registering an app resource declares the extension (
{}) on every protocol revision. An explicitextensions
record still merges in. - A tool annotation that
registerToolkitmerges into the tool's_meta.ui. Registration dies ifTool.Metaalso
setsui, or ifresourceUri(optional, as app-only tools have none) isn'tui://. Only the nested form is
written, not the deprecated_meta["ui/resourceUri"]. McpApps.isSupported(capabilities), usable inEnabledWhenand in handlers.
General fixes, in the existing modules:
- A
_metaoption forMcpServer.registerResourceandMcpServer.resource(the listing entry). toModelOutputbecomes the MCPcontent(text, image, audio, resource link or embedded resource), and
structuredContentstays the encoded success. Tools withouttoModelOutputare unchanged.McpRequestContextgainsrequestId, the JSON-RPC id as received, andsessionId. The docs say where the id is
unique on each transport: within the process on stdio, within the session over sessionful HTTP, and on 2026-07-28
only among the sender's requests still in flight. Fix theclientIddocs.McpClient.make({ extensions: { ui: true } })declares{ mimeTypes: ["text/html;profile=mcp-app"] }.
McpClient.toolkitskips tools whose visibility lacks"model".
Out of scope: result _meta, ui:// templates, and the host-side bridge (rendering, CSP, proxying).
2. The page
effect/ai/McpAppView, platform-agnostic:
- A
McpAppViewservice and layer over anypostMessage-capable target. It performs theui/initialize
handshake and buffers host messages from the moment it connects. - Host data as Effect types:
toolInput(anEffect)toolInputPartial(aStream)toolResult(anEffectthat fails withToolCancelled)hostContext(aSubscribablethat merges partial updates)hostInfoandhostCapabilities
toolCall(tool)decodes the input and the result with the tool's schemas.callTool(tool, params)calls a
server tool through the host, typed from the sameTooldefinitions. A failure is aToolErrorthat keeps the
result, as inMcpClient.callTool.readResource,openLink,sendMessage,updateModelContext,requestDisplayMode,sendSizeChanged,log,
downloadFileandrequestTeardown. They fail fast when the host lacks a capability the stable spec defines.run(program): onui/resource-teardown, the program and every fiber the bridge owns are interrupted, their
finalizers run, and only then does the host get its answer. Any other host request the page doesn't implement gets
JSON-RPC-32601(method not found).- It must work in opaque-origin documents, which is how hosts load pages (cf. #9196).
- A public in-memory test host for pages, in the style of
McpTest.
@effect/platform-browser adds only what needs the DOM: ResizeObserver auto-resize (on by default) and opt-in
helpers for the host's theme, CSS variables and fonts. Is that the right home, or should these stay in the core
module's docs as examples?
Out of scope: app-provided tools and WebMCP (see modelcontextprotocol/ext-apps#797; not planned), sampling from the
page, framework bindings, and HTML bundling.
Development uses MCPJam Inspector to render and check pages against
emulated hosts. The repo's tests use the in-memory test host.
- Lingua principale
- TypeScript
- Stelle
- 16.7k
- Fork
- 808
- Merge medio
- 11h 31m
- PR unite (30g)
- 834
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Effect-TS/effect
-
Xml: `positions` decode option that puts `line` and `column` on `StartElement` eventsForse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10400 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Flat RPC client and AtomRpc query/mutation: type-check cost grows with RPCs in the group × tags calledForse già presa @effect-bot l’ha presa oggi. Aperta
Effect-TS/effect#10396 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Support `format: "password"` as `Redacted` in JSON Schema import/export and the OpenAPI generatorForse già presa @effect-bot l’ha presa oggi. Apertaenhancement
Effect-TS/effect#10394 · 2 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Versioned schemas with version negotiation in HttpApi, Rpc and clusterForse già presa @effect-bot l’ha presa 1 giorno fa. Aperta
Effect-TS/effect#10383 · 2 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Schema encoding can return an internal symbol across module copiesForse già presa @schickling-assistant l’ha presa 1 giorno fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Effect-TS/effect
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 73/100
hugomrdias/playwright-test#753 ·
I maintainer di solito rispondono entro 1 giorno
-
Review view: Daily/Weekly Review header buttons use the app-wide toolbar control styleForse già presa @colherdesopa l’ha presa oggi. Aperta
Difficoltà 1/5 1-3 ore Idoneità per principianti 90/100
dongdongbh/Mindwtr#1515 ·
I maintainer di solito rispondono entro 1 giorno
-
pending-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
kirodotdev/Kiro#12301 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100