Skip to content

feat: add MCP Apps (SEP-1865) support#1335

Open
mattdholloway wants to merge 30 commits into
mainfrom
feat/mcp-apps-support
Open

feat: add MCP Apps (SEP-1865) support#1335
mattdholloway wants to merge 30 commits into
mainfrom
feat/mcp-apps-support

Conversation

@mattdholloway
Copy link
Copy Markdown

@mattdholloway mattdholloway commented May 19, 2026

Adds opt-in MCP Apps (SEP-1865) support across all SDKs: a new enableMcpApps session flag, Node helpers for safely sandboxing ui:// iframe rendering, and regenerated RPC/session-event types.

Changes

  • enableMcpApps opt-in (SessionConfig + ResumeSessionConfig in all SDKs) — plumbed to wire field requestMcpApps. Defaults to false; hosts without an iframe renderer are unaffected.
  • Capability gate warning — each SDK emits a warning when enableMcpApps was requested but capabilities.ui.mcpApps was not advertised by the runtime (feature flag unset). Node uses process.emitWarning with code: "MCPAPPS_CAPABILITY_DROPPED" and includes the session ID for correlation across multi-session hosts:
    // Warning emitted on capabilities mismatch:
    // McpAppsCapabilityDroppedWarning [MCPAPPS_CAPABILITY_DROPPED]: enableMcpApps was requested
    // but the runtime did not advertise capabilities.ui.mcpApps (sessionId: abc-123). ...
    process.on("warning", (w) => { if (w.code === "MCPAPPS_CAPABILITY_DROPPED") { ... } });
  • Node iframe sandbox helpers (buildMcpAppsCspHeader, buildMcpAppsAllowAttribute) — pure functions for constructing the Content-Security-Policy header and allow attribute per SEP-1865 §UI Resource Format.
  • Regenerated types across Node, Python, Go, .NET, and Rust including SessionUiCapabilities.mcpApps.

@mattdholloway
Copy link
Copy Markdown
Author

@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 <[email protected]>
@mattdholloway mattdholloway force-pushed the feat/mcp-apps-support branch from 5f12d41 to 0827b5a Compare May 19, 2026 16:51
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #1335 · ● 793.2K

Comment thread nodejs/src/types.ts
mattdholloway and others added 3 commits May 20, 2026 11:05
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 <[email protected]>
@github-actions

This comment has been minimized.

mattdholloway and others added 3 commits May 20, 2026 14:55
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 <[email protected]>
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 <[email protected]>
@github-actions

This comment has been minimized.

mattdholloway and others added 2 commits May 20, 2026 16:31
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 <[email protected]>
Comment thread nodejs/src/client.ts Fixed
@github-actions

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 <[email protected]>
@github-actions

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 <[email protected]>
@mattdholloway
Copy link
Copy Markdown
Author

@copilot resolve the merge conflicts in this pull request

@github-actions

This comment has been minimized.

…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 <[email protected]>
@github-actions

This comment has been minimized.

@mattdholloway mattdholloway self-assigned this May 22, 2026
@mattdholloway
Copy link
Copy Markdown
Author

@copilot resolve the merge conflicts in this pull request

Copy link
Copy Markdown
Contributor

Copilot AI commented May 26, 2026

@copilot resolve the merge conflicts in this pull request

Merged main and resolved conflicts in 0b38358 (dotnet/src/Client.cs, go/client.go, go/types.go, nodejs/src/client.ts, nodejs/src/index.ts, nodejs/src/types.ts, python/copilot/client.py, rust/src/session.rs, rust/src/types.rs, rust/tests/e2e/elicitation.rs). Builds + targeted tests pass (Go build, Node build + mcpAppsSandbox tests, Rust cargo check/MCP Apps unit tests, .NET net8.0 build, Python syntax). The Rust enable_mcp_apps field had stale #[serde(...)] attrs (the SessionConfig/ResumeSessionConfig structs are not Serialize); removed those and fixed the wire-flag tests to use the into_wire() signature on main.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 23/24 changed files
  • Comments generated: 1

Comment thread nodejs/src/client.ts Outdated
Comment on lines +93 to +102
* `--no-warnings`, or filter by the `name` field below.
*/
function warnIfMcpAppsDropped(
requested: boolean | undefined,
capabilities: { ui?: { mcpApps?: boolean } } | undefined
): void {
if (requested && !capabilities?.ui?.mcpApps) {
process.emitWarning(
"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.",
"McpAppsCapabilityDroppedWarning"
@mattdholloway
Copy link
Copy Markdown
Author

@copilot resolve the merge conflicts in this pull request

Copy link
Copy Markdown
Contributor

Copilot AI commented May 27, 2026

@copilot resolve the merge conflicts in this pull request

Merged main and resolved the conflict in java/src/main/java/com/github/copilot/CopilotClient.java in 8929669 (took main's new com.github.copilot.rpc.* / com.github.copilot.generated.rpc.* import paths from the Java repackaging on main, and kept the SessionCapabilities import added on this branch). All other files auto-merged. Java/Node/Go builds pass; Rust cargo check failed only on the bundled-CLI download in build.rs due to a sandbox TLS issue, unrelated to the merge.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #1335 · ● 6.5M

Comment thread nodejs/src/client.ts Outdated
*/
function warnIfMcpAppsDropped(
requested: boolean | undefined,
capabilities: { ui?: { mcpApps?: boolean } } | undefined
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.

Minor cross-SDK inconsistency: warnIfMcpAppsDropped omits the session ID

All other SDK implementations of this warning helper accept and include the session ID in the log output, e.g.:

  • Python: "Session %s: enable_mcp_apps was requested but ..." (passes session_id to logger.warning)
  • Go: "[copilot-sdk] Session %s: EnableMcpApps was requested but ..." (uses sessionID in log.Printf)
  • .NET: "Session {SessionId}: EnableMcpApps was requested but ..." (structured log field)
  • Java: "Session " + sessionId + ": enableMcpApps was requested but ..."
  • Rust: tracing::warn!(session_id = %session_id, "...")

The Node warning message says "the MCP Apps surface is unavailable for this session" without identifying which session, which makes it harder to correlate the warning to a specific session in multi-session hosts.

Suggestion: accept the sessionId as a third parameter and include it in the warning message for parity:

function warnIfMcpAppsDropped(
    requested: boolean | undefined,
    capabilities: { ui?: { mcpApps?: boolean } } | undefined,
    sessionId: string
): void {
    if (requested && !capabilities?.ui?.mcpApps) {
        process.emitWarning(
            `Session ${sessionId}: enableMcpApps was requested but the runtime did not advertise capabilities.ui.mcpApps. ...`,
            "McpAppsCapabilityDroppedWarning"
        );
    }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@copilot resolve this

@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

This PR adds enableMcpApps / enable_mcp_apps / EnableMcpApps support consistently across all six SDK implementations: Node.js, Python, Go, .NET, Java, and Rust.

Feature parity check

Feature Node.js Python Go .NET Java Rust
enableMcpApps on SessionConfig
enableMcpApps on ResumeSessionConfig
requestMcpApps wire field on create
requestMcpApps wire field on resume
Capability-drop warning
capabilities.ui.mcpApps type

Language-idiomatic warning mechanisms

Each SDK routes the capability-drop warning through its ecosystem-appropriate channel — process.emitWarning (Node), logger.warning (Python), log.Printf (Go), ILogger.LogWarning (.NET), java.util.logging (Java), tracing::warn! (Rust). This is correct and consistent with SDK idioms.

Node.js-specific iframe sandbox helpers

buildMcpAppsCspHeader and buildMcpAppsAllowAttribute in mcpAppsSandbox.ts are intentionally Node.js-specific — they construct HTTP headers and iframe allow attributes for web rendering contexts. Other language SDKs don't need these.

Naming conventions

All names correctly follow each language's convention:

  • TypeScript: enableMcpApps (camelCase) ✅
  • Python: enable_mcp_apps (snake_case) ✅
  • Go: EnableMcpApps (PascalCase exported) ✅
  • .NET: EnableMcpApps (PascalCase) ✅
  • Java: enableMcpApps / isEnableMcpApps() (camelCase) ✅
  • Rust: enable_mcp_apps (snake_case) ✅

No cross-SDK consistency issues found. 🎉

Generated by SDK Consistency Review Agent for issue #1335 · ● 3.9M ·

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.

4 participants