Skip to content

Commit 3226676

Browse files
stephentoubCopilot
andauthored
Refactor MCP server config types across all SDK languages (#1051)
* Refactor MCP server config types across all SDK languages Introduce an abstract McpServerConfig base class in C# with a private protected constructor and sealed derived types McpStdioServerConfig and McpHttpServerConfig. Shared properties (Tools, Type, Timeout) are deduplicated into the base class. The Type property uses the JsonPolymorphic discriminator pattern consistent with SessionEvent. All Dictionary<string, object> McpServers properties are now strongly typed as Dictionary<string, McpServerConfig>. Rename Local/Remote to Stdio/Http across all four SDK languages to match MCP protocol terminology: - C#: McpStdioServerConfig / McpHttpServerConfig - TypeScript: MCPStdioServerConfig / MCPHttpServerConfig - Go: MCPStdioServerConfig / MCPHTTPServerConfig - Python: MCPStdioServerConfig / MCPHttpServerConfig Update documentation examples accordingly. Fixes #245 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use idiomatic HTTP casing in Python and TypeScript type names Rename MCPHttpServerConfig to MCPHTTPServerConfig in Python (matching stdlib convention: HTTPServer, HTTPError) and TypeScript (matching the all-caps treatment of MCP already in use). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update mcp-servers C# scenario to use strongly-typed McpServerConfig API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make Go MCPServerConfig type-safe with interface + marker method Change MCPServerConfig from map[string]any to an interface with a private marker method, matching the type-safety approach used for C#. MCPStdioServerConfig and MCPHTTPServerConfig implement the interface and use MarshalJSON to auto-inject the type discriminator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix consistency gaps: update JSDoc and drop stale type field in Python tests - Update MCPServerConfigBase JSDoc to reference stdio/http instead of local/remote - Remove explicit type: local from Python E2E tests (omitted type defaults to stdio, matching Go/C# pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d2130ff commit 3226676

File tree

15 files changed

+166
-163
lines changed

15 files changed

+166
-163
lines changed

docs/features/mcp.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,13 @@ func main() {
113113
}
114114
defer client.Stop()
115115

116-
// MCPServerConfig is map[string]any for flexibility
117116
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
118117
Model: "gpt-5",
119118
MCPServers: map[string]copilot.MCPServerConfig{
120-
"my-local-server": {
121-
"type": "local",
122-
"command": "node",
123-
"args": []string{"./mcp-server.js"},
124-
"tools": []string{"*"},
119+
"my-local-server": copilot.MCPStdioServerConfig{
120+
Command: "node",
121+
Args: []string{"./mcp-server.js"},
122+
Tools: []string{"*"},
125123
},
126124
},
127125
})
@@ -143,11 +141,10 @@ await using var client = new CopilotClient();
143141
await using var session = await client.CreateSessionAsync(new SessionConfig
144142
{
145143
Model = "gpt-5",
146-
McpServers = new Dictionary<string, object>
144+
McpServers = new Dictionary<string, McpServerConfig>
147145
{
148-
["my-local-server"] = new McpLocalServerConfig
146+
["my-local-server"] = new McpStdioServerConfig
149147
{
150-
Type = "local",
151148
Command = "node",
152149
Args = new List<string> { "./mcp-server.js" },
153150
Tools = new List<string> { "*" },

docs/troubleshooting/mcp-debugging.md

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -250,19 +250,17 @@ public static class McpDotnetConfigExample
250250
{
251251
public static void Main()
252252
{
253-
var servers = new Dictionary<string, McpLocalServerConfig>
253+
var servers = new Dictionary<string, McpServerConfig>
254254
{
255-
["my-dotnet-server"] = new McpLocalServerConfig
255+
["my-dotnet-server"] = new McpStdioServerConfig
256256
{
257-
Type = "local",
258257
Command = @"C:\Tools\MyServer\MyServer.exe",
259258
Args = new List<string>(),
260259
Cwd = @"C:\Tools\MyServer",
261260
Tools = new List<string> { "*" },
262261
},
263-
["my-dotnet-tool"] = new McpLocalServerConfig
262+
["my-dotnet-tool"] = new McpStdioServerConfig
264263
{
265-
Type = "local",
266264
Command = "dotnet",
267265
Args = new List<string> { @"C:\Tools\MyTool\MyTool.dll" },
268266
Cwd = @"C:\Tools\MyTool",
@@ -275,19 +273,17 @@ public static class McpDotnetConfigExample
275273
<!-- /docs-validate: hidden -->
276274
```csharp
277275
// Correct configuration for .NET exe
278-
["my-dotnet-server"] = new McpLocalServerConfig
276+
["my-dotnet-server"] = new McpStdioServerConfig
279277
{
280-
Type = "local",
281278
Command = @"C:\Tools\MyServer\MyServer.exe", // Full path with .exe
282279
Args = new List<string>(),
283280
Cwd = @"C:\Tools\MyServer", // Set working directory
284281
Tools = new List<string> { "*" },
285282
}
286283

287284
// For dotnet tool (DLL)
288-
["my-dotnet-tool"] = new McpLocalServerConfig
285+
["my-dotnet-tool"] = new McpStdioServerConfig
289286
{
290-
Type = "local",
291287
Command = "dotnet",
292288
Args = new List<string> { @"C:\Tools\MyTool\MyTool.dll" },
293289
Cwd = @"C:\Tools\MyTool",
@@ -305,11 +301,10 @@ public static class McpNpxConfigExample
305301
{
306302
public static void Main()
307303
{
308-
var servers = new Dictionary<string, McpLocalServerConfig>
304+
var servers = new Dictionary<string, McpServerConfig>
309305
{
310-
["filesystem"] = new McpLocalServerConfig
306+
["filesystem"] = new McpStdioServerConfig
311307
{
312-
Type = "local",
313308
Command = "cmd",
314309
Args = new List<string> { "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\allowed\\path" },
315310
Tools = new List<string> { "*" },
@@ -321,9 +316,8 @@ public static class McpNpxConfigExample
321316
<!-- /docs-validate: hidden -->
322317
```csharp
323318
// Windows needs cmd /c for npx
324-
["filesystem"] = new McpLocalServerConfig
319+
["filesystem"] = new McpStdioServerConfig
325320
{
326-
Type = "local",
327321
Command = "cmd",
328322
Args = new List<string> { "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\allowed\\path" },
329323
Tools = new List<string> { "*" },
@@ -357,9 +351,9 @@ xattr -d com.apple.quarantine /path/to/mcp-server
357351

358352
<!-- docs-validate: hidden -->
359353
```typescript
360-
import { MCPLocalServerConfig } from "@github/copilot-sdk";
354+
import { MCPStdioServerConfig } from "@github/copilot-sdk";
361355

362-
const mcpServers: Record<string, MCPLocalServerConfig> = {
356+
const mcpServers: Record<string, MCPStdioServerConfig> = {
363357
"my-server": {
364358
command: "/opt/homebrew/bin/node",
365359
args: ["/path/to/server.js"],

dotnet/src/Client.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,7 @@ internal record CreateSessionRequest(
16311631
bool? Hooks,
16321632
string? WorkingDirectory,
16331633
bool? Streaming,
1634-
Dictionary<string, object>? McpServers,
1634+
Dictionary<string, McpServerConfig>? McpServers,
16351635
string? EnvValueMode,
16361636
List<CustomAgentConfig>? CustomAgents,
16371637
string? Agent,
@@ -1686,7 +1686,7 @@ internal record ResumeSessionRequest(
16861686
bool? EnableConfigDiscovery,
16871687
bool? DisableResume,
16881688
bool? Streaming,
1689-
Dictionary<string, object>? McpServers,
1689+
Dictionary<string, McpServerConfig>? McpServers,
16901690
string? EnvValueMode,
16911691
List<CustomAgentConfig>? CustomAgents,
16921692
string? Agent,

dotnet/src/Types.cs

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,27 +1502,44 @@ public class AzureOptions
15021502
// ============================================================================
15031503

15041504
/// <summary>
1505-
/// Configuration for a local/stdio MCP server.
1505+
/// Abstract base class for MCP server configurations.
15061506
/// </summary>
1507-
public class McpLocalServerConfig
1507+
[JsonPolymorphic(
1508+
TypeDiscriminatorPropertyName = "type",
1509+
IgnoreUnrecognizedTypeDiscriminators = true)]
1510+
[JsonDerivedType(typeof(McpStdioServerConfig), "stdio")]
1511+
[JsonDerivedType(typeof(McpHttpServerConfig), "http")]
1512+
public abstract class McpServerConfig
15081513
{
1514+
private protected McpServerConfig() { }
1515+
15091516
/// <summary>
15101517
/// List of tools to include from this server. Empty list means none. Use "*" for all.
15111518
/// </summary>
15121519
[JsonPropertyName("tools")]
15131520
public List<string> Tools { get; set; } = [];
15141521

15151522
/// <summary>
1516-
/// Server type. Defaults to "local".
1523+
/// The server type discriminator.
15171524
/// </summary>
1518-
[JsonPropertyName("type")]
1519-
public string? Type { get; set; }
1525+
[JsonIgnore]
1526+
public virtual string Type => "unknown";
15201527

15211528
/// <summary>
15221529
/// Optional timeout in milliseconds for tool calls to this server.
15231530
/// </summary>
15241531
[JsonPropertyName("timeout")]
15251532
public int? Timeout { get; set; }
1533+
}
1534+
1535+
/// <summary>
1536+
/// Configuration for a local/stdio MCP server.
1537+
/// </summary>
1538+
public sealed class McpStdioServerConfig : McpServerConfig
1539+
{
1540+
/// <inheritdoc />
1541+
[JsonIgnore]
1542+
public override string Type => "stdio";
15261543

15271544
/// <summary>
15281545
/// Command to run the MCP server.
@@ -1552,25 +1569,11 @@ public class McpLocalServerConfig
15521569
/// <summary>
15531570
/// Configuration for a remote MCP server (HTTP or SSE).
15541571
/// </summary>
1555-
public class McpRemoteServerConfig
1572+
public sealed class McpHttpServerConfig : McpServerConfig
15561573
{
1557-
/// <summary>
1558-
/// List of tools to include from this server. Empty list means none. Use "*" for all.
1559-
/// </summary>
1560-
[JsonPropertyName("tools")]
1561-
public List<string> Tools { get; set; } = [];
1562-
1563-
/// <summary>
1564-
/// Server type. Must be "http" or "sse".
1565-
/// </summary>
1566-
[JsonPropertyName("type")]
1567-
public string Type { get; set; } = "http";
1568-
1569-
/// <summary>
1570-
/// Optional timeout in milliseconds for tool calls to this server.
1571-
/// </summary>
1572-
[JsonPropertyName("timeout")]
1573-
public int? Timeout { get; set; }
1574+
/// <inheritdoc />
1575+
[JsonIgnore]
1576+
public override string Type => "http";
15741577

15751578
/// <summary>
15761579
/// URL of the remote server.
@@ -1628,7 +1631,7 @@ public class CustomAgentConfig
16281631
/// MCP servers specific to this agent.
16291632
/// </summary>
16301633
[JsonPropertyName("mcpServers")]
1631-
public Dictionary<string, object>? McpServers { get; set; }
1634+
public Dictionary<string, McpServerConfig>? McpServers { get; set; }
16321635

16331636
/// <summary>
16341637
/// Whether the agent should be available for model inference.
@@ -1697,7 +1700,7 @@ protected SessionConfig(SessionConfig? other)
16971700
Hooks = other.Hooks;
16981701
InfiniteSessions = other.InfiniteSessions;
16991702
McpServers = other.McpServers is not null
1700-
? new Dictionary<string, object>(other.McpServers, other.McpServers.Comparer)
1703+
? new Dictionary<string, McpServerConfig>(other.McpServers, other.McpServers.Comparer)
17011704
: null;
17021705
Model = other.Model;
17031706
ModelCapabilities = other.ModelCapabilities;
@@ -1829,9 +1832,9 @@ protected SessionConfig(SessionConfig? other)
18291832

18301833
/// <summary>
18311834
/// MCP server configurations for the session.
1832-
/// Keys are server names, values are server configurations (McpLocalServerConfig or McpRemoteServerConfig).
1835+
/// Keys are server names, values are server configurations (<see cref="McpStdioServerConfig"/> or <see cref="McpHttpServerConfig"/>).
18331836
/// </summary>
1834-
public Dictionary<string, object>? McpServers { get; set; }
1837+
public Dictionary<string, McpServerConfig>? McpServers { get; set; }
18351838

18361839
/// <summary>
18371840
/// Custom agent configurations for the session.
@@ -1925,7 +1928,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
19251928
Hooks = other.Hooks;
19261929
InfiniteSessions = other.InfiniteSessions;
19271930
McpServers = other.McpServers is not null
1928-
? new Dictionary<string, object>(other.McpServers, other.McpServers.Comparer)
1931+
? new Dictionary<string, McpServerConfig>(other.McpServers, other.McpServers.Comparer)
19291932
: null;
19301933
Model = other.Model;
19311934
ModelCapabilities = other.ModelCapabilities;
@@ -2061,9 +2064,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
20612064

20622065
/// <summary>
20632066
/// MCP server configurations for the session.
2064-
/// Keys are server names, values are server configurations (McpLocalServerConfig or McpRemoteServerConfig).
2067+
/// Keys are server names, values are server configurations (<see cref="McpStdioServerConfig"/> or <see cref="McpHttpServerConfig"/>).
20652068
/// </summary>
2066-
public Dictionary<string, object>? McpServers { get; set; }
2069+
public Dictionary<string, McpServerConfig>? McpServers { get; set; }
20672070

20682071
/// <summary>
20692072
/// Custom agent configurations for the session.
@@ -2608,8 +2611,7 @@ public class SystemMessageTransformRpcResponse
26082611
[JsonSerializable(typeof(GetForegroundSessionResponse))]
26092612
[JsonSerializable(typeof(GetModelsResponse))]
26102613
[JsonSerializable(typeof(GetStatusResponse))]
2611-
[JsonSerializable(typeof(McpLocalServerConfig))]
2612-
[JsonSerializable(typeof(McpRemoteServerConfig))]
2614+
[JsonSerializable(typeof(McpServerConfig))]
26132615
[JsonSerializable(typeof(MessageOptions))]
26142616
[JsonSerializable(typeof(ModelBilling))]
26152617
[JsonSerializable(typeof(ModelCapabilities))]

dotnet/test/CloneTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
8686
ExcludedTools = ["tool3"],
8787
WorkingDirectory = "/workspace",
8888
Streaming = true,
89-
McpServers = new Dictionary<string, object> { ["server1"] = new object() },
89+
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
9090
CustomAgents = [new CustomAgentConfig { Name = "agent1" }],
9191
Agent = "agent1",
9292
SkillDirectories = ["/skills"],
@@ -118,7 +118,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
118118
{
119119
AvailableTools = ["tool1"],
120120
ExcludedTools = ["tool2"],
121-
McpServers = new Dictionary<string, object> { ["s1"] = new object() },
121+
McpServers = new Dictionary<string, McpServerConfig> { ["s1"] = new McpStdioServerConfig { Command = "echo" } },
122122
CustomAgents = [new CustomAgentConfig { Name = "a1" }],
123123
SkillDirectories = ["/skills"],
124124
DisabledSkills = ["skill1"],
@@ -129,7 +129,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
129129
// Mutate clone collections
130130
clone.AvailableTools!.Add("tool99");
131131
clone.ExcludedTools!.Add("tool99");
132-
clone.McpServers!["s2"] = new object();
132+
clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" };
133133
clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" });
134134
clone.SkillDirectories!.Add("/more");
135135
clone.DisabledSkills!.Add("skill99");
@@ -146,7 +146,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent()
146146
[Fact]
147147
public void SessionConfig_Clone_PreservesMcpServersComparer()
148148
{
149-
var servers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase) { ["server"] = new object() };
149+
var servers = new Dictionary<string, McpServerConfig>(StringComparer.OrdinalIgnoreCase) { ["server"] = new McpStdioServerConfig { Command = "echo" } };
150150
var original = new SessionConfig { McpServers = servers };
151151

152152
var clone = original.Clone();
@@ -161,7 +161,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
161161
{
162162
AvailableTools = ["tool1"],
163163
ExcludedTools = ["tool2"],
164-
McpServers = new Dictionary<string, object> { ["s1"] = new object() },
164+
McpServers = new Dictionary<string, McpServerConfig> { ["s1"] = new McpStdioServerConfig { Command = "echo" } },
165165
CustomAgents = [new CustomAgentConfig { Name = "a1" }],
166166
SkillDirectories = ["/skills"],
167167
DisabledSkills = ["skill1"],
@@ -172,7 +172,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
172172
// Mutate clone collections
173173
clone.AvailableTools!.Add("tool99");
174174
clone.ExcludedTools!.Add("tool99");
175-
clone.McpServers!["s2"] = new object();
175+
clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" };
176176
clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" });
177177
clone.SkillDirectories!.Add("/more");
178178
clone.DisabledSkills!.Add("skill99");
@@ -189,7 +189,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent()
189189
[Fact]
190190
public void ResumeSessionConfig_Clone_PreservesMcpServersComparer()
191191
{
192-
var servers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase) { ["server"] = new object() };
192+
var servers = new Dictionary<string, McpServerConfig>(StringComparer.OrdinalIgnoreCase) { ["server"] = new McpStdioServerConfig { Command = "echo" } };
193193
var original = new ResumeSessionConfig { McpServers = servers };
194194

195195
var clone = original.Clone();

0 commit comments

Comments
 (0)