Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0827b5a
feat: add MCP Apps (SEP-1865) support
mattdholloway May 19, 2026
0d544c0
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 19, 2026
f251469
feat: add MCP Apps option to Python, Go, .NET, Rust SDKs
mattdholloway May 20, 2026
d90bb0f
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 20, 2026
7c62138
chore: prettier format mcpAppsSandbox files
mattdholloway May 20, 2026
b301fe4
fix: sanitize CSP domain inputs in mcpAppsSandbox (SEP-1865)
mattdholloway May 20, 2026
8f8b8cf
docs: note runtime MCP_APPS gate on enableMcpApps across SDKs
mattdholloway May 20, 2026
cba4220
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 20, 2026
53c1a2b
feat: surface capabilities.ui.mcpApps and warn on silent drop
mattdholloway May 20, 2026
f390934
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 20, 2026
e29ea92
fix: ruff format + add mcp_apps field to Rust e2e UiCapabilities literal
mattdholloway May 20, 2026
af3ee8a
fix: drop sessionId from MCP Apps warning to silence CodeQL clear-tex…
mattdholloway May 21, 2026
fb8cefd
Merge remote-tracking branch 'origin/main' into feat/mcp-apps-support
mattdholloway May 21, 2026
93fa5fe
Merge remote-tracking branch 'origin/main' into feat/mcp-apps-support
mattdholloway May 22, 2026
c3e1524
feat: add enableMcpApps support to Java SDK
mattdholloway May 22, 2026
8541da5
Merge remote-tracking branch 'origin/main' into feat/mcp-apps-support
mattdholloway May 22, 2026
9ab8617
chore: address sanity-check findings
mattdholloway May 22, 2026
7c06494
style: apply spotless formatting to Java MCP Apps additions
mattdholloway May 22, 2026
cf5ecb3
Omit requestMcpApps from wire payload when disabled
mattdholloway May 22, 2026
82ce21e
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 22, 2026
3d41f8e
fix(mcp-apps): address high-priority review feedback
mattdholloway May 22, 2026
f61d1fe
docs(mcp-apps): address worth-doing review feedback
mattdholloway May 22, 2026
6c19fa2
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 22, 2026
6ce0f03
fix(go): route MCP Apps warning through log.Default() instead of os.S…
mattdholloway May 22, 2026
0b38358
Merge main into feat/mcp-apps-support and resolve conflicts
Copilot May 26, 2026
8929669
Merge main into feat/mcp-apps-support and resolve conflicts
May 27, 2026
40ab151
Revert mode change on .githooks/pre-commit
mattdholloway May 27, 2026
24917fb
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 27, 2026
6e902df
fix: include session ID in warnIfMcpAppsDropped warning
May 27, 2026
fb4272f
fix: include session ID in warnIfMcpAppsDropped warning
Copilot May 27, 2026
f31c88c
Merge main into feat/mcp-apps-support and resolve conflicts
Copilot May 28, 2026
9c6b2ab
Merge branch 'main' into feat/mcp-apps-support
mattdholloway May 28, 2026
c8d0326
remove nodejs specific mcp apps sandbox code
mattdholloway May 28, 2026
8b2be40
fix: update session ID handling in MCP apps configuration
mattdholloway May 28, 2026
d6bc52d
refactor: remove MCP Apps warning handling from multiple clients
mattdholloway May 28, 2026
233eb50
style: remove unused SessionCapabilities import in Java client
mattdholloway May 28, 2026
035e9c5
fix: address review feedback on MCP Apps PR
SteveSandersonMS May 28, 2026
8a76b32
docs: mark MCP Apps APIs as experimental
SteveSandersonMS May 28, 2026
5e8234b
refactor(rust): drop skip_serializing_if from request_mcp_apps wire f…
SteveSandersonMS May 28, 2026
b951466
style: spotless reflow on SessionConfig MCP Apps javadoc
SteveSandersonMS May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
config.InfiniteSessions,
Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description)).ToList(),
RequestElicitation: config.OnElicitationRequest != null,
RequestMcpApps: config.EnableMcpApps ? true : null,
Traceparent: traceparent,
Tracestate: tracestate,
ModelCapabilities: config.ModelCapabilities,
Expand Down Expand Up @@ -1059,6 +1060,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
config.InfiniteSessions,
Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description)).ToList(),
RequestElicitation: config.OnElicitationRequest != null,
RequestMcpApps: config.EnableMcpApps ? true : null,
Traceparent: traceparent,
Tracestate: tracestate,
ModelCapabilities: config.ModelCapabilities,
Expand Down Expand Up @@ -2171,6 +2173,7 @@ internal record CreateSessionRequest(
InfiniteSessionConfig? InfiniteSessions,
IList<CommandWireDefinition>? Commands = null,
bool? RequestElicitation = null,
bool? RequestMcpApps = null,
string? Traceparent = null,
string? Tracestate = null,
ModelCapabilitiesOverride? ModelCapabilities = null,
Expand Down Expand Up @@ -2243,6 +2246,7 @@ internal record ResumeSessionRequest(
InfiniteSessionConfig? InfiniteSessions,
IList<CommandWireDefinition>? Commands = null,
bool? RequestElicitation = null,
bool? RequestMcpApps = null,
string? Traceparent = null,
string? Tracestate = null,
ModelCapabilitiesOverride? ModelCapabilities = null,
Expand Down
36 changes: 36 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,16 @@ public sealed class SessionUiCapabilities
/// Whether the host supports interactive elicitation dialogs.
/// </summary>
public bool? Elicitation { get; set; }

/// <summary>
/// Whether the runtime has accepted the session's MCP Apps (SEP-1865) opt-in.
/// <c>true</c> when the consumer set <see cref="SessionConfigBase.EnableMcpApps"/>
/// to <c>true</c> on create/resume <b>and</b> the runtime's <c>MCP_APPS</c> feature flag
/// (or <c>COPILOT_MCP_APPS=true</c> env override) is on. Otherwise absent or
/// <c>false</c>, indicating the runtime silently dropped the opt-in.
/// </summary>
[Experimental(Diagnostics.Experimental)]
public bool? McpApps { get; set; }
}

// ============================================================================
Expand Down Expand Up @@ -2346,6 +2356,7 @@ protected SessionConfigBase(SessionConfigBase? other)
Agent = other.Agent;
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
EnableConfigDiscovery = other.EnableConfigDiscovery;
EnableMcpApps = other.EnableMcpApps;
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
Hooks = other.Hooks;
InfiniteSessions = other.InfiniteSessions;
Expand Down Expand Up @@ -2507,6 +2518,31 @@ protected SessionConfigBase(SessionConfigBase? other)
/// <summary>Handler for auto-mode-switch requests from the server.</summary>
public Func<AutoModeSwitchRequest, AutoModeSwitchInvocation, Task<AutoModeSwitchResponse>>? OnAutoModeSwitchRequest { get; set; }

/// <summary>
/// Enable MCP Apps (SEP-1865) UI passthrough on this session.
/// <para>
/// When <c>true</c> <b>and</b> the runtime has MCP Apps enabled (via the
/// <c>MCP_APPS</c> feature flag or <c>COPILOT_MCP_APPS=true</c> environment override), the
/// runtime adds the <c>mcp-apps</c> capability to the session, which causes it to advertise
/// the <c>extensions.io.modelcontextprotocol/ui</c> extension to MCP servers (so they expose
/// <c>_meta.ui.resourceUri</c> on tools) and to expose the
/// <c>session.rpc.mcp.apps.{listTools,callTool,readResource,setHostContext,getHostContext,diagnose}</c>
/// JSON-RPC methods.
/// </para>
/// <para>
/// If the runtime gate is off, the opt-in is silently dropped server-side (the runtime logs a
/// warning); the session is created normally but the MCP Apps surface is unavailable. Inspect
/// the runtime's <c>capabilities.ui.mcpApps</c> on the create/resume response to detect this.
/// </para>
/// <para>
/// SDK consumers MUST set this to <c>true</c> only when they have an iframe renderer that can
/// display <c>ui://</c> MCP App bundles. Setting it without a renderer will cause MCP servers
/// to register UI-enabled tool variants the consumer cannot display.
/// </para>
/// </summary>
[Experimental(Diagnostics.Experimental)]
public bool EnableMcpApps { get; set; }

/// <summary>Hook handlers for session lifecycle events.</summary>
public SessionHooks? Hooks { get; set; }

Expand Down
6 changes: 6 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if config.OnAutoModeSwitchRequest != nil {
req.RequestAutoModeSwitch = Bool(true)
}
if config.EnableMcpApps {
req.RequestMcpApps = Bool(true)
}

if config.Streaming != nil {
req.Streaming = config.Streaming
Expand Down Expand Up @@ -985,6 +988,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.OnAutoModeSwitchRequest != nil {
req.RequestAutoModeSwitch = Bool(true)
}
if config.EnableMcpApps {
req.RequestMcpApps = Bool(true)
}

traceparent, tracestate := getTraceContext(ctx)
req.Traceparent = traceparent
Expand Down
59 changes: 59 additions & 0 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,65 @@ func TestResumeSessionRequest_RequestElicitation(t *testing.T) {
})
}

