Skip to content
Merged
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
29 changes: 29 additions & 0 deletions docs/features/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,35 @@ await using var session = await client.CreateSessionAsync(new SessionConfig
});
```

## Disabling configured servers per session

Set `disabledMcpServers` to exact MCP server names that must not run in a session.
The setting is scoped to the individual create or resume request; it does not
modify global MCP settings or the server configuration.

```typescript
const session = await client.createSession({
mcpServers: {
filesystem: { type: "local", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."] },
github: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
},
disabledMcpServers: ["github"],
});
```

| SDK | Configuration property |
| --- | --- |
| Node.js | `disabledMcpServers` |
| Python | `disabled_mcp_servers` |
| Go | `DisabledMCPServers` |
| .NET | `DisabledMcpServers` |
| Java | `setDisabledMcpServers(...)` |
| Rust | `with_disabled_mcp_servers(...)` |

On session creation and a **cold** resume, disabled servers are not started and
the runtime does not initiate their authentication. A resident resume cannot
undo a server that the runtime has already spawned. Names are matched exactly.

## Tool configuration

You can control which tools are available to an MCP server using the `tools` field.
Expand Down
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
Cloud: config.Cloud,
InstructionDirectories: config.InstructionDirectories,
PluginDirectories: config.PluginDirectories,
DisabledMcpServers: config.DisabledMcpServers,
LargeOutput: config.LargeOutput,
ToolSearch: config.ToolSearch,
Memory: config.Memory,
Expand Down Expand Up @@ -1401,6 +1402,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
ContinuePendingWork: config.ContinuePendingWork,
InstructionDirectories: config.InstructionDirectories,
PluginDirectories: config.PluginDirectories,
DisabledMcpServers: config.DisabledMcpServers,
LargeOutput: config.LargeOutput,
ToolSearch: config.ToolSearch,
Memory: config.Memory,
Expand Down Expand Up @@ -2755,6 +2757,7 @@ internal record CreateSessionRequest(
CloudSessionOptions? Cloud = null,
IList<string>? InstructionDirectories = null,
IList<string>? PluginDirectories = null,
[property: JsonPropertyName("disabledMcpServers")] IList<string>? DisabledMcpServers = null,
LargeToolOutputConfig? LargeOutput = null,
ToolSearchConfig? ToolSearch = null,
MemoryConfiguration? Memory = null,
Expand Down Expand Up @@ -2862,6 +2865,7 @@ internal record ResumeSessionRequest(
bool? ContinuePendingWork = null,
IList<string>? InstructionDirectories = null,
IList<string>? PluginDirectories = null,
[property: JsonPropertyName("disabledMcpServers")] IList<string>? DisabledMcpServers = null,
LargeToolOutputConfig? LargeOutput = null,
ToolSearchConfig? ToolSearch = null,
MemoryConfiguration? Memory = null,
Expand Down
8 changes: 8 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3016,6 +3016,7 @@ protected SessionConfigBase(SessionConfigBase? other)
DefaultAgent = other.DefaultAgent;
Agent = other.Agent;
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
DisabledMcpServers = other.DisabledMcpServers is not null ? [.. other.DisabledMcpServers] : null;
EnableCitations = other.EnableCitations;
EnableConfigDiscovery = other.EnableConfigDiscovery;
SkipEmbeddingRetrieval = other.SkipEmbeddingRetrieval;
Expand Down Expand Up @@ -3429,6 +3430,13 @@ protected SessionConfigBase(SessionConfigBase? other)
/// <summary>List of skill names to disable.</summary>
public IList<string>? DisabledSkills { get; set; }

/// <summary>
/// Exact MCP server names to disable for this session. Disabled servers are not
/// started or authenticated on create or cold resume; a resident resume cannot
/// stop servers that are already running.
/// </summary>
public IList<string>? DisabledMcpServers { get; set; }

/// <summary>
/// Infinite session configuration for persistent workspaces and automatic compaction.
/// When enabled (default), sessions automatically manage context limits and persist state.
Expand Down
9 changes: 9 additions & 0 deletions dotnet/test/Unit/CloneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
SkillDirectories = ["/skills"],
InstructionDirectories = ["/instructions"],
DisabledSkills = ["skill1"],
DisabledMcpServers = ["server1"],
PluginDirectories = ["/plugins"],
LargeOutput = new LargeToolOutputConfig { Enabled = true, MaxSizeBytes = 2048, OutputDirectory = "/tmp/out" },
Memory = new MemoryConfiguration { Enabled = true },
Expand Down Expand Up @@ -136,6 +137,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
Assert.Equal(original.SkillDirectories, clone.SkillDirectories);
Assert.Equal(original.InstructionDirectories, clone.InstructionDirectories);
Assert.Equal(original.DisabledSkills, clone.DisabledSkills);
Assert.Equal(original.DisabledMcpServers, clone.DisabledMcpServers);
Assert.Equal(original.PluginDirectories, clone.PluginDirectories);
Assert.Same(original.LargeOutput, clone.LargeOutput);
Assert.Same(original.Memory, clone.Memory);
Expand All @@ -157,6 +159,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
SkillDirectories = ["/skills"],
InstructionDirectories = ["/instructions"],
DisabledSkills = ["skill1"],
DisabledMcpServers = ["server1"],
};

var clone = original.Clone();
Expand All @@ -170,6 +173,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
clone.SkillDirectories!.Add("/more");
clone.InstructionDirectories!.Add("/more-instructions");
clone.DisabledSkills!.Add("skill99");
clone.DisabledMcpServers!.Add("server99");

// Original is unaffected
Assert.Single(original.AvailableTools!);
Expand All @@ -180,6 +184,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
Assert.Single(original.SkillDirectories!);
Assert.Single(original.InstructionDirectories!);
Assert.Single(original.DisabledSkills!);
Assert.Single(original.DisabledMcpServers!);
}

[Fact]
Expand All @@ -206,6 +211,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
SkillDirectories = ["/skills"],
InstructionDirectories = ["/instructions"],
DisabledSkills = ["skill1"],
DisabledMcpServers = ["server1"],
};

var clone = original.Clone();
Expand All @@ -219,6 +225,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
clone.SkillDirectories!.Add("/more");
clone.InstructionDirectories!.Add("/more-instructions");
clone.DisabledSkills!.Add("skill99");
clone.DisabledMcpServers!.Add("server99");

// Original is unaffected
Assert.Single(original.AvailableTools!);
Expand All @@ -229,6 +236,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
Assert.Single(original.SkillDirectories!);
Assert.Single(original.InstructionDirectories!);
Assert.Single(original.DisabledSkills!);
Assert.Single(original.DisabledMcpServers!);
}

[Fact]
Expand Down Expand Up @@ -289,6 +297,7 @@ public void Clone_WithNullCollections_ReturnsNullCollections()
Assert.Null(clone.SkillDirectories);
Assert.Null(clone.InstructionDirectories);
Assert.Null(clone.DisabledSkills);
Assert.Null(clone.DisabledMcpServers);
Assert.Null(clone.Tools);
Assert.Null(clone.DefaultAgent);
Assert.True(clone.IncludeSubAgentStreamingEvents);
Expand Down
4 changes: 4 additions & 0 deletions dotnet/test/Unit/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,14 @@ public void SessionRequests_CanSerializePluginDirectoriesAndLargeOutput_WithSdkO
createRequestType,
("SessionId", "session-id"),
("PluginDirectories", pluginDirs),
("DisabledMcpServers", new List<string> { "local-files", "remote-github" }),
("LargeOutput", largeOutput));

var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
using var createDocument = JsonDocument.Parse(createJson);
var createRoot = createDocument.RootElement;
Assert.Equal("/tmp/plugins/a", createRoot.GetProperty("pluginDirectories")[0].GetString());
Assert.Equal("local-files", createRoot.GetProperty("disabledMcpServers")[0].GetString());
Assert.Equal("/tmp/plugins/b", createRoot.GetProperty("pluginDirectories")[1].GetString());
var createLargeOutput = createRoot.GetProperty("largeOutput");
Assert.True(createLargeOutput.GetProperty("enabled").GetBoolean());
Expand All @@ -428,12 +430,14 @@ public void SessionRequests_CanSerializePluginDirectoriesAndLargeOutput_WithSdkO
resumeRequestType,
("SessionId", "session-id"),
("PluginDirectories", pluginDirs),
("DisabledMcpServers", new List<string> { "local-files", "remote-github" }),
("LargeOutput", largeOutput));

var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
using var resumeDocument = JsonDocument.Parse(resumeJson);
var resumeRoot = resumeDocument.RootElement;
Assert.Equal("/tmp/plugins/a", resumeRoot.GetProperty("pluginDirectories")[0].GetString());
Assert.Equal("local-files", resumeRoot.GetProperty("disabledMcpServers")[0].GetString());
var resumeLargeOutput = resumeRoot.GetProperty("largeOutput");
Assert.True(resumeLargeOutput.GetProperty("enabled").GetBoolean());
Assert.Equal(1024, resumeLargeOutput.GetProperty("maxSizeBytes").GetInt64());
Expand Down
6 changes: 6 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.PluginDirectories = config.PluginDirectories
req.InstructionDirectories = config.InstructionDirectories
req.DisabledSkills = config.DisabledSkills
if config.DisabledMCPServers != nil {
req.DisabledMCPServers = &config.DisabledMCPServers
}
req.InfiniteSessions = config.InfiniteSessions
req.LargeOutput = config.LargeOutput
req.ToolSearch = config.ToolSearch
Expand Down Expand Up @@ -1187,6 +1190,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.PluginDirectories = config.PluginDirectories
req.InstructionDirectories = config.InstructionDirectories
req.DisabledSkills = config.DisabledSkills
if config.DisabledMCPServers != nil {
req.DisabledMCPServers = &config.DisabledMCPServers
}
req.InfiniteSessions = config.InfiniteSessions
req.LargeOutput = config.LargeOutput
req.ToolSearch = config.ToolSearch
Expand Down
52 changes: 50 additions & 2 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,9 +1049,11 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) {
"outputDir": "/tmp/large-output",
}
expectedPluginDirs := []any{"/tmp/plugins/a", "/tmp/plugins/b"}
expectedDisabledMCPServers := []any{"local-files", "remote-github"}
disabledMCPServers := []string{"local-files", "remote-github"}

t.Run("create includes pluginDirectories and largeOutput in JSON when set", func(t *testing.T) {
req := createSessionRequest{PluginDirectories: pluginDirs, LargeOutput: largeOutput}
req := createSessionRequest{PluginDirectories: pluginDirs, DisabledMCPServers: &disabledMCPServers, LargeOutput: largeOutput}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
Expand All @@ -1063,13 +1065,16 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) {
if !reflect.DeepEqual(m["pluginDirectories"], expectedPluginDirs) {
t.Errorf("Expected pluginDirectories %v, got %v", expectedPluginDirs, m["pluginDirectories"])
}
if !reflect.DeepEqual(m["disabledMcpServers"], expectedDisabledMCPServers) {
t.Errorf("Expected disabledMcpServers %v, got %v", expectedDisabledMCPServers, m["disabledMcpServers"])
}
if !reflect.DeepEqual(m["largeOutput"], expectedLargeOutput) {
t.Errorf("Expected largeOutput %v, got %v", expectedLargeOutput, m["largeOutput"])
}
})

t.Run("resume includes pluginDirectories and largeOutput in JSON when set", func(t *testing.T) {
req := resumeSessionRequest{SessionID: "s1", PluginDirectories: pluginDirs, LargeOutput: largeOutput}
req := resumeSessionRequest{SessionID: "s1", PluginDirectories: pluginDirs, DisabledMCPServers: &disabledMCPServers, LargeOutput: largeOutput}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
Expand All @@ -1081,11 +1086,36 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) {
if !reflect.DeepEqual(m["pluginDirectories"], expectedPluginDirs) {
t.Errorf("Expected pluginDirectories %v, got %v", expectedPluginDirs, m["pluginDirectories"])
}
if !reflect.DeepEqual(m["disabledMcpServers"], expectedDisabledMCPServers) {
t.Errorf("Expected disabledMcpServers %v, got %v", expectedDisabledMCPServers, m["disabledMcpServers"])
}
if !reflect.DeepEqual(m["largeOutput"], expectedLargeOutput) {
t.Errorf("Expected largeOutput %v, got %v", expectedLargeOutput, m["largeOutput"])
}
})

t.Run("create and resume include explicit empty disabledMcpServers", func(t *testing.T) {
emptyDisabledMCPServers := []string{}
requests := []any{
createSessionRequest{DisabledMCPServers: &emptyDisabledMCPServers},
resumeSessionRequest{SessionID: "s1", DisabledMCPServers: &emptyDisabledMCPServers},
}

for _, request := range requests {
data, err := json.Marshal(request)
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 value, ok := m["disabledMcpServers"]; !ok || !reflect.DeepEqual(value, []any{}) {
t.Errorf("Expected explicit empty disabledMcpServers, got %v", value)
}
}
})

t.Run("create omits pluginDirectories and largeOutput when nil", func(t *testing.T) {
req := createSessionRequest{}
data, err := json.Marshal(req)
Expand All @@ -1099,10 +1129,28 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) {
if _, ok := m["pluginDirectories"]; ok {
t.Errorf("Expected pluginDirectories to be omitted")
}
if _, ok := m["disabledMcpServers"]; ok {
t.Error("Expected disabledMcpServers to be omitted")
}
if _, ok := m["largeOutput"]; ok {
t.Errorf("Expected largeOutput to be omitted")
}
})

t.Run("resume omits disabledMcpServers when nil", func(t *testing.T) {
req := resumeSessionRequest{SessionID: "s1"}
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 _, ok := m["disabledMcpServers"]; ok {
t.Error("Expected disabledMcpServers to be omitted")
}
})
}

func TestSessionRequests_Memory(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,10 @@ type SessionConfig struct {
InstructionDirectories []string
// DisabledSkills is a list of skill names to disable
DisabledSkills []string
// DisabledMCPServers is a list of exact MCP server names to disable for this session.
// Disabled servers are not started or authenticated on create or cold resume.
// A resident resume cannot stop servers that are already running.
DisabledMCPServers []string
// InfiniteSessions configures infinite sessions for persistent workspaces and automatic compaction.
// When enabled (default), sessions automatically manage context limits and persist state.
InfiniteSessions *InfiniteSessionConfig
Expand Down Expand Up @@ -1800,6 +1804,10 @@ type ResumeSessionConfig struct {
InstructionDirectories []string
// DisabledSkills is a list of skill names to disable
DisabledSkills []string
// DisabledMCPServers is a list of exact MCP server names to disable for this session.
// Disabled servers are not started or authenticated on create or cold resume.
// A resident resume cannot stop servers that are already running.
DisabledMCPServers []string
// InfiniteSessions configures infinite sessions for persistent workspaces and automatic compaction.
InfiniteSessions *InfiniteSessionConfig
// LargeOutput configures handling of large tool outputs. When a tool produces
Expand Down Expand Up @@ -2333,6 +2341,7 @@ type createSessionRequest struct {
PluginDirectories []string `json:"pluginDirectories,omitempty"`
InstructionDirectories []string `json:"instructionDirectories,omitempty"`
DisabledSkills []string `json:"disabledSkills,omitempty"`
DisabledMCPServers *[]string `json:"disabledMcpServers,omitempty"`
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"`
ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"`
Expand Down Expand Up @@ -2426,6 +2435,7 @@ type resumeSessionRequest struct {
PluginDirectories []string `json:"pluginDirectories,omitempty"`
InstructionDirectories []string `json:"instructionDirectories,omitempty"`
DisabledSkills []string `json:"disabledSkills,omitempty"`
DisabledMCPServers *[]string `json:"disabledMcpServers,omitempty"`
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"`
ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
request.setToolSearch(config.getToolSearch());
request.setMemory(config.getMemory());
request.setDisabledSkills(config.getDisabledSkills());
request.setDisabledMcpServers(config.getDisabledMcpServers());
request.setConfigDirectory(config.getConfigDirectory());
config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery);
config.getSkipEmbeddingRetrieval().ifPresent(request::setSkipEmbeddingRetrieval);
Expand Down Expand Up @@ -302,6 +303,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
request.setToolSearch(config.getToolSearch());
request.setMemory(config.getMemory());
request.setDisabledSkills(config.getDisabledSkills());
request.setDisabledMcpServers(config.getDisabledMcpServers());
request.setInfiniteSessions(config.getInfiniteSessions());
request.setModelCapabilities(config.getModelCapabilities());

Expand Down
Loading
Loading