Skip to content

Add app-rendered elicitations to the MCP Apps draft - #733

Draft
krubenok wants to merge 3 commits into
modelcontextprotocol:mainfrom
krubenok:feature/app-rendered-elicitations
Draft

Add app-rendered elicitations to the MCP Apps draft#733
krubenok wants to merge 3 commits into
modelcontextprotocol:mainfrom
krubenok:feature/app-rendered-elicitations

Conversation

@krubenok

@krubenok krubenok commented Jul 29, 2026

Copy link
Copy Markdown

Summary

  • define app-rendered form elicitations in the draft MCP Apps specification
  • negotiate the feature as elicitation: {} inside the existing io.modelcontextprotocol/ui extension on both client and server
  • define the 2026-07-28 path using request-scoped client capabilities, server/discover, and core MRTR
  • add TypeScript types and request-bound host/app APIs that forward the standard elicitation/create request and return the standard ElicitResult

Design

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 from server/discover; older initialized sessions use the same capability objects in the initialize exchange.

The server embeds the ordinary elicitation/create request in an InputRequiredResult. The host resolves the bound app resource, validates the app's standard ElicitResult, places it under the matching inputResponses key, and retries the original request with requestState. The View↔Host Apps bridge independently negotiates MCP Apps protocol version 2026-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. Explicit decline and cancel results do not trigger fallback.

The SDK adds:

  • App.onelicitation
  • AppBridge.requestElicitation
  • client/server/app/host capability types
  • supportsAppElicitation, withElicitationUi, and metadata readers
  • generated schemas and focused tests

This 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 build
  • all example builds run by the repository commit hook
  • bun test src/app-bridge.test.ts src/server/index.test.ts (168 passed, from the implementation commit)
  • formatting and generated-schema checks

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.resourceUri on 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:foo because it only checks parsed.protocol === "ui:". If callers rely on it for enforcement, that would treat non-absolute ui: URIs as valid even though the API and spec require an absolute ui:// 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

Comment thread src/server/index.ts
Comment on lines +538 to +546
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");
}
Comment thread src/server/index.test.ts
Comment on lines +440 to +442
expect(() => withElicitationUi(params, "https://example.com")).toThrow(
"absolute ui://",
);
@krubenok

krubenok commented Jul 29, 2026

Copy link
Copy Markdown
Author

The coordinated public references are now aligned to protocol revision 2026-07-28:

The primary flow uses request-scoped client capabilities, server capabilities from server/discover, and the core MRTR shape: the server embeds the ordinary elicitation/create request in InputRequiredResult; the host validates the App's standard ElicitResult, places it under the matching inputResponses key, and retries the originating request with requestState.

No second extension, custom method, or custom result type is introduced. The Inspector is pinned to ext-apps commit 89ab2bc. The MCP Apps View↔Host bridge independently negotiates Apps protocol version 2026-01-26; that is separate from core MCP 2026-07-28.

The references are generic and contain no product-specific example details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Comments suppressed due to low confidence (2)

src/server/index.ts:589

  • getElicitationUiResourceUri is intended to validate an absolute ui:// URI, but it only checks parsed.protocol === "ui:", so ui:relative will pass validation even though it is not an absolute ui:// URI.
  if (parsed.protocol !== "ui:") {
    throw new Error("Elicitation UI resourceUri must be an absolute ui:// URI");
  }

src/server/index.ts:546

  • withElicitationUi claims to require an absolute ui:// URI, but the validation only checks parsed.protocol === "ui:". This will incorrectly accept values like ui:relative (valid URL with protocol ui: but not ui://), 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Comments suppressed due to low confidence (3)

src/server/index.ts:589

  • getElicitationUiResourceUri currently accepts ui:/... because it only checks the URL protocol. To match the spec wording and getToolUiResourceUri behavior, require the ui:// 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 the new URL() + protocol check even though the API requires an absolute ui:// 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 like ui:/path will be accepted even though the error message (and other helpers like getToolUiResourceUri) require an absolute ui:// URI. Tighten the check to require the ui:// prefix (and ideally reject any non-form mode, not just mode === "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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants