From 290c4fa9f9eb42a59f4c27650f4a237a5cb77687 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Jul 2026 11:10:11 -0700 Subject: [PATCH 1/4] sdk: Expose githubMcpToolConfig across languages Adds the `githubMcpToolConfig` session option to `session.create` and `session.resume` in all six SDKs, forwarding the built-in GitHub MCP server settings the runtime already accepts: `enableAllTools`, `additionalToolsets`, `additionalTools`, `enableInsidersMode`, and the new `disableFormDeferral`. `disableFormDeferral` lets autonomous SDK workflows opt out of MCP App form deferral, so form-backed GitHub write tools execute directly instead of returning an awaiting-form stub. It requires MCP Apps to be enabled for the session. The option is omitted from the wire payload entirely when unset, so existing consumers see no change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Client.cs | 8 +- dotnet/src/Types.cs | 51 ++++++ dotnet/test/Unit/SerializationTests.cs | 45 ++++++ go/client.go | 2 + go/client_test.go | 60 +++++++ go/types.go | 22 +++ .../github/copilot/SessionRequestBuilder.java | 2 + .../copilot/rpc/CreateSessionRequest.java | 13 ++ .../copilot/rpc/GitHubMcpToolConfig.java | 80 +++++++++ .../copilot/rpc/ResumeSessionConfig.java | 23 +++ .../copilot/rpc/ResumeSessionRequest.java | 13 ++ .../com/github/copilot/rpc/SessionConfig.java | 23 +++ .../copilot/SessionRequestBuilderTest.java | 23 +++ nodejs/src/client.ts | 6 + nodejs/src/index.ts | 1 + nodejs/src/types.ts | 22 +++ nodejs/test/client.test.ts | 49 ++++++ python/copilot/__init__.py | 2 + python/copilot/client.py | 27 ++++ python/copilot/session.py | 16 ++ python/test_client.py | 40 +++++ rust/src/types.rs | 153 +++++++++++++++++- rust/src/wire.rs | 11 +- 23 files changed, 681 insertions(+), 11 deletions(-) create mode 100644 java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index c8d83dfee2..ca37ea6f30 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1196,6 +1196,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, + GitHubMcpToolConfig: config.GitHubMcpToolConfig, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); var rpcTimestamp = Stopwatch.GetTimestamp(); @@ -1410,6 +1411,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, + GitHubMcpToolConfig: config.GitHubMcpToolConfig, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); var rpcTimestamp = Stopwatch.GetTimestamp(); @@ -2762,7 +2764,8 @@ internal record CreateSessionRequest( OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, - bool? EnableGitHubTelemetryForwarding = null); + bool? EnableGitHubTelemetryForwarding = null, + [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null); #pragma warning restore GHCP001 internal record ToolDefinition( @@ -2868,7 +2871,8 @@ internal record ResumeSessionRequest( OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, - bool? EnableGitHubTelemetryForwarding = null); + bool? EnableGitHubTelemetryForwarding = null, + [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null); #pragma warning restore GHCP001 internal record ResumeSessionResponse( diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 85248707fd..f3523b0f68 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -2957,6 +2957,36 @@ public sealed class CopilotExpAssignmentResponse public string AssignmentContext { get; set; } = string.Empty; } +/// +/// Configuration for the built-in GitHub MCP server. +/// +public sealed class GitHubMcpToolConfig +{ + /// Enables all GitHub MCP tools. + [JsonPropertyName("enableAllTools")] + public bool? EnableAllTools { get; set; } + + /// Additional GitHub MCP toolsets to enable. + [JsonPropertyName("additionalToolsets")] + public IList? AdditionalToolsets { get; set; } + + /// Additional GitHub MCP tools to enable. + [JsonPropertyName("additionalTools")] + public IList? AdditionalTools { get; set; } + + /// Enables GitHub MCP insiders-mode tools. + [JsonPropertyName("enableInsidersMode")] + public bool? EnableInsidersMode { get; set; } + + /// + /// Disables form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + /// + [JsonPropertyName("disableFormDeferral")] + public bool? DisableFormDeferral { get; set; } +} + /// /// Shared configuration properties for creating or resuming a Copilot session. /// Use when creating a new session, or @@ -2994,6 +3024,20 @@ protected SessionConfigBase(SessionConfigBase? other) EnableSessionStore = other.EnableSessionStore; EnableSkills = other.EnableSkills; EnableMcpApps = other.EnableMcpApps; + GitHubMcpToolConfig = other.GitHubMcpToolConfig is null + ? null + : new GitHubMcpToolConfig + { + EnableAllTools = other.GitHubMcpToolConfig.EnableAllTools, + AdditionalToolsets = other.GitHubMcpToolConfig.AdditionalToolsets is not null + ? [.. other.GitHubMcpToolConfig.AdditionalToolsets] + : null, + AdditionalTools = other.GitHubMcpToolConfig.AdditionalTools is not null + ? [.. other.GitHubMcpToolConfig.AdditionalTools] + : null, + EnableInsidersMode = other.GitHubMcpToolConfig.EnableInsidersMode, + DisableFormDeferral = other.GitHubMcpToolConfig.DisableFormDeferral, + }; ExcludedBuiltInAgents = other.ExcludedBuiltInAgents is not null ? [.. other.ExcludedBuiltInAgents] : null; ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null; Hooks = other.Hooks; @@ -3309,6 +3353,13 @@ protected SessionConfigBase(SessionConfigBase? other) [Experimental(Diagnostics.Experimental)] public bool EnableMcpApps { get; set; } + /// + /// Configuration for the built-in GitHub MCP server. + /// DisableFormDeferral only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + /// + public GitHubMcpToolConfig? GitHubMcpToolConfig { get; set; } + /// Hook handlers for session lifecycle events. public SessionHooks? Hooks { get; set; } diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index 6aaee01e77..8ef5fedc4b 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -299,6 +299,51 @@ public void SessionRequests_OmitCapiOptions_WhenUnset() Assert.False(resumeDocument.RootElement.TryGetProperty("capi", out _)); } + [Fact] + public void SessionRequests_CanSerializeGitHubMcpToolConfig_WithSdkOptions() + { + var options = GetSerializerOptions(); + var githubConfig = new GitHubMcpToolConfig + { + EnableAllTools = true, + AdditionalToolsets = ["repos"], + AdditionalTools = ["get_issue"], + EnableInsidersMode = true, + DisableFormDeferral = true, + }; + + var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest"); + var createRequest = CreateInternalRequest( + createRequestType, + ("GitHubMcpToolConfig", githubConfig)); + using var createDocument = JsonDocument.Parse(JsonSerializer.Serialize(createRequest, createRequestType, options)); + var createConfig = createDocument.RootElement.GetProperty("githubMcpToolConfig"); + Assert.True(createConfig.GetProperty("enableAllTools").GetBoolean()); + Assert.Equal("repos", createConfig.GetProperty("additionalToolsets")[0].GetString()); + Assert.True(createConfig.GetProperty("disableFormDeferral").GetBoolean()); + + var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest"); + var resumeRequest = CreateInternalRequest( + resumeRequestType, + ("SessionId", "session-id"), + ("GitHubMcpToolConfig", githubConfig)); + using var resumeDocument = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequest, resumeRequestType, options)); + Assert.True(resumeDocument.RootElement.TryGetProperty("githubMcpToolConfig", out _)); + } + + [Fact] + public void SessionRequests_OmitGitHubMcpToolConfig_WhenUnset() + { + var options = GetSerializerOptions(); + foreach (var requestName in new[] { "CreateSessionRequest", "ResumeSessionRequest" }) + { + var requestType = GetNestedType(typeof(CopilotClient), requestName); + var request = CreateInternalRequest(requestType, ("SessionId", "session-id")); + using var document = JsonDocument.Parse(JsonSerializer.Serialize(request, requestType, options)); + Assert.False(document.RootElement.TryGetProperty("githubMcpToolConfig", out _)); + } + } + [Fact] public void SessionRequests_CanSerializeReasoningSummary_WithSdkOptions() { diff --git a/go/client.go b/go/client.go index 0d07e21f54..0c5ac7a396 100644 --- a/go/client.go +++ b/go/client.go @@ -848,6 +848,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if config.EnableMCPApps { req.RequestMCPApps = Bool(true) } + req.GitHubMCPToolConfig = config.GitHubMCPToolConfig if config.Streaming != nil { req.Streaming = config.Streaming @@ -1220,6 +1221,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, if config.EnableMCPApps { req.RequestMCPApps = Bool(true) } + req.GitHubMCPToolConfig = config.GitHubMCPToolConfig traceparent, tracestate := getTraceContext(ctx) req.Traceparent = traceparent diff --git a/go/client_test.go b/go/client_test.go index 3e4675d0b3..8f0bd080a9 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -2363,6 +2363,66 @@ func TestResumeSessionRequest_RequestMCPApps(t *testing.T) { }) } +func TestSessionRequests_GitHubMCPToolConfig(t *testing.T) { + config := &GitHubMCPToolConfig{ + EnableAllTools: Bool(true), + AdditionalToolsets: []string{"repos"}, + AdditionalTools: []string{"get_issue"}, + EnableInsidersMode: Bool(true), + DisableFormDeferral: Bool(true), + } + expected := map[string]any{ + "enableAllTools": true, + "additionalToolsets": []any{"repos"}, + "additionalTools": []any{"get_issue"}, + "enableInsidersMode": true, + "disableFormDeferral": true, + } + + t.Run("create", func(t *testing.T) { + data, err := json.Marshal(createSessionRequest{GitHubMCPToolConfig: config}) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if !reflect.DeepEqual(payload["githubMcpToolConfig"], expected) { + t.Fatalf("Unexpected githubMcpToolConfig: %#v", payload["githubMcpToolConfig"]) + } + }) + + t.Run("resume", func(t *testing.T) { + data, err := json.Marshal(resumeSessionRequest{ + SessionID: "s1", + GitHubMCPToolConfig: config, + }) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if !reflect.DeepEqual(payload["githubMcpToolConfig"], expected) { + t.Fatalf("Unexpected githubMcpToolConfig: %#v", payload["githubMcpToolConfig"]) + } + }) + + t.Run("unset is omitted", func(t *testing.T) { + data, err := json.Marshal(createSessionRequest{}) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + _ = json.Unmarshal(data, &payload) + if _, ok := payload["githubMcpToolConfig"]; ok { + t.Fatal("Expected githubMcpToolConfig to be omitted") + } + }) +} + func TestResumeSessionRequest_ModeCallbackFlags(t *testing.T) { req := resumeSessionRequest{ SessionID: "s1", diff --git a/go/types.go b/go/types.go index b0fd9c06ef..31a8e355d9 100644 --- a/go/types.go +++ b/go/types.go @@ -1140,6 +1140,18 @@ func (e ExpConfigEntry) MarshalJSON() ([]byte, error) { return json.Marshal(w) } +// GitHubMCPToolConfig configures the built-in GitHub MCP server. +// +// DisableFormDeferral only applies to the built-in GitHub MCP server and only +// has an effect when MCP Apps and form-backed GitHub tools are enabled. +type GitHubMCPToolConfig struct { + EnableAllTools *bool `json:"enableAllTools,omitempty"` + AdditionalToolsets []string `json:"additionalToolsets,omitempty"` + AdditionalTools []string `json:"additionalTools,omitempty"` + EnableInsidersMode *bool `json:"enableInsidersMode,omitempty"` + DisableFormDeferral *bool `json:"disableFormDeferral,omitempty"` +} + // SessionConfig configures a new session type SessionConfig struct { // SessionID is an optional custom session ID @@ -1379,6 +1391,10 @@ type SessionConfig struct { // cause MCP servers to register UI-enabled tool variants the consumer cannot // display. EnableMCPApps bool + // GitHubMCPToolConfig configures the built-in GitHub MCP server. + // DisableFormDeferral only applies to that server and only has an effect + // when MCP Apps and form-backed GitHub tools are enabled. + GitHubMCPToolConfig *GitHubMCPToolConfig // 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. @@ -1847,6 +1863,10 @@ type ResumeSessionConfig struct { // Experimental: EnableMCPApps is part of an experimental wire-protocol // surface (SEP-1865) and may change or be removed in a future release. EnableMCPApps bool + // GitHubMCPToolConfig configures the built-in GitHub MCP server. + // DisableFormDeferral only applies to that server and only has an effect + // when MCP Apps and form-backed GitHub tools are enabled. + GitHubMCPToolConfig *GitHubMCPToolConfig // Canvases declares canvases this session provides. Sent over the wire on // `session.resume`. See SessionConfig.Canvases. Canvases []CanvasDeclaration @@ -2327,6 +2347,7 @@ type createSessionRequest struct { Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` RequestMCPApps *bool `json:"requestMcpApps,omitempty"` + GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Cloud *CloudSessionOptions `json:"cloud,omitempty"` @@ -2419,6 +2440,7 @@ type resumeSessionRequest struct { Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` RequestMCPApps *bool `json:"requestMcpApps,omitempty"` + GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` diff --git a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java index 57d9a46a21..545d8a9f20 100644 --- a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -176,6 +176,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess if (config.isEnableMcpApps()) { request.setRequestMcpApps(true); } + request.setGitHubMcpToolConfig(config.getGitHubMcpToolConfig()); if (config.getOnExitPlanMode() != null) { request.setRequestExitPlanMode(true); } @@ -300,6 +301,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo if (config.isEnableMcpApps()) { request.setRequestMcpApps(true); } + request.setGitHubMcpToolConfig(config.getGitHubMcpToolConfig()); if (config.getOnExitPlanMode() != null) { request.setRequestExitPlanMode(true); } diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index 9a9f4f152f..ccf538baef 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -193,6 +193,9 @@ public final class CreateSessionRequest { @JsonProperty("requestMcpApps") private Boolean requestMcpApps; + @JsonProperty("githubMcpToolConfig") + private GitHubMcpToolConfig githubMcpToolConfig; + @JsonProperty("requestExitPlanMode") private Boolean requestExitPlanMode; @@ -902,6 +905,16 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } + /** Gets the built-in GitHub MCP tool configuration. @return the configuration */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** Sets the built-in GitHub MCP tool configuration. @param config the configuration */ + public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { + this.githubMcpToolConfig = config; + } + /** Gets the requestExitPlanMode flag. @return the flag */ public Boolean getRequestExitPlanMode() { return requestExitPlanMode; diff --git a/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java b/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java new file mode 100644 index 0000000000..d5bc0157da --- /dev/null +++ b/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Configuration for the built-in GitHub MCP server. + * + *

{@code disableFormDeferral} only applies to the built-in GitHub MCP server + * and only has an effect when MCP Apps and form-backed GitHub tools are enabled. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GitHubMcpToolConfig { + + @JsonProperty("enableAllTools") + private Boolean enableAllTools; + + @JsonProperty("additionalToolsets") + private List additionalToolsets; + + @JsonProperty("additionalTools") + private List additionalTools; + + @JsonProperty("enableInsidersMode") + private Boolean enableInsidersMode; + + @JsonProperty("disableFormDeferral") + private Boolean disableFormDeferral; + + public Boolean getEnableAllTools() { + return enableAllTools; + } + + public GitHubMcpToolConfig setEnableAllTools(Boolean enableAllTools) { + this.enableAllTools = enableAllTools; + return this; + } + + public List getAdditionalToolsets() { + return additionalToolsets; + } + + public GitHubMcpToolConfig setAdditionalToolsets(List additionalToolsets) { + this.additionalToolsets = additionalToolsets; + return this; + } + + public List getAdditionalTools() { + return additionalTools; + } + + public GitHubMcpToolConfig setAdditionalTools(List additionalTools) { + this.additionalTools = additionalTools; + return this; + } + + public Boolean getEnableInsidersMode() { + return enableInsidersMode; + } + + public GitHubMcpToolConfig setEnableInsidersMode(Boolean enableInsidersMode) { + this.enableInsidersMode = enableInsidersMode; + return this; + } + + public Boolean getDisableFormDeferral() { + return disableFormDeferral; + } + + public GitHubMcpToolConfig setDisableFormDeferral(Boolean disableFormDeferral) { + this.disableFormDeferral = disableFormDeferral; + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index a3dd336cf5..59f75e8e46 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java @@ -99,6 +99,7 @@ public class ResumeSessionConfig { private ExitPlanModeHandler onExitPlanMode; private AutoModeSwitchHandler onAutoModeSwitch; private boolean enableMcpApps; + private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; private String remoteSession; private CopilotExpAssignmentResponse expAssignments; @@ -1634,6 +1635,27 @@ public ResumeSessionConfig setEnableMcpApps(boolean enableMcpApps) { return this; } + /** + * Gets the configuration for the built-in GitHub MCP server. + * + * @return the GitHub MCP configuration, or {@code null} + */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** + * Sets the configuration for the built-in GitHub MCP server. + * + * @param githubMcpToolConfig + * the GitHub MCP configuration + * @return this config instance for method chaining + */ + public ResumeSessionConfig setGitHubMcpToolConfig(GitHubMcpToolConfig githubMcpToolConfig) { + this.githubMcpToolConfig = githubMcpToolConfig; + return this; + } + /** * Gets the exit-plan-mode request handler. * @@ -1870,6 +1892,7 @@ public ResumeSessionConfig clone() { copy.onExitPlanMode = this.onExitPlanMode; copy.onAutoModeSwitch = this.onAutoModeSwitch; copy.enableMcpApps = this.enableMcpApps; + copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; copy.remoteSession = this.remoteSession; copy.expAssignments = this.expAssignments; diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index a81ed49dde..15e05e2261 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -198,6 +198,9 @@ public final class ResumeSessionRequest { @JsonProperty("requestMcpApps") private Boolean requestMcpApps; + @JsonProperty("githubMcpToolConfig") + private GitHubMcpToolConfig githubMcpToolConfig; + @JsonProperty("requestExitPlanMode") private Boolean requestExitPlanMode; @@ -927,6 +930,16 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } + /** Gets the built-in GitHub MCP tool configuration. @return the configuration */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** Sets the built-in GitHub MCP tool configuration. @param config the configuration */ + public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { + this.githubMcpToolConfig = config; + } + /** Gets the requestExitPlanMode flag. @return the flag */ public Boolean getRequestExitPlanMode() { return requestExitPlanMode; diff --git a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java index 0e02482de4..327491b5fe 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -99,6 +99,7 @@ public class SessionConfig { private ExitPlanModeHandler onExitPlanMode; private AutoModeSwitchHandler onAutoModeSwitch; private boolean enableMcpApps; + private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; private String remoteSession; private CloudSessionOptions cloud; @@ -1718,6 +1719,27 @@ public SessionConfig setEnableMcpApps(boolean enableMcpApps) { return this; } + /** + * Gets the configuration for the built-in GitHub MCP server. + * + * @return the GitHub MCP configuration, or {@code null} + */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** + * Sets the configuration for the built-in GitHub MCP server. + * + * @param githubMcpToolConfig + * the GitHub MCP configuration + * @return this config instance for method chaining + */ + public SessionConfig setGitHubMcpToolConfig(GitHubMcpToolConfig githubMcpToolConfig) { + this.githubMcpToolConfig = githubMcpToolConfig; + return this; + } + /** * Gets the exit-plan-mode request handler. * @@ -2007,6 +2029,7 @@ public SessionConfig clone() { copy.onExitPlanMode = this.onExitPlanMode; copy.onAutoModeSwitch = this.onAutoModeSwitch; copy.enableMcpApps = this.enableMcpApps; + copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; copy.remoteSession = this.remoteSession; copy.cloud = this.cloud; diff --git a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 652be026b1..c2f91bc07e 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -24,6 +24,7 @@ import com.github.copilot.rpc.ElicitationResultAction; import com.github.copilot.rpc.ExitPlanModeResult; import com.github.copilot.rpc.ExpConfigEntry; +import com.github.copilot.rpc.GitHubMcpToolConfig; import com.github.copilot.rpc.LargeToolOutputConfig; import com.github.copilot.rpc.MemoryConfiguration; import com.github.copilot.rpc.ResumeSessionConfig; @@ -958,4 +959,26 @@ void testClonePreservesAndForwardsExpAssignments() throws Exception { assertEquals(resumeAssignments, resumeRequest.getExpAssignments()); assertTrue(mapper.writeValueAsString(resumeRequest).contains("\"Id\":\"exp-resume\"")); } + + @Test + void githubMcpToolConfigIsMappedAndSerializedForCreateAndResume() throws Exception { + var config = new GitHubMcpToolConfig() + .setEnableAllTools(true) + .setAdditionalToolsets(List.of("repos")) + .setAdditionalTools(List.of("get_issue")) + .setEnableInsidersMode(true) + .setDisableFormDeferral(true); + var createRequest = SessionRequestBuilder.buildCreateRequest( + new SessionConfig().setGitHubMcpToolConfig(config), "session-1"); + var resumeRequest = SessionRequestBuilder.buildResumeRequest( + "session-1", new ResumeSessionConfig().setGitHubMcpToolConfig(config)); + + assertSame(config, createRequest.getGitHubMcpToolConfig()); + assertSame(config, resumeRequest.getGitHubMcpToolConfig()); + var mapper = JsonRpcClient.getObjectMapper(); + assertTrue(mapper.writeValueAsString(createRequest).contains("\"githubMcpToolConfig\"")); + assertTrue(mapper.writeValueAsString(resumeRequest).contains("\"githubMcpToolConfig\"")); + assertFalse(mapper.writeValueAsString(SessionRequestBuilder.buildCreateRequest(new SessionConfig())) + .contains("\"githubMcpToolConfig\"")); + } } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 61f4a99416..c09e50f4fc 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1557,6 +1557,9 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), + ...(config.githubMcpToolConfig !== undefined + ? { githubMcpToolConfig: config.githubMcpToolConfig } + : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, requestAutoModeSwitch: !!config.onAutoModeSwitchRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), @@ -1770,6 +1773,9 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), + ...(config.githubMcpToolConfig !== undefined + ? { githubMcpToolConfig: config.githubMcpToolConfig } + : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, requestAutoModeSwitch: !!config.onAutoModeSwitchRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 352afc6a94..07643a812b 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -85,6 +85,7 @@ export type { ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, + GitHubMcpToolConfig, GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 5f89ca3bed..208097606e 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1953,6 +1953,20 @@ export interface CopilotExpAssignmentResponse { AssignmentContext: string; } +/** + * Configuration for the built-in GitHub MCP server. + * + * `disableFormDeferral` only applies to the built-in GitHub MCP server and + * only has an effect when MCP Apps and form-backed GitHub tools are enabled. + */ +export interface GitHubMcpToolConfig { + enableAllTools?: boolean; + additionalToolsets?: string[]; + additionalTools?: string[]; + enableInsidersMode?: boolean; + disableFormDeferral?: boolean; +} + /** * Shared configuration fields used by both {@link SessionConfig} (for * creating a new session) and {@link ResumeSessionConfig} (for resuming @@ -2287,6 +2301,14 @@ export interface SessionConfigBase { */ enableMcpApps?: boolean; + /** + * Configuration for the built-in GitHub MCP server. + * + * `disableFormDeferral` only applies to the built-in GitHub MCP server and + * only has an effect when MCP Apps and form-backed GitHub tools are enabled. + */ + githubMcpToolConfig?: GitHubMcpToolConfig; + /** * Handler for exit-plan-mode requests from the agent. * When provided, enables `exitPlanMode.request` callbacks. diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 77149bc4b9..974e1c971b 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -102,6 +102,55 @@ describe("CopilotClient", () => { }); }); + it("forwards GitHub MCP tool config on create and resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + const githubMcpToolConfig = { + enableAllTools: true, + additionalToolsets: ["repos"], + additionalTools: ["get_issue"], + enableInsidersMode: true, + disableFormDeferral: true, + }; + + const session = await client.createSession({ githubMcpToolConfig }); + await client.resumeSession(session.sessionId, { githubMcpToolConfig }); + + expect(spy.mock.calls.find(([method]) => method === "session.create")![1]).toMatchObject({ + githubMcpToolConfig, + }); + expect(spy.mock.calls.find(([method]) => method === "session.resume")![1]).toMatchObject({ + githubMcpToolConfig, + }); + }); + + it("omits GitHub MCP tool config when unset", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({}); + + expect( + spy.mock.calls.find(([method]) => method === "session.create")![1] + ).not.toHaveProperty("githubMcpToolConfig"); + }); + it("passes MCP OAuth requests through when optional metadata is absent", async () => { let observedRequest: any; const session = new CopilotSession( diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 27c220ecf2..2f5b9b3309 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -113,6 +113,7 @@ ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, + GitHubMcpToolConfig, InfiniteSessionConfig, InputOptions, LargeToolOutputConfig, @@ -247,6 +248,7 @@ "GetAuthStatusResponse", "BearerTokenProvider", "GetStatusResponse", + "GitHubMcpToolConfig", "GitHubTelemetryClientInfo", "GitHubTelemetryEvent", "GitHubTelemetryNotification", diff --git a/python/copilot/client.py b/python/copilot/client.py index a7706dfe57..272dbd0c10 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -94,6 +94,7 @@ DefaultAgentConfig, ElicitationHandler, ExitPlanModeHandler, + GitHubMcpToolConfig, InfiniteSessionConfig, LargeToolOutputConfig, McpAuthHandler, @@ -334,6 +335,22 @@ def _tool_search_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: return wire +def _github_mcp_tool_config_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: + """Convert a ``GitHubMcpToolConfig`` mapping to wire format.""" + wire: dict[str, Any] = {} + if "enable_all_tools" in config: + wire["enableAllTools"] = config["enable_all_tools"] + if "additional_toolsets" in config: + wire["additionalToolsets"] = config["additional_toolsets"] + if "additional_tools" in config: + wire["additionalTools"] = config["additional_tools"] + if "enable_insiders_mode" in config: + wire["enableInsidersMode"] = config["enable_insiders_mode"] + if "disable_form_deferral" in config: + wire["disableFormDeferral"] = config["disable_form_deferral"] + return wire + + class TelemetryConfig(TypedDict, total=False): """Configuration for OpenTelemetry integration with the Copilot CLI.""" @@ -2067,6 +2084,7 @@ async def create_session( canvas_handler: CanvasHandler | None = None, exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, + github_mcp_tool_config: GitHubMcpToolConfig | None = None, ) -> CopilotSession: """ Create a new conversation session with the Copilot CLI. @@ -2312,6 +2330,10 @@ async def create_session( payload["requestElicitation"] = bool(on_elicitation_request) if enable_mcp_apps: payload["requestMcpApps"] = True + if github_mcp_tool_config is not None: + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire( + github_mcp_tool_config + ) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) @@ -2740,6 +2762,7 @@ async def resume_session( open_canvases: list[OpenCanvasInstance] | None = None, exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, + github_mcp_tool_config: GitHubMcpToolConfig | None = None, ) -> CopilotSession: """ Resume an existing conversation session by its ID. @@ -3014,6 +3037,10 @@ async def resume_session( payload["requestElicitation"] = bool(on_elicitation_request) if enable_mcp_apps: payload["requestMcpApps"] = True + if github_mcp_tool_config is not None: + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire( + github_mcp_tool_config + ) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) diff --git a/python/copilot/session.py b/python/copilot/session.py index d9fc04dcef..9c467e086b 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -1084,6 +1084,22 @@ class MCPHTTPServerConfig(TypedDict, total=False): MCPServerConfig = MCPStdioServerConfig | MCPHTTPServerConfig + +class GitHubMcpToolConfig(TypedDict, total=False): + """Configuration for the built-in GitHub MCP server. + + ``disable_form_deferral`` only applies to the built-in GitHub MCP server + and only has an effect when MCP Apps and form-backed GitHub tools are + enabled. + """ + + enable_all_tools: bool + additional_toolsets: list[str] + additional_tools: list[str] + enable_insiders_mode: bool + disable_form_deferral: bool + + # ============================================================================ # Custom Agent Configuration Types # ============================================================================ diff --git a/python/test_client.py b/python/test_client.py index a37d8dce2b..ba353bde38 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -504,6 +504,46 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_github_mcp_tool_config(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + config = { + "enable_all_tools": True, + "additional_toolsets": ["repos"], + "additional_tools": ["get_issue"], + "enable_insiders_mode": True, + "disable_form_deferral": True, + } + session = await client.create_session(github_mcp_tool_config=config) + await client.resume_session(session.session_id, github_mcp_tool_config=config) + + expected = { + "enableAllTools": True, + "additionalToolsets": ["repos"], + "additionalTools": ["get_issue"], + "enableInsidersMode": True, + "disableFormDeferral": True, + } + assert captured["session.create"]["githubMcpToolConfig"] == expected + assert captured["session.resume"]["githubMcpToolConfig"] == expected + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_and_resume_session_forward_reasoning_summary(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) diff --git a/rust/src/types.rs b/rust/src/types.rs index 163e1d29c0..064b96ec7c 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -824,6 +824,80 @@ impl ToolSearchConfig { } } +/// Configuration for the built-in GitHub MCP server. +/// +/// `disable_form_deferral` only applies to the built-in GitHub MCP server and +/// only has an effect when MCP Apps and form-backed GitHub tools are enabled. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct GitHubMcpToolConfig { + /// Whether all GitHub MCP tools are enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_all_tools: Option, + /// Additional GitHub MCP toolsets to enable. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub additional_toolsets: Option>, + /// Additional GitHub MCP tools to enable. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub additional_tools: Option>, + /// Whether GitHub MCP insiders mode is enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_insiders_mode: Option, + /// Disables form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_form_deferral: Option, +} + +impl GitHubMcpToolConfig { + /// Construct an empty GitHub MCP tool configuration. + pub fn new() -> Self { + Self::default() + } + + /// Set whether all GitHub MCP tools are enabled. + pub fn with_enable_all_tools(mut self, value: bool) -> Self { + self.enable_all_tools = Some(value); + self + } + + /// Set the additional GitHub MCP toolsets to enable. + pub fn with_additional_toolsets(mut self, values: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.additional_toolsets = Some(values.into_iter().map(Into::into).collect()); + self + } + + /// Set the additional GitHub MCP tools to enable. + pub fn with_additional_tools(mut self, values: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.additional_tools = Some(values.into_iter().map(Into::into).collect()); + self + } + + /// Set whether GitHub MCP insiders mode is enabled. + pub fn with_enable_insiders_mode(mut self, value: bool) -> Self { + self.enable_insiders_mode = Some(value); + self + } + + /// Disable form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + pub fn with_disable_form_deferral(mut self, value: bool) -> Self { + self.disable_form_deferral = Some(value); + self + } +} + /// Configures infinite sessions: persistent workspaces with automatic /// context-window compaction. /// @@ -1831,6 +1905,11 @@ pub struct SessionConfig { /// /// Defaults to `None` (treated as `false`). pub enable_mcp_apps: Option, + /// Configuration for the built-in GitHub MCP server. + /// + /// `disable_form_deferral` only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + pub github_mcp_tool_config: Option, /// Skill directory paths passed through to the GitHub Copilot CLI. pub skill_directories: Option>, /// Additional directories to search for custom instruction files. @@ -2164,6 +2243,7 @@ impl Default for SessionConfig { enable_skills: None, embedding_cache_storage: None, enable_mcp_apps: None, + github_mcp_tool_config: None, skill_directories: None, instruction_directories: None, plugin_directories: None, @@ -2322,6 +2402,7 @@ impl SessionConfig { request_auto_mode_switch, request_elicitation, request_mcp_apps: self.enable_mcp_apps.unwrap_or(false), + github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, instruction_directories: self.instruction_directories, @@ -2701,6 +2782,12 @@ impl SessionConfig { self } + /// Set the built-in GitHub MCP server configuration. + pub fn with_github_mcp_tool_config(mut self, config: GitHubMcpToolConfig) -> Self { + self.github_mcp_tool_config = Some(config); + self + } + /// Set skill directory paths passed through to the CLI. pub fn with_skill_directories(mut self, paths: I) -> Self where @@ -3034,6 +3121,11 @@ pub struct ResumeSessionConfig { /// Enable MCP Apps (SEP-1865) UI passthrough on resume. See /// [`SessionConfig::enable_mcp_apps`]. Defaults to `None` (treated as `false`). pub enable_mcp_apps: Option, + /// Configuration for the built-in GitHub MCP server. + /// + /// `disable_form_deferral` only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + pub github_mcp_tool_config: Option, /// Skill directory paths passed through to the GitHub Copilot CLI on resume. pub skill_directories: Option>, /// Additional directories to search for custom instruction files on @@ -3376,6 +3468,7 @@ impl ResumeSessionConfig { request_auto_mode_switch, request_elicitation, request_mcp_apps: self.enable_mcp_apps.unwrap_or(false), + github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, instruction_directories: self.instruction_directories, @@ -3467,6 +3560,7 @@ impl ResumeSessionConfig { enable_skills: None, embedding_cache_storage: None, enable_mcp_apps: None, + github_mcp_tool_config: None, skill_directories: None, instruction_directories: None, plugin_directories: None, @@ -3825,6 +3919,12 @@ impl ResumeSessionConfig { self } + /// Set the built-in GitHub MCP server configuration. + pub fn with_github_mcp_tool_config(mut self, config: GitHubMcpToolConfig) -> Self { + self.github_mcp_tool_config = Some(config); + self + } + /// Set skill directory paths passed through to the CLI on resume. pub fn with_skill_directories(mut self, paths: I) -> Self where @@ -5490,12 +5590,12 @@ mod tests { AgentMode, Attachment, AttachmentLineRange, AttachmentSelectionPosition, AttachmentSelectionRange, AzureProviderOptions, CapiSessionOptions, ConnectionState, CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, ExpConfigEntry, - ExpFlagValue, ExtensionInfo, GitHubReferenceType, InfiniteSessionConfig, - LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, MemoryConfiguration, - NamedProviderConfig, ProviderConfig, ProviderModelConfig, ReasoningSummary, - ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, SystemMessageConfig, Tool, - ToolBinaryResult, ToolResult, ToolResultExpanded, ToolResultResponse, - ensure_attachment_display_names, + ExpFlagValue, ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, + InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, + MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, + ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, + SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, + ToolResultResponse, ensure_attachment_display_names, }; use crate::generated::session_events::TypedSessionEvent; @@ -5800,6 +5900,47 @@ mod tests { assert_eq!(json["requestMcpApps"], serde_json::Value::Bool(true)); } + #[test] + fn github_mcp_tool_config_serializes_for_create_and_resume() { + let github_config = GitHubMcpToolConfig::new() + .with_enable_all_tools(true) + .with_additional_toolsets(["repos"]) + .with_additional_tools(["get_issue"]) + .with_enable_insiders_mode(true) + .with_disable_form_deferral(true); + + let (create_wire, _) = SessionConfig::default() + .with_github_mcp_tool_config(github_config.clone()) + .into_wire(Some(SessionId::from("github-mcp"))) + .expect("create config has no duplicate handlers"); + assert_eq!( + serde_json::to_value(&create_wire).unwrap()["githubMcpToolConfig"], + serde_json::json!({ + "enableAllTools": true, + "additionalToolsets": ["repos"], + "additionalTools": ["get_issue"], + "enableInsidersMode": true, + "disableFormDeferral": true, + }) + ); + + let (resume_wire, _) = ResumeSessionConfig::new(SessionId::from("github-mcp")) + .with_github_mcp_tool_config(github_config) + .into_wire() + .expect("resume config has no duplicate handlers"); + assert!(resume_wire.github_mcp_tool_config.is_some()); + + let (unset_wire, _) = SessionConfig::default() + .into_wire(Some(SessionId::from("github-mcp-unset"))) + .expect("default config has no duplicate handlers"); + assert!( + serde_json::to_value(&unset_wire) + .unwrap() + .get("githubMcpToolConfig") + .is_none() + ); + } + #[test] fn memory_configuration_constructors_and_serde() { assert!(MemoryConfiguration::enabled().enabled); diff --git a/rust/src/wire.rs b/rust/src/wire.rs index 2eabbe848d..036dbb11a8 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -25,9 +25,10 @@ use crate::generated::api_types::{ use crate::generated::session_events::ReasoningSummary; use crate::types::{ CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, CustomAgentConfig, - DefaultAgentConfig, ExtensionInfo, InfiniteSessionConfig, LargeToolOutputConfig, - McpServerConfig, MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, - SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, ToolSearchConfig, + DefaultAgentConfig, ExtensionInfo, GitHubMcpToolConfig, InfiniteSessionConfig, + LargeToolOutputConfig, McpServerConfig, MemoryConfiguration, NamedProviderConfig, + ProviderConfig, ProviderModelConfig, SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, + ToolSearchConfig, }; /// Wire representation of a slash command (name + description only). The @@ -113,6 +114,8 @@ pub(crate) struct SessionCreateWire { pub request_auto_mode_switch: bool, pub request_elicitation: bool, pub request_mcp_apps: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_config: Option, pub hooks: bool, #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, @@ -249,6 +252,8 @@ pub(crate) struct SessionResumeWire { pub request_auto_mode_switch: bool, pub request_elicitation: bool, pub request_mcp_apps: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_config: Option, pub hooks: bool, #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, From 975d6e89f9424cdaacc311c4eb527a51470c7fa9 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Jul 2026 11:18:28 -0700 Subject: [PATCH 2/4] python: apply ruff formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/copilot/client.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/python/copilot/client.py b/python/copilot/client.py index 272dbd0c10..b15fcbfe92 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2331,9 +2331,7 @@ async def create_session( if enable_mcp_apps: payload["requestMcpApps"] = True if github_mcp_tool_config is not None: - payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire( - github_mcp_tool_config - ) + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire(github_mcp_tool_config) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) @@ -3038,9 +3036,7 @@ async def resume_session( if enable_mcp_apps: payload["requestMcpApps"] = True if github_mcp_tool_config is not None: - payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire( - github_mcp_tool_config - ) + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire(github_mcp_tool_config) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) From 9307a1a9d0c7cbd9c0b30f85485125041aba2f00 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Jul 2026 11:24:41 -0700 Subject: [PATCH 3/4] java: apply spotless formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../github/copilot/rpc/CreateSessionRequest.java | 4 ++-- .../github/copilot/rpc/GitHubMcpToolConfig.java | 6 ++++-- .../github/copilot/rpc/ResumeSessionRequest.java | 4 ++-- .../copilot/SessionRequestBuilderTest.java | 16 ++++++---------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index ccf538baef..651ca8d642 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -905,12 +905,12 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } - /** Gets the built-in GitHub MCP tool configuration. @return the configuration */ + /** Gets the GitHub MCP tool configuration. @return the configuration */ public GitHubMcpToolConfig getGitHubMcpToolConfig() { return githubMcpToolConfig; } - /** Sets the built-in GitHub MCP tool configuration. @param config the configuration */ + /** Sets the GitHub MCP tool configuration. @param config the value */ public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { this.githubMcpToolConfig = config; } diff --git a/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java b/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java index d5bc0157da..75a8e30163 100644 --- a/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java @@ -12,8 +12,10 @@ /** * Configuration for the built-in GitHub MCP server. * - *

{@code disableFormDeferral} only applies to the built-in GitHub MCP server - * and only has an effect when MCP Apps and form-backed GitHub tools are enabled. + *

+ * {@code disableFormDeferral} only applies to the built-in GitHub MCP server + * and only has an effect when MCP Apps and form-backed GitHub tools are + * enabled. */ @JsonInclude(JsonInclude.Include.NON_NULL) public class GitHubMcpToolConfig { diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index 15e05e2261..9e6ba77496 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -930,12 +930,12 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } - /** Gets the built-in GitHub MCP tool configuration. @return the configuration */ + /** Gets the GitHub MCP tool configuration. @return the configuration */ public GitHubMcpToolConfig getGitHubMcpToolConfig() { return githubMcpToolConfig; } - /** Sets the built-in GitHub MCP tool configuration. @param config the configuration */ + /** Sets the GitHub MCP tool configuration. @param config the value */ public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { this.githubMcpToolConfig = config; } diff --git a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index c2f91bc07e..e300858b5b 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -962,16 +962,12 @@ void testClonePreservesAndForwardsExpAssignments() throws Exception { @Test void githubMcpToolConfigIsMappedAndSerializedForCreateAndResume() throws Exception { - var config = new GitHubMcpToolConfig() - .setEnableAllTools(true) - .setAdditionalToolsets(List.of("repos")) - .setAdditionalTools(List.of("get_issue")) - .setEnableInsidersMode(true) - .setDisableFormDeferral(true); - var createRequest = SessionRequestBuilder.buildCreateRequest( - new SessionConfig().setGitHubMcpToolConfig(config), "session-1"); - var resumeRequest = SessionRequestBuilder.buildResumeRequest( - "session-1", new ResumeSessionConfig().setGitHubMcpToolConfig(config)); + var config = new GitHubMcpToolConfig().setEnableAllTools(true).setAdditionalToolsets(List.of("repos")) + .setAdditionalTools(List.of("get_issue")).setEnableInsidersMode(true).setDisableFormDeferral(true); + var createRequest = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setGitHubMcpToolConfig(config), + "session-1"); + var resumeRequest = SessionRequestBuilder.buildResumeRequest("session-1", + new ResumeSessionConfig().setGitHubMcpToolConfig(config)); assertSame(config, createRequest.getGitHubMcpToolConfig()); assertSame(config, resumeRequest.getGitHubMcpToolConfig()); From 33138005447c37fb4bd36a390c57c2d187d0cf21 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Jul 2026 11:41:23 -0700 Subject: [PATCH 4/4] Address review feedback - node: omit githubMcpToolConfig for null as well as undefined - python: document github_mcp_tool_config on create_session/resume_session - go: assert json.Unmarshal succeeds in the omitted-when-unset subtest - java: call the non-deprecated buildCreateRequest(config, sessionId) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/client_test.go | 4 +++- .../copilot/SessionRequestBuilderTest.java | 5 +++-- nodejs/src/client.ts | 4 ++-- python/copilot/client.py | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/go/client_test.go b/go/client_test.go index 8f0bd080a9..2301c990d3 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -2416,7 +2416,9 @@ func TestSessionRequests_GitHubMCPToolConfig(t *testing.T) { t.Fatalf("Failed to marshal: %v", err) } var payload map[string]any - _ = json.Unmarshal(data, &payload) + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } if _, ok := payload["githubMcpToolConfig"]; ok { t.Fatal("Expected githubMcpToolConfig to be omitted") } diff --git a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index e300858b5b..4efdafd0e4 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -974,7 +974,8 @@ void githubMcpToolConfigIsMappedAndSerializedForCreateAndResume() throws Excepti var mapper = JsonRpcClient.getObjectMapper(); assertTrue(mapper.writeValueAsString(createRequest).contains("\"githubMcpToolConfig\"")); assertTrue(mapper.writeValueAsString(resumeRequest).contains("\"githubMcpToolConfig\"")); - assertFalse(mapper.writeValueAsString(SessionRequestBuilder.buildCreateRequest(new SessionConfig())) - .contains("\"githubMcpToolConfig\"")); + assertFalse( + mapper.writeValueAsString(SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "session-2")) + .contains("\"githubMcpToolConfig\"")); } } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c09e50f4fc..a09a55a2c4 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1557,7 +1557,7 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), - ...(config.githubMcpToolConfig !== undefined + ...(config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, @@ -1773,7 +1773,7 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), - ...(config.githubMcpToolConfig !== undefined + ...(config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, diff --git a/python/copilot/client.py b/python/copilot/client.py index b15fcbfe92..907ce61cdd 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2205,6 +2205,16 @@ async def create_session( override) is on; otherwise the request is silently dropped. Inspect ``capabilities.ui.mcpApps`` on the create response to detect the drop. + github_mcp_tool_config: Configuration for the built-in GitHub MCP + server, sent as ``githubMcpToolConfig`` on ``session.create``. + Supports ``enable_all_tools``, ``additional_toolsets``, + ``additional_tools``, ``enable_insiders_mode``, and + ``disable_form_deferral``. Setting ``disable_form_deferral`` + makes form-backed GitHub write tools execute directly instead + of returning an awaiting-form stub; it does not enable MCP Apps + on its own and has no effect unless MCP Apps are enabled for + the session (see ``enable_mcp_apps``). Omitted from the wire + payload entirely when None. exp_assignments: ExP assignment ("flight") data injected by a trusted integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service @@ -2878,6 +2888,16 @@ async def resume_session( override) is on; otherwise the request is silently dropped. Inspect ``capabilities.ui.mcpApps`` on the resume response to detect the drop. + github_mcp_tool_config: Configuration for the built-in GitHub MCP + server, sent as ``githubMcpToolConfig`` on ``session.resume``. + Supports ``enable_all_tools``, ``additional_toolsets``, + ``additional_tools``, ``enable_insiders_mode``, and + ``disable_form_deferral``. Setting ``disable_form_deferral`` + makes form-backed GitHub write tools execute directly instead + of returning an awaiting-form stub; it does not enable MCP Apps + on its own and has no effect unless MCP Apps are enabled for + the session (see ``enable_mcp_apps``). Omitted from the wire + payload entirely when None. continue_pending_work: When True, instructs the runtime to continue any tool calls or permission prompts that were still pending when the session was last suspended. When False (the default), the runtime