effect/ai: support the MCP Apps extension (`io.modelcontextprotocol/ui`)
メンテナーはふだん 1 日以内に返信
@effect-bot がすでに取り組んでいます。
2026年11月5日 から。
評価
この issue はまだ評価されていません。
説明
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.
- 主要言語
- TypeScript
- スター
- 16.7k
- フォーク
- 808
- 平均マージ
- 11時間 31分
- マージ済み PR(30日)
- 834
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Effect-TS/effect のほかの issue
-
Xml: `positions` decode option that puts `line` and `column` on `StartElement` events対応中かも @effect-bot が今日担当しました。 オープン
Effect-TS/effect#10400 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Flat RPC client and AtomRpc query/mutation: type-check cost grows with RPCs in the group × tags called対応中かも @effect-bot が今日担当しました。 オープン
Effect-TS/effect#10396 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Support `format: "password"` as `Redacted` in JSON Schema import/export and the OpenAPI generator対応中かも @effect-bot が今日担当しました。 オープンenhancement
Effect-TS/effect#10394 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Versioned schemas with version negotiation in HttpApi, Rpc and cluster対応中かも @effect-bot が 1 日前に担当しました。 オープン
Effect-TS/effect#10383 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Schema encoding can return an internal symbol across module copies対応中かも @schickling-assistant が 1 日前に担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 25/100
メンテナーはふだん 1 日以内に返信
Effect-TS/effect の issue をすべて見る
似ている issue
-
unwrapDatabaseError masks real DB errors with TypeError: Cannot convert object to primitive valueオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
NEARBuilders/everything-dev#798 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
vercel-labs/durable-harness#53 ·
メンテナーはふだん 1 日以内に返信
-
[Bug]: Redesign tab bars ignore clicks on Linux: hidden chat layers keep window drag regions対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
experiência: a doc manda subir para o Micro do Supabase, mas não diz que exige plano pago nem quanto custa対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンarea/docs area/ui enhancement
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
melgarafael/DeskcommCRM#4098 ·
メンテナーはふだん 1 日以内に返信
-
dependencies platform: ios
難易度 1/5 1〜3時間 初心者へのやさしさ 78/100
software-mansion/react-native-executorch#1617 ·
メンテナーはふだん 1 日以内に返信