Skip to content

Commit 56cee2f

Browse files
stephentoubCopilot
andcommitted
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>
1 parent f769503 commit 56cee2f

4 files changed

Lines changed: 73 additions & 54 deletions

File tree

docs/features/mcp.md

Lines changed: 4 additions & 6 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
})

go/internal/e2e/mcp_and_agents_test.go

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ func TestMCPServers(t *testing.T) {
1818
ctx.ConfigureForTest(t)
1919

2020
mcpServers := map[string]copilot.MCPServerConfig{
21-
"test-server": {
22-
"type": "local",
23-
"command": "echo",
24-
"args": []string{"hello"},
25-
"tools": []string{"*"},
21+
"test-server": copilot.MCPStdioServerConfig{
22+
Command: "echo",
23+
Args: []string{"hello"},
24+
Tools: []string{"*"},
2625
},
2726
}
2827

@@ -75,11 +74,10 @@ func TestMCPServers(t *testing.T) {
7574

7675
// Resume with MCP servers
7776
mcpServers := map[string]copilot.MCPServerConfig{
78-
"test-server": {
79-
"type": "local",
80-
"command": "echo",
81-
"args": []string{"hello"},
82-
"tools": []string{"*"},
77+
"test-server": copilot.MCPStdioServerConfig{
78+
Command: "echo",
79+
Args: []string{"hello"},
80+
Tools: []string{"*"},
8381
},
8482
}
8583

@@ -117,13 +115,12 @@ func TestMCPServers(t *testing.T) {
117115
mcpServerDir := filepath.Dir(mcpServerPath)
118116

119117
mcpServers := map[string]copilot.MCPServerConfig{
120-
"env-echo": {
121-
"type": "local",
122-
"command": "node",
123-
"args": []string{mcpServerPath},
124-
"tools": []string{"*"},
125-
"env": map[string]string{"TEST_SECRET": "hunter2"},
126-
"cwd": mcpServerDir,
118+
"env-echo": copilot.MCPStdioServerConfig{
119+
Command: "node",
120+
Args: []string{mcpServerPath},
121+
Tools: []string{"*"},
122+
Env: map[string]string{"TEST_SECRET": "hunter2"},
123+
Cwd: mcpServerDir,
127124
},
128125
}
129126

@@ -157,17 +154,15 @@ func TestMCPServers(t *testing.T) {
157154
ctx.ConfigureForTest(t)
158155

159156
mcpServers := map[string]copilot.MCPServerConfig{
160-
"server1": {
161-
"type": "local",
162-
"command": "echo",
163-
"args": []string{"server1"},
164-
"tools": []string{"*"},
157+
"server1": copilot.MCPStdioServerConfig{
158+
Command: "echo",
159+
Args: []string{"server1"},
160+
Tools: []string{"*"},
165161
},
166-
"server2": {
167-
"type": "local",
168-
"command": "echo",
169-
"args": []string{"server2"},
170-
"tools": []string{"*"},
162+
"server2": copilot.MCPStdioServerConfig{
163+
Command: "echo",
164+
Args: []string{"server2"},
165+
Tools: []string{"*"},
171166
},
172167
}
173168

@@ -327,11 +322,10 @@ func TestCustomAgents(t *testing.T) {
327322
Description: "An agent with its own MCP servers",
328323
Prompt: "You are an agent with MCP servers.",
329324
MCPServers: map[string]copilot.MCPServerConfig{
330-
"agent-server": {
331-
"type": "local",
332-
"command": "echo",
333-
"args": []string{"agent-mcp"},
334-
"tools": []string{"*"},
325+
"agent-server": copilot.MCPStdioServerConfig{
326+
Command: "echo",
327+
Args: []string{"agent-mcp"},
328+
Tools: []string{"*"},
335329
},
336330
},
337331
},
@@ -399,11 +393,10 @@ func TestCombinedConfiguration(t *testing.T) {
399393
ctx.ConfigureForTest(t)
400394

401395
mcpServers := map[string]copilot.MCPServerConfig{
402-
"shared-server": {
403-
"type": "local",
404-
"command": "echo",
405-
"args": []string{"shared"},
406-
"tools": []string{"*"},
396+
"shared-server": copilot.MCPStdioServerConfig{
397+
Command: "echo",
398+
Args: []string{"shared"},
399+
Tools: []string{"*"},
407400
},
408401
}
409402

go/types.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,29 +382,57 @@ type SessionHooks struct {
382382
OnErrorOccurred ErrorOccurredHandler
383383
}
384384

385-
// MCPStdioServerConfig configures a local/stdio MCP server
385+
// MCPServerConfig is implemented by MCP server configuration types.
386+
// Only MCPStdioServerConfig and MCPHTTPServerConfig implement this interface.
387+
type MCPServerConfig interface {
388+
mcpServerConfig()
389+
}
390+
391+
// MCPStdioServerConfig configures a local/stdio MCP server.
386392
type MCPStdioServerConfig struct {
387393
Tools []string `json:"tools"`
388-
Type string `json:"type,omitempty"` // "local" or "stdio"
389394
Timeout int `json:"timeout,omitempty"`
390395
Command string `json:"command"`
391396
Args []string `json:"args"`
392397
Env map[string]string `json:"env,omitempty"`
393398
Cwd string `json:"cwd,omitempty"`
394399
}
395400

396-
// MCPHTTPServerConfig configures a remote MCP server (HTTP or SSE)
401+
func (MCPStdioServerConfig) mcpServerConfig() {}
402+
403+
// MarshalJSON implements json.Marshaler, injecting the "type" discriminator.
404+
func (c MCPStdioServerConfig) MarshalJSON() ([]byte, error) {
405+
type alias MCPStdioServerConfig
406+
return json.Marshal(struct {
407+
Type string `json:"type"`
408+
alias
409+
}{
410+
Type: "stdio",
411+
alias: alias(c),
412+
})
413+
}
414+
415+
// MCPHTTPServerConfig configures a remote MCP server (HTTP or SSE).
397416
type MCPHTTPServerConfig struct {
398417
Tools []string `json:"tools"`
399-
Type string `json:"type"` // "http" or "sse"
400418
Timeout int `json:"timeout,omitempty"`
401419
URL string `json:"url"`
402420
Headers map[string]string `json:"headers,omitempty"`
403421
}
404422

405-
// MCPServerConfig can be either MCPStdioServerConfig or MCPHTTPServerConfig
406-
// Use a map[string]any for flexibility, or create separate configs
407-
type MCPServerConfig map[string]any
423+
func (MCPHTTPServerConfig) mcpServerConfig() {}
424+
425+
// MarshalJSON implements json.Marshaler, injecting the "type" discriminator.
426+
func (c MCPHTTPServerConfig) MarshalJSON() ([]byte, error) {
427+
type alias MCPHTTPServerConfig
428+
return json.Marshal(struct {
429+
Type string `json:"type"`
430+
alias
431+
}{
432+
Type: "http",
433+
alias: alias(c),
434+
})
435+
}
408436

409437
// CustomAgentConfig configures a custom agent
410438
type CustomAgentConfig struct {

test/scenarios/tools/mcp-servers/go/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ func main() {
3030
if argsStr := os.Getenv("MCP_SERVER_ARGS"); argsStr != "" {
3131
args = strings.Split(argsStr, " ")
3232
}
33-
mcpServers["example"] = copilot.MCPServerConfig{
34-
"type": "stdio",
35-
"command": cmd,
36-
"args": args,
33+
mcpServers["example"] = copilot.MCPStdioServerConfig{
34+
Command: cmd,
35+
Args: args,
36+
Tools: []string{"*"},
3737
}
3838
}
3939

0 commit comments

Comments
 (0)