Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,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 All @@ -652,6 +653,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance

session.WorkspacePath = response.WorkspacePath;
session.SetCapabilities(response.Capabilities);
WarnIfMcpAppsDropped(config.EnableMcpApps, response.Capabilities, sessionId);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -793,6 +795,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 All @@ -811,6 +814,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes

session.WorkspacePath = response.WorkspacePath;
session.SetCapabilities(response.Capabilities);
WarnIfMcpAppsDropped(config.EnableMcpApps, response.Capabilities, sessionId);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1756,6 +1760,23 @@ private void RemoveSession(string sessionId)
_sessions.TryRemove(sessionId, out _);
}

/// <summary>
/// Emit a warning log when the consumer set <c>EnableMcpApps=true</c> on create/resume
/// but the runtime did not advertise <c>capabilities.ui.mcpApps</c> in the response.
/// The runtime silently drops the opt-in when its <c>MCP_APPS</c> feature flag (or
/// <c>COPILOT_MCP_APPS=true</c> env override) is unset, so without this warning a
/// consumer trying to use MCP Apps would see no error -- just tools that never expose
/// <c>_meta.ui.resourceUri</c>.
/// </summary>
private void WarnIfMcpAppsDropped(bool requested, SessionCapabilities? capabilities, string sessionId)
{
if (!requested) return;
if (capabilities?.Ui?.McpApps == true) return;
_logger?.LogWarning(
"Session {SessionId}: 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.",
sessionId);
}

/// <summary>
/// Disposes the <see cref="CopilotClient"/> synchronously.
/// </summary>
Expand Down Expand Up @@ -2034,6 +2055,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 @@ -2096,6 +2118,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
42 changes: 42 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,16 @@ public 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="SessionConfig.EnableMcpApps"/>
/// (or <see cref="ResumeSessionConfig.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>
public bool? McpApps { get; set; }
}