func TestCreateSessionRequest_RequestMcpApps(t *testing.T) {
t.Run("sends requestMcpApps flag when EnableMcpApps is set", func(t *testing.T) {
req := createSessionRequest{
RequestMcpApps: Bool(true),
}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if m["requestMcpApps"] != true {
t.Errorf("Expected requestMcpApps to be true, got %v", m["requestMcpApps"])
}
})

t.Run("does not send requestMcpApps when EnableMcpApps is unset", func(t *testing.T) {
req := createSessionRequest{}
data, _ := json.Marshal(req)
var m map[string]any
json.Unmarshal(data, &m)
if _, ok := m["requestMcpApps"]; ok {
t.Error("Expected requestMcpApps to be omitted when not set")
}
})
}

func TestResumeSessionRequest_RequestMcpApps(t *testing.T) {
t.Run("sends requestMcpApps flag when EnableMcpApps is set", func(t *testing.T) {
req := resumeSessionRequest{
SessionID: "s1",
RequestMcpApps: Bool(true),
}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if m["requestMcpApps"] != true {
t.Errorf("Expected requestMcpApps to be true, got %v", m["requestMcpApps"])
}
})

t.Run("does not send requestMcpApps when EnableMcpApps is unset", func(t *testing.T) {
req := resumeSessionRequest{SessionID: "s1"}
data, _ := json.Marshal(req)
var m map[string]any
json.Unmarshal(data, &m)
if _, ok := m["requestMcpApps"]; ok {
t.Error("Expected requestMcpApps to be omitted when not set")
}
})
}

