Add app-rendered elicitations to the MCP Apps draft - #733
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the MCP Apps SDK and draft specification to support app-rendered form elicitations, negotiated via the existing io.modelcontextprotocol/ui extension (elicitation: {}) and forwarded using the standard elicitation/create request/result shapes.
Changes:
- Adds new MCP Apps capability + metadata types/schemas for elicitation, and wires them into the SDK type unions.
- Implements server-side helpers for negotiation and for attaching/reading
_meta.ui.resourceUrion elicitation requests, plus tests. - Adds app/host APIs (
App.onelicitation,AppBridge.requestElicitation) and updates the draft spec to document the end-to-end flow.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Re-exports new elicitation-related spec types/schemas and includes ElicitRequest/ElicitResult in SDK unions. |
| src/spec.types.ts | Adds elicitation capability fields and new McpUiServerCapabilities + McpUiElicitationMeta interfaces. |
| src/server/index.ts | Adds negotiation helpers and helpers to attach/read elicitation UI metadata. |
| src/server/index.test.ts | Adds tests for elicitation negotiation and metadata helpers. |
| src/generated/schema.ts | Adds Zod schemas for new capabilities + elicitation metadata. |
| src/generated/schema.test.ts | Adds type-level checks ensuring schema inference matches spec types. |
| src/generated/schema.json | Updates generated JSON Schema outputs for the new types. |
| src/app.ts | Adds App.onelicitation handler support and capability enforcement for elicitation/create. |
| src/app-bridge.ts | Adds requestElicitation API and capability enforcement for forwarded elicitations. |
| src/app-bridge.test.ts | Adds integration tests for forwarding a form-mode elicitation and failing closed when unsupported. |
| specification/draft/apps.mdx | Documents the protocol negotiation + app-rendered elicitation flow and cleans up formatting. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
src/server/index.ts:589
- This helper currently accepts
ui:foobecause it only checksparsed.protocol === "ui:". If callers rely on it for enforcement, that would treat non-absoluteui:URIs as valid even though the API and spec require an absoluteui://URI.
Add an explicit resourceUri.startsWith("ui://") check before returning.
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
if (parsed.protocol !== "ui:") {
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
- Files reviewed: 8/11 changed files
- Comments generated: 2
- Review effort level: Low
| let parsed: URL; | ||
| try { | ||
| parsed = new URL(resourceUri); | ||
| } catch { | ||
| throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI"); | ||
| } | ||
| if (parsed.protocol !== "ui:") { | ||
| throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI"); | ||
| } |
| expect(() => withElicitationUi(params, "https://example.com")).toThrow( | ||
| "absolute ui://", | ||
| ); |
|
The coordinated public references are now aligned to protocol revision
The primary flow uses request-scoped client capabilities, server capabilities from No second extension, custom method, or custom result type is introduced. The Inspector is pinned to ext-apps commit The references are generic and contain no product-specific example details. |
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (2)
src/server/index.ts:589
getElicitationUiResourceUriis intended to validate an absoluteui://URI, but it only checksparsed.protocol === "ui:", soui:relativewill pass validation even though it is not an absoluteui://URI.
if (parsed.protocol !== "ui:") {
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
src/server/index.ts:546
withElicitationUiclaims to require an absoluteui://URI, but the validation only checksparsed.protocol === "ui:". This will incorrectly accept values likeui:relative(valid URL with protocolui:but notui://), contradicting the spec requirement and the error message.
This issue also appears on line 587 of the same file.
if (parsed.protocol !== "ui:") {
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
- Files reviewed: 8/11 changed files
- Comments generated: 0 new
- Review effort level: Low
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (3)
src/server/index.ts:589
getElicitationUiResourceUricurrently acceptsui:/...because it only checks the URL protocol. To match the spec wording andgetToolUiResourceUribehavior, require theui://prefix here as well.
if (parsed.protocol !== "ui:") {
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
src/server/index.test.ts:442
- This test suite doesn't cover the
ui:/...case, which currently passes thenew URL()+ protocol check even though the API requires an absoluteui://URI. Adding a regression test here will prevent accidentally re-allowing non-ui://forms.
expect(() => withElicitationUi(params, "https://example.com")).toThrow(
"absolute ui://",
);
src/server/index.ts:546
- This validation only checks
new URL(resourceUri).protocol === "ui:", so values likeui:/pathwill be accepted even though the error message (and other helpers likegetToolUiResourceUri) require an absoluteui://URI. Tighten the check to require theui://prefix (and ideally reject any non-form mode, not justmode === "url").
if (parsed.protocol !== "ui:") {
throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
}
- Files reviewed: 8/11 changed files
- Comments generated: 0 new
- Review effort level: Low
Summary
elicitation: {}inside the existingio.modelcontextprotocol/uiextension on both client and server2026-07-28path using request-scoped client capabilities,server/discover, and core MRTRelicitation/createrequest and return the standardElicitResultDesign
This does not add a second extension, JSON-RPC method, result shape, or retry mechanism.
Client/server negotiation requires core form elicitation, the MCP Apps MIME type, and the nested MCP Apps elicitation setting on both peers. In
2026-07-28, client capabilities are carried on each originating request and server capabilities come fromserver/discover; older initialized sessions use the same capability objects in the initialize exchange.The server embeds the ordinary
elicitation/createrequest in anInputRequiredResult. The host resolves the bound app resource, validates the app's standardElicitResult, places it under the matchinginputResponseskey, and retries the original request withrequestState. The View↔Host Apps bridge independently negotiates MCP Apps protocol version2026-01-26.The host binds each request to the originating server connection,
ui://resource, and app instance. Resource, initialization, bridge, or result-validation failures fall back to native form rendering. Explicitdeclineandcancelresults do not trigger fallback.The SDK adds:
App.onelicitationAppBridge.requestElicitationsupportsAppElicitation,withElicitationUi, and metadata readersThis follows the direction discussed in #511 and #514, and incorporates the useful app-handler portion of #531 while avoiding forwarding through a global/current bridge.
Core protocol context: modelcontextprotocol/modelcontextprotocol#3118.
Reference implementations:
Validation
npm run buildbun test src/app-bridge.test.ts src/server/index.test.ts(168 passed, from the implementation commit)