// ============================================================================
Expand Down Expand Up @@ -2065,6 +2075,7 @@ protected SessionConfig(SessionConfig? 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 @@ -2217,6 +2228,30 @@ protected SessionConfig(SessionConfig? other)
/// </summary>
public AutoModeSwitchHandler? OnAutoModeSwitch { 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>
public bool EnableMcpApps { get; set; }

/// <summary>
/// Hook handlers for session lifecycle events.
/// </summary>
Expand Down Expand Up @@ -2377,6 +2412,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
EnableConfigDiscovery = other.EnableConfigDiscovery;
ContinuePendingWork = other.ContinuePendingWork;
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
EnableMcpApps = other.EnableMcpApps;
Hooks = other.Hooks;
InfiniteSessions = other.InfiniteSessions;
McpServers = other.McpServers is not null
Expand Down Expand Up @@ -2507,6 +2543,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
/// </summary>
public AutoModeSwitchHandler? OnAutoModeSwitch { get; set; }

/// <summary>
/// Enable MCP Apps (SEP-1865) UI passthrough on the resumed session.
/// See <see cref="SessionConfig.EnableMcpApps"/>.
/// </summary>
public bool EnableMcpApps { get; set; }

/// <summary>
/// Hook handlers for session lifecycle events.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ import (

const noResultPermissionV2Error = "permission handlers cannot return 'no-result' when connected to a protocol v2 server"

// warnIfMcpAppsDropped emits a stderr warning when the consumer set
// EnableMcpApps=true on create/resume but the runtime did not advertise
// capabilities.ui.mcpApps in the response. The runtime silently drops the
// opt-in when its MCP_APPS feature flag (or COPILOT_MCP_APPS=true env
// override) is unset, so without this warning a consumer trying to use MCP
// Apps would see no error -- just tools that never expose _meta.ui.resourceUri.
func warnIfMcpAppsDropped(requested bool, capabilities *SessionCapabilities, sessionID string) {
if !requested {
return
}
if capabilities != nil && capabilities.UI != nil && capabilities.UI.McpApps {
return
}
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,
)
}

func validateSessionFsConfig(config *SessionFsConfig) error {
if config == nil {
return nil
Expand Down Expand Up @@ -670,6 +689,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if config.OnAutoModeSwitch != nil {
req.RequestAutoModeSwitch = Bool(true)
}
if config.EnableMcpApps {
req.RequestMcpApps = Bool(true)
}

if config.Streaming {
req.Streaming = Bool(true)
Expand Down Expand Up @@ -774,6 +796,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses

session.workspacePath = response.WorkspacePath
session.setCapabilities(response.Capabilities)
warnIfMcpAppsDropped(config.EnableMcpApps, response.Capabilities, sessionID)

return session, nil
}
Expand Down Expand Up @@ -882,6 +905,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.OnAutoModeSwitch != nil {
req.RequestAutoModeSwitch = Bool(true)
}
if config.EnableMcpApps {
req.RequestMcpApps = Bool(true)
}

traceparent, tracestate := getTraceContext(ctx)
req.Traceparent = traceparent
Expand Down Expand Up @@ -959,6 +985,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,

session.workspacePath = response.WorkspacePath
session.setCapabilities(response.Capabilities)
warnIfMcpAppsDropped(config.EnableMcpApps, response.Capabilities, sessionID)

return session, nil
}
Expand Down
31 changes: 31 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,26 @@ type SessionConfig struct {
// OnAutoModeSwitch is a handler for auto-mode-switch requests from the server.
// When provided, enables autoModeSwitch.request callbacks for the session.
OnAutoModeSwitch AutoModeSwitchHandler
// EnableMcpApps enables MCP Apps (SEP-1865) UI passthrough on this session.
//
// 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 @@ -784,6 +804,12 @@ 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.
McpApps bool `json:"mcpApps,omitempty"`
}

// ElicitationResult is the user's response to an elicitation dialog.
Expand Down Expand Up @@ -956,6 +982,9 @@ type ResumeSessionConfig struct {
// OnAutoModeSwitch is a handler for auto-mode-switch requests from the server.
// See SessionConfig.OnAutoModeSwitch.
OnAutoModeSwitch AutoModeSwitchHandler
// EnableMcpApps enables MCP Apps (SEP-1865) UI passthrough on resume.
// See SessionConfig.EnableMcpApps.
EnableMcpApps bool
}
type ProviderConfig struct {
// Type is the provider type: "openai", "azure", or "anthropic". Defaults to "openai".
Expand Down Expand Up @@ -1177,6 +1206,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 @@ -1233,6 +1263,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"`
Traceparent string `json:"traceparent,omitempty"`
Expand Down
28 changes: 26 additions & 2 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@
*/
const MIN_PROTOCOL_VERSION = 2;

/**
* Emit a `console.warn` when the consumer set `enableMcpApps: true` on
* create/resume but the runtime did not advertise `capabilities.ui.mcpApps`
* in the response. The runtime silently drops the opt-in when its `MCP_APPS`
* feature flag (or `COPILOT_MCP_APPS=true` env override) is unset, so without
* this warning a consumer trying to use MCP Apps would see no error — just
* tools that never expose `_meta.ui.resourceUri`.
*/
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. 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.`

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
process environment
as clear text.
);
}
}

/**
* Check if value is a Zod schema (has toJSONSchema method)
*/
Expand Down Expand Up @@ -823,6 +843,7 @@
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
requestElicitation: !!config.onElicitationRequest,
requestMcpApps: !!config.enableMcpApps,
requestExitPlanMode: !!config.onExitPlanMode,
requestAutoModeSwitch: !!config.onAutoModeSwitch,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
Expand All @@ -848,10 +869,11 @@
const { workspacePath, capabilities } = response as {
sessionId: string;
workspacePath?: string;
capabilities?: { ui?: { elicitation?: boolean } };
capabilities?: { ui?: { elicitation?: boolean; mcpApps?: boolean } };
};
session["_workspacePath"] = workspacePath;
session.setCapabilities(capabilities);
warnIfMcpAppsDropped(config.enableMcpApps, capabilities, sessionId);
} catch (e) {
this.sessions.delete(sessionId);
throw e;
Expand Down Expand Up @@ -962,6 +984,7 @@
config.onPermissionRequest !== defaultJoinSessionPermissionHandler,
requestUserInput: !!config.onUserInputRequest,
requestElicitation: !!config.onElicitationRequest,
requestMcpApps: !!config.enableMcpApps,
requestExitPlanMode: !!config.onExitPlanMode,
requestAutoModeSwitch: !!config.onAutoModeSwitch,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
Expand All @@ -988,10 +1011,11 @@
const { workspacePath, capabilities } = response as {
sessionId: string;
workspacePath?: string;
capabilities?: { ui?: { elicitation?: boolean } };
capabilities?: { ui?: { elicitation?: boolean; mcpApps?: boolean } };
};
session["_workspacePath"] = workspacePath;
session.setCapabilities(capabilities);
warnIfMcpAppsDropped(config.enableMcpApps, capabilities, sessionId);
} catch (e) {
this.sessions.delete(sessionId);
throw e;
Expand Down
6 changes: 6 additions & 0 deletions nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

export { CopilotClient } from "./client.js";
export { CopilotSession, type AssistantMessageEvent } from "./session.js";
export {
buildMcpAppsAllowAttribute,
buildMcpAppsCspHeader,
type McpAppsCspInput,
type McpAppsPermissionsInput,
} from "./mcpAppsSandbox.js";
export {
defineTool,
approveAll,
Expand Down
Loading
Loading