func TestResumeSessionRequest_ModeCallbackFlags(t *testing.T) {
req := resumeSessionRequest{
SessionID: "s1",
Expand Down
40 changes: 40 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,29 @@ type SessionConfig struct {
// OnAutoModeSwitchRequest is a handler for auto-mode-switch requests from the server.
// When provided, enables autoModeSwitch.request callbacks for the session.
OnAutoModeSwitchRequest AutoModeSwitchRequestHandler
// EnableMcpApps enables MCP Apps (SEP-1865) UI passthrough on this session.
//
// Experimental: EnableMcpApps is part of an experimental wire-protocol
// surface (SEP-1865) and may change or be removed in a future release.
//
// When true AND the runtime has MCP Apps enabled (via the MCP_APPS feature
// flag or COPILOT_MCP_APPS=true environment override), the runtime adds the
// mcp-apps capability to the session, which causes it to advertise the
// extensions.io.modelcontextprotocol/ui 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.
//
// If the runtime gate is off, the opt-in is silently dropped server-side
// (the runtime logs a warning); the session is created normally but the
// MCP Apps surface is unavailable. Inspect the runtime's
// capabilities.ui.mcpApps on the create/resume response to detect this.
//
// SDK consumers MUST set this to true only when they have an iframe renderer
// that can display ui:// MCP App bundles. Setting it without a renderer will
// cause MCP servers to register UI-enabled tool variants the consumer cannot
// display.
EnableMcpApps bool
// GitHubToken is an optional per-session GitHub token used for authentication.
// When provided, the session authenticates as the token's owner instead of
// using the global client-level auth.
Expand Down Expand Up @@ -1089,6 +1112,15 @@ type SessionCapabilities struct {
type UICapabilities struct {
// Elicitation indicates whether the host supports interactive elicitation dialogs.
Elicitation bool `json:"elicitation,omitempty"`
// McpApps indicates whether the runtime has accepted the session's MCP Apps
// (SEP-1865) opt-in. True when the consumer set EnableMcpApps=true on
// create/resume AND the runtime's MCP_APPS feature flag (or
// COPILOT_MCP_APPS=true env override) is on. Otherwise false, indicating
// the runtime silently dropped the opt-in.
//
// Experimental: McpApps is part of an experimental wire-protocol surface
// (SEP-1865) and may change or be removed in a future release.
McpApps bool `json:"mcpApps,omitempty"`
}

// ElicitationResult is the user's response to an elicitation dialog.
Expand Down Expand Up @@ -1275,6 +1307,12 @@ type ResumeSessionConfig struct {
// OnAutoModeSwitchRequest is a handler for auto-mode-switch requests from the server.
// See SessionConfig.OnAutoModeSwitchRequest.
OnAutoModeSwitchRequest AutoModeSwitchRequestHandler
// EnableMcpApps enables MCP Apps (SEP-1865) UI passthrough on resume.
// See SessionConfig.EnableMcpApps.
//
// Experimental: EnableMcpApps is part of an experimental wire-protocol
// surface (SEP-1865) and may change or be removed in a future release.
EnableMcpApps bool
// Canvases declares canvases this session provides. Sent over the wire on
// `session.resume`. See SessionConfig.Canvases.
Canvases []CanvasDeclaration
Expand Down Expand Up @@ -1534,6 +1572,7 @@ type createSessionRequest struct {
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
Commands []wireCommand `json:"commands,omitempty"`
RequestElicitation *bool `json:"requestElicitation,omitempty"`
RequestMcpApps *bool `json:"requestMcpApps,omitempty"`
GitHubToken string `json:"gitHubToken,omitempty"`
RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"`
Cloud *CloudSessionOptions `json:"cloud,omitempty"`
Expand Down Expand Up @@ -1599,6 +1638,7 @@ type resumeSessionRequest struct {
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
Commands []wireCommand `json:"commands,omitempty"`
RequestElicitation *bool `json:"requestElicitation,omitempty"`
RequestMcpApps *bool `json:"requestMcpApps,omitempty"`
GitHubToken string `json:"gitHubToken,omitempty"`
RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"`
Canvases []CanvasDeclaration `json:"canvases,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
if (config.getOnElicitationRequest() != null) {
request.setRequestElicitation(true);
}
if (config.isEnableMcpApps()) {
request.setRequestMcpApps(true);
}
if (config.getOnExitPlanMode() != null) {
request.setRequestExitPlanMode(true);
}
Expand Down Expand Up @@ -238,6 +241,9 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
if (config.getOnElicitationRequest() != null) {
request.setRequestElicitation(true);
}
if (config.isEnableMcpApps()) {
request.setRequestMcpApps(true);
}
if (config.getOnExitPlanMode() != null) {
request.setRequestExitPlanMode(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ public final class CreateSessionRequest {
@JsonProperty("requestElicitation")
private Boolean requestElicitation;

@JsonProperty("requestMcpApps")
private Boolean requestMcpApps;

@JsonProperty("requestExitPlanMode")
private Boolean requestExitPlanMode;

Expand Down Expand Up @@ -503,6 +506,21 @@ public void clearRequestElicitation() {
this.requestElicitation = null;
}

/** Gets the requestMcpApps flag. @return the flag */
public Boolean getRequestMcpApps() {
return requestMcpApps;
}

/** Sets the requestMcpApps flag. @param requestMcpApps the flag */
public void setRequestMcpApps(boolean requestMcpApps) {
this.requestMcpApps = requestMcpApps;
}

/** Clears the requestMcpApps setting, reverting to the default behavior. */
public void clearRequestMcpApps() {
this.requestMcpApps = null;
}

/** Gets the requestExitPlanMode flag. @return the flag */
public Boolean getRequestExitPlanMode() {
return requestExitPlanMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class ResumeSessionConfig {
private ElicitationHandler onElicitationRequest;
private ExitPlanModeHandler onExitPlanMode;
private AutoModeSwitchHandler onAutoModeSwitch;
private boolean enableMcpApps;
private String gitHubToken;
private String remoteSession;

Expand Down Expand Up @@ -972,6 +973,31 @@ public ResumeSessionConfig setOnElicitationRequest(ElicitationHandler onElicitat
return this;
}

/**
* Returns whether MCP Apps (SEP-1865) UI passthrough is enabled on resume.
*
* @return {@code true} if the consumer has opted into MCP Apps, otherwise
* {@code false}
* @see #setEnableMcpApps(boolean)
*/
public boolean isEnableMcpApps() {
return enableMcpApps;
}

/**
* Enables MCP Apps (SEP-1865) UI passthrough on the resumed session. See
* {@link SessionConfig#setEnableMcpApps(boolean)} for full semantics (runtime
* gate, capability inspection, renderer requirement).
*
* @param enableMcpApps
* {@code true} to opt into MCP Apps support on resume
* @return this config for method chaining
*/
public ResumeSessionConfig setEnableMcpApps(boolean enableMcpApps) {
this.enableMcpApps = enableMcpApps;
return this;
}

/**
* Gets the exit-plan-mode request handler.
*
Expand Down Expand Up @@ -1129,6 +1155,7 @@ public ResumeSessionConfig clone() {
copy.onElicitationRequest = this.onElicitationRequest;
copy.onExitPlanMode = this.onExitPlanMode;
copy.onAutoModeSwitch = this.onAutoModeSwitch;
copy.enableMcpApps = this.enableMcpApps;
copy.gitHubToken = this.gitHubToken;
copy.remoteSession = this.remoteSession;
return copy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ public final class ResumeSessionRequest {
@JsonProperty("requestElicitation")
private Boolean requestElicitation;

@JsonProperty("requestMcpApps")
private Boolean requestMcpApps;

@JsonProperty("requestExitPlanMode")
private Boolean requestExitPlanMode;

Expand Down Expand Up @@ -528,6 +531,21 @@ public void clearRequestElicitation() {
this.requestElicitation = null;
}

/** Gets the requestMcpApps flag. @return the flag */
public Boolean getRequestMcpApps() {
return requestMcpApps;
}

/** Sets the requestMcpApps flag. @param requestMcpApps the flag */
public void setRequestMcpApps(boolean requestMcpApps) {
this.requestMcpApps = requestMcpApps;
}

/** Clears the requestMcpApps setting, reverting to the default behavior. */
public void clearRequestMcpApps() {
this.requestMcpApps = null;
}

/** Gets the requestExitPlanMode flag. @return the flag */
public Boolean getRequestExitPlanMode() {
return requestExitPlanMode;
Expand Down
Loading
Loading