Skip to content

[FEATURE]: Deliver MCP Apps through a minimal extension substrate #5009

@vishu-bh

Description

@vishu-bh

🧭 Type of Feature

Please select the most appropriate category:

  • Enhancement to existing functionality
  • New feature or capability
  • New MCP-compliant server
  • New component or integration
  • Developer tooling or test improvement
  • Packaging, automation and deployment (ex: pypi, docker, quay.io, kubernetes, terraform)
  • Other (please describe below)

🧭 Epic

Title: Deliver MCP Apps through a minimal extension substrate

Goal: Ship MCP Apps support in ContextForge while establishing the first extension-shaped foundation that future generic extension framework work can replace or expand without rewriting MCP Apps as a one-off feature.

Why now: MCP Apps support is needed as a concrete product capability, and it should be delivered using the same extension-oriented boundaries described in #2527 and #4957. This keeps MCP Apps aligned with capabilities.extensions["io.modelcontextprotocol/ui"] and avoids embedding MCP Apps-specific behavior directly into the core MCP dispatcher.


🧑🏻‍💻 User Story 1

As a: MCP client or host
I want: ContextForge to advertise MCP Apps through capabilities.extensions
So that: clients can negotiate MCP Apps using the official extension capability model.

✅ Acceptance Criteria

Scenario: MCP Apps extension is advertised during initialize
  Given MCP Apps support is enabled
  And the caller is authorized to use the target server or gateway scope
  When an MCP client calls initialize with compatible client capabilities
  Then the initialize response includes capabilities.extensions["io.modelcontextprotocol/ui"]
  And existing prompts/resources/tools/logging/completions capabilities are preserved
  And capabilities.experimental is not used as the canonical MCP Apps signal

Scenario: MCP Apps extension is not advertised to unauthorized callers
  Given MCP Apps support is enabled
  And the caller is not authorized for the target server or gateway scope
  When the caller invokes initialize
  Then capabilities.extensions["io.modelcontextprotocol/ui"] is omitted
  And no private or team-only extension availability is leaked

🧑🏻‍💻 User Story 2

As a: platform maintainer
I want: a minimal built-in extension substrate for MCP Apps
So that: MCP Apps is implemented as an extension handler rather than as scattered dispatcher-specific logic.

✅ Acceptance Criteria

Scenario: MCP Apps is registered as a built-in extension handler
  Given the gateway starts with MCP Apps enabled
  When extension handlers are initialized
  Then io.modelcontextprotocol/ui is registered as a built-in extension
  And the handler can contribute initialize capabilities
  And the handler can process MCP Apps-specific resource/session behavior

Scenario: Core MCP methods still take precedence
  Given an MCP request uses a core method such as tools/list or resources/read
  When the request is dispatched
  Then the existing core MCP path handles the request
  And the extension handler is invoked only for MCP Apps-specific behavior or metadata processing

Scenario: Unknown extension methods remain default-deny
  Given no built-in extension handler owns method unknown/hello
  When a client calls unknown/hello
  Then ContextForge returns JSON-RPC method not found
  And no upstream call is made

🧑🏻‍💻 User Story 3

As a: server administrator
I want: to register and serve MCP Apps UI resources
So that: tools can return interactive UI metadata backed by ui:// resources.

✅ Acceptance Criteria

Scenario: Register UI resource
  Given an administrator has UI content for a dashboard
  When they register a UI resource with a ui:// URI, MIME type, CSP config, and visibility metadata
  Then the resource is stored with audit metadata
  And the resource is associated with the configured owner/team visibility

Scenario: Read UI resource through MCP resource flow
  Given ui://dashboard exists
  And the caller is authorized to read it
  When the caller invokes resources/read for ui://dashboard
  Then the response returns the UI content in MCP resource content format
  And the response includes the metadata needed by an MCP Apps host

Scenario: Deny unauthorized UI resource read
  Given ui://dashboard is private or team-scoped
  And the caller lacks access to that scope
  When the caller invokes resources/read for ui://dashboard
  Then the request is denied
  And an audit event records the denied access

🧑🏻‍💻 User Story 4

As a: tool author
I want: tools to declare associated UI resources and app/model visibility
So that: MCP Apps can show interactive views without exposing app-only helper tools to the model.

