Skip to content

Add Server-Side MCP Apps Helpers per SEP-1865#441

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:mcp_apps
Open

Add Server-Side MCP Apps Helpers per SEP-1865#441
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:mcp_apps

Conversation

@koic

@koic koic commented Jul 6, 2026

Copy link
Copy Markdown
Member

Motivation and Context

SEP-1865 "MCP Apps - Interactive User Interfaces for MCP" (modelcontextprotocol/modelcontextprotocol#1865) is Final on the Extensions Track: an optional extension, negotiated through the SEP-2133 capabilities.extensions mechanism, that lets a server ship HTML user interfaces which the host renders for tool results. The normative spec lives in the ext-apps repository (specification/2026-01-26/apps.mdx). The Python SDK implements it in-SDK (python-sdk#3003, the Apps extension); the TypeScript ecosystem ships it as the separate @modelcontextprotocol/ext-apps package on top of the core SDK's SEP-2133 support.

Server-side support is a thin convention layer over primitives this SDK already has (SEP-2133 extension capabilities, _meta on tools and resources, free-form resource URIs and MIME types). New MCP::Apps module:

  • Wire vocabulary constants shared with the reference implementations: EXTENSION_ID ("io.modelcontextprotocol/ui" - note /ui, not /apps), RESOURCE_MIME_TYPE ("text/html;profile=mcp-app"), URI_SCHEME ("ui://"), and the legacy flat meta key RESOURCE_URI_META_KEY ("ui/resourceUri").
  • Apps.capability(mime_types:) builds the capabilities.extensions fragment for support_extensions on the server or connect on the client.
  • Apps.ui_resource(uri:, name:, ...) builds an MCP::Resource for a UI template, enforcing the spec's MUSTs (a ui:// URI; the parameterized HTML MIME type by default).
  • Apps.tool_meta(resource_uri:, visibility:, meta:, legacy:) builds the _meta.ui.resourceUri linkage (optionally also emitting the legacy flat alias, matching the reference server helper that keeps both shapes in sync), merged non-destructively into caller-supplied meta.
  • Apps.client_supports?(client_capabilities, mime_type:) gates the text-only fallback the extension requires for clients that did not declare the capability (symbol/string tolerant).

Everything else the extension defines (the sandboxed iframe, the ui/* postMessage bridge methods, consent for UI-initiated calls) is HOST responsibility and out of scope for a server SDK; a server only ever receives ordinary resources/read and tools/call requests. The README section states this boundary.

Resolves #376.

How Has This Been Tested?

New test/mcp/apps_test.rb pins the wire vocabulary and covers the capability fragment, ui_resource scheme validation and MIME default, tool_meta non-destructive merging, visibility, the legacy alias, and client_supports? across symbol/string keys, mimeTypes narrowing, and missing declarations.

New integration tests in test/mcp/server_test.rb drive a full server: the initialize result advertises the extension, tools/list serializes _meta.ui.resourceUri, resources/read serves the HTML template, and client_supports? distinguishes hosts with and without the declared extension.

bundle exec rake (tests, RuboCop, and conformance baseline) passes. Also verified end-to-end over stdio: a client connecting with the extension declared negotiates the capability, discovers the tool linkage via _meta.ui.resourceUri, and reads the ui:// template as text/html;profile=mcp-app content.

Breaking Changes

None. Purely additive helper module; no existing code paths change.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

SEP-1865 "MCP Apps - Interactive User Interfaces for MCP" is Final on the Extensions Track:
(modelcontextprotocol/modelcontextprotocol#1865) an optional extension,
negotiated through the SEP-2133 `capabilities.extensions` mechanism, that lets a server ship
HTML user interfaces which the host renders for tool results.
The normative spec lives in the ext-apps repository (`specification/2026-01-26/apps.mdx`).
The Python SDK implements it in-SDK (python-sdk#3003, the `Apps` extension); the TypeScript
ecosystem ships it as the separate `@modelcontextprotocol/ext-apps` package on top of
the core SDK's SEP-2133 support.

Server-side support is a thin convention layer over primitives this SDK already has
(SEP-2133 extension capabilities, `_meta` on tools and resources, free-form resource URIs
and MIME types). New `MCP::Apps` module:

- Wire vocabulary constants shared with the reference implementations: `EXTENSION_ID`
  (`"io.modelcontextprotocol/ui"` - note `/ui`, not `/apps`), `RESOURCE_MIME_TYPE`
  (`"text/html;profile=mcp-app"`), `URI_SCHEME` (`"ui://"`), and the legacy flat meta key
  `RESOURCE_URI_META_KEY` (`"ui/resourceUri"`).
- `Apps.capability(mime_types:)` builds the `capabilities.extensions` fragment for
  `support_extensions` on the server or `connect` on the client.
- `Apps.ui_resource(uri:, name:, ...)` builds an `MCP::Resource` for a UI template,
  enforcing the spec's MUSTs (a `ui://` URI; the parameterized HTML MIME type by default).
- `Apps.tool_meta(resource_uri:, visibility:, meta:, legacy:)` builds the `_meta.ui.resourceUri`
  linkage (optionally also emitting the legacy flat alias, matching the reference server helper
  that keeps both shapes in sync), merged non-destructively into caller-supplied meta.
- `Apps.client_supports?(client_capabilities, mime_type:)` gates the text-only fallback
  the extension requires for clients that did not declare the capability (symbol/string tolerant).

Everything else the extension defines (the sandboxed iframe, the `ui/*` postMessage bridge methods,
consent for UI-initiated calls) is HOST responsibility and out of scope for a server SDK;
a server only ever receives ordinary `resources/read` and `tools/call` requests.
The README section states this boundary.

Resolves modelcontextprotocol#376.

## How Has This Been Tested?

New `test/mcp/apps_test.rb` pins the wire vocabulary and covers the capability fragment,
`ui_resource` scheme validation and MIME default, `tool_meta` non-destructive merging,
visibility, the legacy alias, and `client_supports?` across symbol/string keys, mimeTypes narrowing,
and missing declarations.

New integration tests in `test/mcp/server_test.rb` drive a full server: the `initialize` result
advertises the extension, tools/list serializes `_meta.ui.resourceUri`, `resources/read` serves
the HTML template, and `client_supports?` distinguishes hosts with and without the declared extension.

`bundle exec rake` (tests, RuboCop, and conformance baseline) passes. Also verified end-to-end
over stdio: a client connecting with the extension declared negotiates the capability,
discovers the tool linkage via `_meta.ui.resourceUri`, and reads the `ui://` template
as `text/html;profile=mcp-app` content.

## Breaking Changes

None. Purely additive helper module; no existing code paths change.
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.

SEP-1865: MCP Apps - Interactive User Interfaces for MCP

1 participant