feat: add MCP Apps (SEP-1865) support#1335
Conversation
|
@copilot resolve the merge conflicts in this pull request |
Adds opt-in 'enableMcpApps' session capability that advertises the
'extensions.io.modelcontextprotocol/ui' extension to MCP servers and
exposes 'session.rpc.mcp.apps.*' JSON-RPC methods.
Node SDK gains two pure helpers for hosts rendering 'ui://' MCP App
bundles in iframes:
- buildMcpAppsCspHeader — constructs the Content-Security-Policy header
per SEP-1865 §UI Resource Format + §Security Implications, including
the restrictive default ('connect-src none') when '_meta.ui.csp' is
absent and constructed defaults ('connect-src self', etc.) when it is
declared.
- buildMcpAppsAllowAttribute — maps '_meta.ui.permissions' to the iframe
'allow' attribute (Permission Policy).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5f12d41 to
0827b5a
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1335 · ● 793.2K
Mirror nodejs enableMcpApps across the other four SDKs so hosts using them can opt into MCP Apps (SEP-1865) UI passthrough by sending requestMcpApps on session.create / session.resume. - python: enable_mcp_apps kwarg on create_session / resume_session - go: EnableMcpApps field on SessionConfig / ResumeSessionConfig - dotnet: EnableMcpApps property on SessionConfig / ResumeSessionConfig - rust: request_mcp_apps field + with_request_mcp_apps builder Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Port the CSP directive injection defense from copilot-agent-runtime PR #7605 into the SDK. Without sanitization, an MCP server returning `frameDomains: ['evil.com; form-action *']` could break out of one CSP directive and inject sibling directives (CSP first-occurrence rule then lets an earlier injected `script-src *` win). Each server-supplied entry is now: - rejected if it contains CSP metacharacters ([;,\\s'"\\\\]) - accepted verbatim for the bare-scheme allowlist (data:, blob:, mediastream:, filesystem:) - otherwise parsed via URL and canonicalized to its origin; opaque origins (where `URL.origin` is the literal string 'null') are dropped Adds 10 sanitization tests mirroring runtime PR coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflect the runtime-side gate added in copilot-agent-runtime PR #7605: requestMcpApps is now honored server-side only when the MCP_APPS feature flag or COPILOT_MCP_APPS=true env override is set; otherwise the opt-in is silently dropped (the runtime logs a warning, but the SDK consumer sees nothing). Update the JSDoc / docstrings on Node, Go, .NET, and Rust to document this and to point at capabilities.ui.mcpApps on the create/resume response as the way to detect the silent drop. Also adds the diagnose method to the enumerated mcp.apps.* RPCs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Expose the runtime's response capability so consumers can detect when their enableMcpApps opt-in was silently dropped by the runtime gate (MCP_APPS feature flag / COPILOT_MCP_APPS env override unset). For each SDK: - Add mcpApps?: bool to the SessionUiCapabilities type - After session.create / session.resume, if the consumer requested the opt-in but capabilities.ui.mcpApps is not true on the response, log a warning (console.warn / logger.warning / slog / tracing::warn / fmt.Fprintf(os.Stderr, ...)) so the silent drop is discoverable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
- python: ruff format reflowed the new _warn_if_mcp_apps_dropped helper - rust: tests/e2e/elicitation.rs constructs UiCapabilities as a struct literal; the new mcp_apps field made it non-exhaustive Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
…t-logging CodeQL flags any value flowing from process.env as sensitive via taint analysis (joinSession() reads process.env.SESSION_ID which propagates to resumeSession's sessionId argument). The session ID is a UUID and not actually sensitive, but the alert noise is not worth it -- the warning is per-call so the consumer already knows which session triggered it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
# Conflicts: # dotnet/src/Types.cs
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Mirrors the MCP Apps (SEP-1865) opt-in already wired into Node, Python, Go, .NET, and Rust: - SessionConfig / ResumeSessionConfig: enableMcpApps field with isEnableMcpApps / setEnableMcpApps accessors and copy() inclusion - CreateSessionRequest / ResumeSessionRequest: requestMcpApps wire field with getter/setter/clearer (Boolean nullable, matches requestElicitation) - SessionUiCapabilities: mcpApps response field with getter/setter/clearer - SessionRequestBuilder: wires config.isEnableMcpApps() -> requestMcpApps on both create and resume paths - CopilotClient: warnIfMcpAppsDropped helper logs when the consumer requested the opt-in but the runtime did not advertise it back (runtime silently drops the opt-in when its MCP_APPS feature flag / COPILOT_MCP_APPS env override is unset) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # rust/src/types.rs
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1335 · ● 8.7M
| ): void { | ||
| if (requested && !capabilities?.ui?.mcpApps) { | ||
| console.warn( | ||
| "[copilot-sdk] enableMcpApps was requested but the runtime did not advertise capabilities.ui.mcpApps. The runtime's MCP_APPS feature flag or COPILOT_MCP_APPS=true environment override is likely unset; the MCP Apps surface is unavailable for this session." |
There was a problem hiding this comment.
Cross-SDK consistency nit: The Python, Go, .NET, and Rust implementations all include the sessionId in this warning to help users identify which session has the issue. For example, Python logs "Session %s: enable_mcp_apps was requested..." and Go logs "[copilot-sdk] Session %s: EnableMcpApps was requested...".
The sessionId variable is available at the call sites (lines ~918 and ~1056). Consider threading it through:
function warnIfMcpAppsDropped(
requested: boolean | undefined,
capabilities: { ui?: { mcpApps?: boolean } } | undefined,
sessionId: string,
): void {
if (requested && !capabilities?.ui?.mcpApps) {
console.warn(
`[copilot-sdk] Session ${sessionId}: enableMcpApps was requested but the runtime did not advertise capabilities.ui.mcpApps. ...`
);
}
}- Revert unintended java/mvnw mode change (644 -> 755) introduced in the Java SDK commit; CI runs mvnw with explicit bash and doesn't require the exec bit. - Refresh Rust doc comments left stale after renaming request_mcp_apps -> enable_mcp_apps on the user-facing API (session.rs warn helper docstring + tracing message; UiCapabilities.mcp_apps cref). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Aligns Node.js and Python with Go/.NET/Java/Rust, which all omit the field when the feature is not opted in. Previously these two SDKs always sent requestMcpApps: false, cluttering protocol logs and risking ambiguity if the protocol ever distinguishes 'not sent' from 'explicitly false'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Adds opt-in MCP Apps (SEP-1865) support across the multi-language SDKs by introducing a new session opt-in flag, exposing/typing the runtime capability (capabilities.ui.mcpApps), and providing Node-only helpers for safely sandboxing ui:// iframe rendering.
Changes:
- Adds
enableMcpApps/enable_mcp_apps(create + resume) and plumbs it through to the JSON-RPC wire flagrequestMcpApps. - Extends session UI capabilities types to include
mcpAppsand adds “opt-in dropped” warnings in each SDK when the runtime gate is off. - Adds Node SDK sandbox helper utilities (
buildMcpAppsCspHeader,buildMcpAppsAllowAttribute) with dedicated tests.
Show a summary per file
| File | Description |
|---|---|
| rust/tests/e2e/elicitation.rs | Updates struct literal to account for new UiCapabilities.mcp_apps field. |
| rust/src/wire.rs | Adds request_mcp_apps to create/resume wire payloads (camelCase serialization). |
| rust/src/types.rs | Adds enable_mcp_apps to SessionConfig/ResumeSessionConfig, new builder methods, and UiCapabilities.mcp_apps. |
| rust/src/session.rs | Warns (via tracing::warn!) when MCP Apps was requested but not advertised by runtime capabilities. |
| python/copilot/session.py | Extends typed capabilities (SessionUiCapabilities) with mcpApps. |
| python/copilot/client.py | Adds enable_mcp_apps parameters; sends requestMcpApps; logs warning if opt-in dropped. |
| nodejs/test/mcpAppsSandbox.test.ts | Adds tests pinning SEP-1865 CSP/Permission Policy mapping + sanitization behavior. |
| nodejs/src/types.ts | Adds mcpApps capability typing and enableMcpApps config option (documented). |
| nodejs/src/mcpAppsSandbox.ts | Introduces pure CSP header + iframe allow attribute builders with sanitization. |
| nodejs/src/index.ts | Exports MCP Apps sandbox helpers from package entrypoint. |
| nodejs/src/client.ts | Sends requestMcpApps when enabled; warns when runtime drops opt-in. |
| java/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java | Plumbs enableMcpApps into requestMcpApps for create/resume payloads. |
| java/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java | Adds mcpApps capability field + Optional getter/set/clear helpers. |
| java/src/main/java/com/github/copilot/sdk/json/SessionConfig.java | Adds enableMcpApps config flag, docs, and clone propagation. |
| java/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java | Adds requestMcpApps to resume wire request type. |
| java/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java | Adds enableMcpApps to resume config and clone propagation. |
| java/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java | Adds requestMcpApps to create wire request type. |
| java/src/main/java/com/github/copilot/sdk/CopilotClient.java | Emits warning when opt-in requested but runtime doesn’t advertise capability. |
| go/types.go | Adds EnableMcpApps config fields + RequestMcpApps wire fields + mcpApps capability. |
| go/client.go | Sends RequestMcpApps when enabled; emits warning when dropped. |
| dotnet/src/Types.cs | Adds McpApps capability + EnableMcpApps session config property (copied in clone ctor). |
| dotnet/src/Client.cs | Sends RequestMcpApps and logs warning when MCP Apps was requested but not advertised. |
Copilot's findings
- Files reviewed: 22/22 changed files
- Comments generated: 7
- nodejs: switch warnIfMcpAppsDropped from console.warn to
process.emitWarning with name McpAppsCapabilityDroppedWarning so
consumers can route/suppress it (--no-warnings,
process.on('warning', ...)) like any other Node deprecation warning.
- go: add TestCreateSessionRequest_RequestMcpApps /
TestResumeSessionRequest_RequestMcpApps mirroring the existing
RequestElicitation marshal/omit tests.
- rust: add session_config_enable_mcp_apps_sets_wire_flag_and_serializes
and resume_session_config_enable_mcp_apps_sets_wire_flag_and_serializes
to cover the opt-in path (config field -> wire flag -> requestMcpApps
in serialized JSON).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- python: add enable_mcp_apps entries to create_session and resume_session docstring Args lists, describing the runtime gate and the capabilities.ui.mcpApps detection mechanism. - java: thread sessionId through warnIfMcpAppsDropped and include it in the warning message, matching the Python/Go/.NET/Rust pattern for multi-session debugging. - nodejs/test: replace the 'see review feedback' marker in the sandbox sanitization section header with a self-contained reference to SEP-1865 \xc2\xa7Security Implications. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
| fmt.Fprintf(os.Stderr, | ||
| "[copilot-sdk] Session %s: EnableMcpApps was requested but the runtime did not advertise capabilities.ui.mcpApps. The runtime's MCP_APPS feature flag or COPILOT_MCP_APPS=true environment override is likely unset; the MCP Apps surface is unavailable for this session.\n", | ||
| sessionID, | ||
| ) |
| * extension to MCP servers (so they expose `_meta.ui.resourceUri` on | ||
| * tools) and to expose the `session.rpc.mcp.apps.{listTools,callTool, | ||
| * readResource,setHostContext,getHostContext,diagnose}` JSON-RPC methods. |
…tderr Writing directly to os.Stderr from library code is unsuppressible and unroutable. Switch to log.Printf so consumers can call log.Default().SetOutput(io.Discard) (or any other writer) to control the warning. Default behavior is unchanged (log.Default() writes to stderr). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency Review ✅This PR maintains excellent cross-SDK consistency. Here's the summary: Features added across all SDKs (Node, Python, Go, .NET, Java, Rust)
Node-only additions
These are intentionally Node-only per the PR description, as they are pure helpers for hosts rendering `(redacted) MCP App bundles in iframes — a use-case primarily driven by JavaScript/TypeScript renderer contexts. No consistency gap here. API naming conventions followed correctly
No cross-SDK consistency issues found. 🎉
|
Adds opt-in MCP Apps (SEP-1865) support to the SDK.
What
enableMcpAppssession capability (SessionConfig+ResumeSessionConfig). Whentrue, the runtime advertises theextensions.io.modelcontextprotocol/uiextension to MCP servers and exposes thesession.rpc.mcp.apps.{listTools,callTool,readResource,setHostContext,getHostContext}JSON-RPC methods. Defaults tofalseso hosts without an iframe renderer don't accidentally register UI-enabled tool variants they can't display.ui://MCP App bundles in iframes:buildMcpAppsCspHeader(csp)— builds theContent-Security-Policyheader per SEP-1865 §UI Resource Format + §Security Implications. Emits the restrictive default (connect-src 'none') when_meta.ui.cspis absent, and the constructed default (connect-src 'self'+ declared domains, etc.) when it is declared (even with empty arrays).buildMcpAppsAllowAttribute(permissions)— maps_meta.ui.permissionsto the iframeallowattribute (Permission Policy), including the hyphenatedclipboard-writename.sequenceDiagram autonumber participant Host as Host App participant SDK as Copilot SDK participant Sandbox as mcpAppsSandbox helpers participant Conn as JSON-RPC Connection participant Runtime as Copilot Runtime participant Iframe as ui iframe Note over Host,Runtime: 1. Opt-in via SDK config Host->>SDK: createSession({ enableMcpApps: true }) SDK->>Conn: session.create { ..., requestMcpApps: true } Conn->>Runtime: JSON-RPC request Runtime-->>Conn: CreateSessionResponse Conn-->>SDK: response SDK-->>Host: CopilotSession Note over Runtime,Host: 2. Tool execution events forwarded unchanged Runtime-->>Conn: tool.execution_complete { uiResource, toolDescription._meta.ui } Conn-->>SDK: SessionEvent (untyped pass-through) SDK-->>Host: onEvent(event) Note over Host,Iframe: 3. Host builds sandboxed iframe using SDK helpers Host->>Sandbox: buildMcpAppsCspHeader(uiResource._meta.ui.csp) Sandbox-->>Host: CSP header value Host->>Sandbox: buildMcpAppsAllowAttribute(uiResource._meta.ui.permissions) Sandbox-->>Host: allow attribute value Host->>Iframe: render with CSP + allow + uiResource Note over Iframe,Runtime: 4. App-to-server RPCs (no typed SDK surface) Iframe->>Host: postMessage RPC Host->>Conn: raw JSON-RPC: session.rpc.mcp.apps.{listTools|callTool|readResource} Conn->>Runtime: forwarded Runtime-->>Conn: result (+ optional ephemeral mcp_app.tool_call_complete event) Conn-->>SDK: SessionEvent (untyped pass-through, callTool only) SDK-->>Host: onEvent(event) Conn-->>Host: RPC response Host-->>Iframe: postMessage response Note over Host,Runtime: 5. Resume preserves opt-in Host->>SDK: resumeSession({ enableMcpApps: true }) SDK->>Conn: session.resume { ..., requestMcpApps: true }Closes https://github.com/github/copilot-mcp-core/issues/1715
Depends on https://github.com/github/copilot-agent-runtime/pull/7605