✅ Acceptance Criteria

Scenario: Tool declares UI resource metadata
  Given tool create_dashboard exists
  And ui://dashboard exists
  When the tool metadata is updated with _meta.ui.resourceUri="ui://dashboard"
  Then tools/list returns the UI metadata to authorized model-facing clients
  And tools/call can return UI-capable results for compatible hosts

Scenario: App-only tools are hidden from model-facing list
  Given helper tool get_dashboard_data is app-visible only
  When a model-facing client invokes tools/list
  Then get_dashboard_data is omitted
  And model-visible tools remain listed normally

Scenario: App session can call app-visible helper tool
  Given an active MCP Apps session is bound to the same server as get_dashboard_data
  And the caller has permission to execute the tool
  When the app invokes tools/call for get_dashboard_data
  Then the call succeeds
  And the result is returned to the app bridge

🧑🏻‍💻 User Story 5

As a: security administrator
I want: MCP Apps sessions, AppBridge calls, CSP, and sandbox policy to be enforced
So that: interactive UI content cannot cross server, team, or origin boundaries.

✅ Acceptance Criteria

Scenario: AppBridge enforces same-server boundary
  Given an app session is bound to server-a
  When the app attempts to call a tool from server-b
  Then the call is denied
  And an audit event records a cross-server app call denial

Scenario: Expired or missing app session is denied
  Given no active app session exists for the provided session id
  When an app-originated JSON-RPC call is received
  Then the call is denied
  And no tool or resource operation is executed

Scenario: CSP and sandbox policy are validated
  Given an administrator registers a UI resource
  When the CSP, sandbox, or permissions policy is unsafe
  Then the resource is rejected or requires an explicit approved override
  And the policy decision is recorded

📐 Design Sketch (optional)

flowchart TD
    Client[MCP Client / Host] -->|initialize| Gateway[ContextForge]
    Gateway --> BuiltInExt[Built-in Extension Substrate]
    BuiltInExt --> UIExt[io.modelcontextprotocol/ui Handler]
    UIExt --> InitCaps[capabilities.extensions]

    Client -->|tools/call| ToolSvc[Tool Service]
    ToolSvc --> UIMeta[_meta.ui.resourceUri]
    Client -->|resources/read ui://dashboard| ResourceSvc[Resource Service]
    ResourceSvc --> UIStore[UI Resource Store]

    AppView[Sandboxed App View] -->|AppBridge JSON-RPC| AppBridge[AppBridge Handler]
    AppBridge --> Policy[Same-server + RBAC + Token Scope]
    Policy --> ToolSvc
Loading

🔗 MCP Standards Check

  • Change adheres to current MCP extension negotiation using capabilities.extensions
  • MCP Apps uses the official io.modelcontextprotocol/ui extension identifier
  • UI resources are represented as ui:// resources and served through MCP resource flows
  • No breaking changes to existing MCP-compliant integrations
  • If deviations exist, please describe them below:

No intentional deviations. Legacy capabilities.experimental may be preserved as compatibility metadata, but it is not the canonical MCP Apps signal.


🔄 Alternatives Considered

  • Implement MCP Apps directly in the core dispatcher. Rejected because it would make MCP Apps a one-off feature and create avoidable migration work when the broader extension framework lands.
  • Wait for the full generic extension framework before implementing MCP Apps. Rejected because MCP Apps can be delivered through a minimal built-in extension substrate while still preserving the future framework direction.
  • Proxy all unknown extension methods. Rejected because unknown extension routing must remain default-deny and explicitly owned.

📓 Additional Context

Related work:

This issue should finish with MCP Apps delivered through a minimal built-in extension-shaped substrate. The broader registry, discovery, generic auto-proxy, Admin API/UI, and custom handler framework can later replace or expand the substrate without requiring MCP Apps to be rewritten as a standalone feature.

Metadata

Metadata

Assignees

Labels

client-mrkHigh priority itemsenhancementNew feature or requestfrontendFrontend development (HTML, CSS, JavaScript)mcp-appsSEP-1865: MCP Apps: Interactive User Interfaces for MCPmcp-protocolAlignment with MCP protocol or specificationpythonPython / backend development (FastAPI)securityImproves securitytriageIssues / Features awaiting triage

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions