Skip to content

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

Merged
SteveSandersonMS merged 40 commits into
mainfrom
feat/mcp-apps-support
May 28, 2026
Merged

feat: add MCP Apps (SEP-1865) support#1335
SteveSandersonMS merged 40 commits into
mainfrom
feat/mcp-apps-support

Conversation

@mattdholloway

@mattdholloway mattdholloway commented May 19, 2026

Copy link
Copy Markdown
Contributor

Adds opt-in MCP Apps (SEP-1865) support across all SDKs: a new enableMcpApps session flag 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 detection — consumers inspect capabilities.ui.mcpApps on the create/resume response to detect whether the runtime honored the opt-in

Closes https://github.com/github/copilot-mcp-core/issues/1715

@mattdholloway

Copy link
Copy Markdown
Contributor 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 <223556219+Copilot@users.noreply.github.com>
@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.

@github-actions github-actions Bot 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.

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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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 <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>
@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 <223556219+Copilot@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
@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 <223556219+Copilot@users.noreply.github.com>
@mattdholloway

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

@github-actions

This comment has been minimized.

edburns pushed a commit that referenced this pull request Aug 4, 2026
* feat: add MCP Apps (SEP-1865) support

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>

* feat: add MCP Apps option to Python, Go, .NET, Rust SDKs

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>

* chore: prettier format mcpAppsSandbox files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: sanitize CSP domain inputs in mcpAppsSandbox (SEP-1865)

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>

* docs: note runtime MCP_APPS gate on enableMcpApps across SDKs

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>

* feat: surface capabilities.ui.mcpApps and warn on silent drop

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>

* fix: ruff format + add mcp_apps field to Rust e2e UiCapabilities literal

- 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>

* fix: drop sessionId from MCP Apps warning to silence CodeQL clear-text-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>

* feat: add enableMcpApps support to Java SDK

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>

* chore: address sanity-check findings

- 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>

* style: apply spotless formatting to Java MCP Apps additions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Omit requestMcpApps from wire payload when disabled

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>

* fix(mcp-apps): address high-priority review feedback

- 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>

* docs(mcp-apps): address worth-doing review feedback

- 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>

* fix(go): route MCP Apps warning through log.Default() instead of os.Stderr

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>

* Revert mode change on .githooks/pre-commit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: include session ID in warnIfMcpAppsDropped warning

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>

* fix: include session ID in warnIfMcpAppsDropped warning

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>

* remove nodejs specific mcp apps sandbox code

* fix: update session ID handling in MCP apps configuration

* refactor: remove MCP Apps warning handling from multiple clients

* style: remove unused SessionCapabilities import in Java client

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address review feedback on MCP Apps PR

- Fix broken @link in Java SessionConfig (com.github.copilot.sdk.CopilotSession -> com.github.copilot.CopilotSession)

- Revert stray mode change on .githooks/pre-commit (100755 -> 100644)

- Rust: make enable_mcp_apps Option<bool> for consistency with sibling opt-ins (e.g. enable_config_discovery)

- Python: remove stray blank line after logger init in client.py

- Drop incorrect 'the SDK also logs a warning' wording from Python docstrings (the SDK no longer emits a warning; only the runtime does)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: mark MCP Apps APIs as experimental

Mark the new SEP-1865 MCP Apps public APIs as experimental across .NET, Node, Python, Go and Rust SDKs, following each SDK's existing convention (e.g. canvas surface):

- .NET: [Experimental(Diagnostics.Experimental)] on SessionConfigBase.EnableMcpApps and SessionUiCapabilities.McpApps. No #pragma needed at internal call sites because GHCP001 is in the project's NoWarn.

- Node: @experimental JSDoc tag on SessionConfigBase.enableMcpApps and SessionCapabilities.ui.mcpApps.

- Python: **Experimental.** lead-in on enable_mcp_apps parameter docstrings (create_session, resume_session) and SessionUiCapabilities.mcpApps.

- Go: // Experimental: ... doc lines on SessionConfig.EnableMcpApps, ResumeSessionConfig.EnableMcpApps and UICapabilities.McpApps.

- Rust: **Experimental.** first paragraph on SessionConfig.enable_mcp_apps, ResumeSessionConfig.enable_mcp_apps, with_enable_mcp_apps (x2) and UiCapabilities.mcp_apps.

Java is intentionally skipped — the repo has no precedent for marking Java APIs as experimental, so introducing a convention here is out of scope for this commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(rust): drop skip_serializing_if from request_mcp_apps wire field

Match the surrounding request_* bool fields (request_user_input, request_permission, request_exit_plan_mode, request_auto_mode_switch, request_elicitation, hooks) which all serialize unconditionally. Snapshots don't capture these fields so there is no compatibility cost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* style: spotless reflow on SessionConfig MCP Apps javadoc

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot <copilot@github.com>
Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
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.

5 participants