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
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/app-bridge.ts:455
- The JSDoc says the
ElicitResult“can be returned directly to the server”, but the spec text added in this PR requires the host to validate the app result (including againstrequestedSchema) and potentially fall back to native rendering on bridge/validation failures. This comment is likely to mislead host implementers into skipping required validation.
* Hosts should call this only from the core MCP client's handler for the
* originating `elicitation/create` request. The returned value is the
* standard MCP `ElicitResult` and can be returned directly to the server.
*/
- Files reviewed: 8/11 changed files
- Comments generated: 0 new
- Review effort level: Lite
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)