Skip to content

Commit 866d7db

Browse files
Equivalent fixes for other languages
1 parent 66c9cb0 commit 866d7db

File tree

10 files changed

+147
-2
lines changed

10 files changed

+147
-2
lines changed

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
382382
config?.WorkingDirectory,
383383
config?.Streaming == true ? true : null,
384384
config?.McpServers,
385+
"direct",
385386
config?.CustomAgents,
386387
config?.ConfigDir,
387388
config?.SkillDirectories,
@@ -467,6 +468,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
467468
config?.DisableResume == true ? true : null,
468469
config?.Streaming == true ? true : null,
469470
config?.McpServers,
471+
"direct",
470472
config?.CustomAgents,
471473
config?.SkillDirectories,
472474
config?.DisabledSkills,
@@ -1355,6 +1357,7 @@ internal record CreateSessionRequest(
13551357
string? WorkingDirectory,
13561358
bool? Streaming,
13571359
Dictionary<string, object>? McpServers,
1360+
string? EnvValueMode,
13581361
List<CustomAgentConfig>? CustomAgents,
13591362
string? ConfigDir,
13601363
List<string>? SkillDirectories,
@@ -1391,6 +1394,7 @@ internal record ResumeSessionRequest(
13911394
bool? DisableResume,
13921395
bool? Streaming,
13931396
Dictionary<string, object>? McpServers,
1397+
string? EnvValueMode,
13941398
List<CustomAgentConfig>? CustomAgents,
13951399
List<string>? SkillDirectories,
13961400
List<string>? DisabledSkills,

dotnet/test/Harness/E2ETestContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public IReadOnlyDictionary<string, string> GetEnvironment()
9292
public CopilotClient CreateClient() => new(new CopilotClientOptions
9393
{
9494
Cwd = WorkDir,
95+
CliPath = GetCliPath(_repoRoot),
9596
Environment = GetEnvironment(),
9697
GithubToken = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) ? "fake-token-for-e2e-tests" : null,
9798
});

dotnet/test/McpAndAgentsTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,41 @@ public async Task Should_Handle_Multiple_Custom_Agents()
260260
await session.DisposeAsync();
261261
}
262262

263+
[Fact]
264+
public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess()
265+
{
266+
var testHarnessDir = FindTestHarnessDir();
267+
var mcpServers = new Dictionary<string, object>
268+
{
269+
["env-echo"] = new McpLocalServerConfig
270+
{
271+
Type = "local",
272+
Command = "node",
273+
Args = [Path.Combine(testHarnessDir, "test-mcp-server.mjs")],
274+
Env = new Dictionary<string, string> { ["TEST_SECRET"] = "hunter2" },
275+
Cwd = testHarnessDir,
276+
Tools = ["*"]
277+
}
278+
};
279+
280+
var session = await Client.CreateSessionAsync(new SessionConfig
281+
{
282+
McpServers = mcpServers
283+
});
284+
285+
Assert.Matches(@"^[a-f0-9-]+$", session.SessionId);
286+
287+
var message = await session.SendAndWaitAsync(new MessageOptions
288+
{
289+
Prompt = "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else."
290+
});
291+
292+
Assert.NotNull(message);
293+
Assert.Contains("hunter2", message!.Data.Content);
294+
295+
await session.DisposeAsync();
296+
}
297+
263298
[Fact]
264299
public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents()
265300
{
@@ -301,4 +336,17 @@ public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents()
301336

302337
await session.DisposeAsync();
303338
}
339+
340+
private static string FindTestHarnessDir()
341+
{
342+
var dir = new DirectoryInfo(AppContext.BaseDirectory);
343+
while (dir != null)
344+
{
345+
var candidate = Path.Combine(dir.FullName, "test", "harness", "test-mcp-server.mjs");
346+
if (File.Exists(candidate))
347+
return Path.GetDirectoryName(candidate)!;
348+
dir = dir.Parent;
349+
}
350+
throw new InvalidOperationException("Could not find test/harness/test-mcp-server.mjs");
351+
}
304352
}

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
459459
req.Provider = config.Provider
460460
req.WorkingDirectory = config.WorkingDirectory
461461
req.MCPServers = config.MCPServers
462+
req.EnvValueMode = "direct"
462463
req.CustomAgents = config.CustomAgents
463464
req.SkillDirectories = config.SkillDirectories
464465
req.DisabledSkills = config.DisabledSkills
@@ -576,6 +577,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
576577
req.DisableResume = Bool(true)
577578
}
578579
req.MCPServers = config.MCPServers
580+
req.EnvValueMode = "direct"
579581
req.CustomAgents = config.CustomAgents
580582
req.SkillDirectories = config.SkillDirectories
581583
req.DisabledSkills = config.DisabledSkills

go/internal/e2e/mcp_and_agents_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package e2e
22

33
import (
4+
"path/filepath"
45
"strings"
56
"testing"
67

@@ -104,6 +105,51 @@ func TestMCPServers(t *testing.T) {
104105
session2.Destroy()
105106
})
106107

108+
t.Run("should pass literal env values to MCP server subprocess", func(t *testing.T) {
109+
ctx.ConfigureForTest(t)
110+
111+
mcpServerPath, err := filepath.Abs("../../../test/harness/test-mcp-server.mjs")
112+
if err != nil {
113+
t.Fatalf("Failed to resolve test-mcp-server path: %v", err)
114+
}
115+
mcpServerDir := filepath.Dir(mcpServerPath)
116+
117+
mcpServers := map[string]copilot.MCPServerConfig{
118+
"env-echo": {
119+
"type": "local",
120+
"command": "node",
121+
"args": []string{mcpServerPath},
122+
"tools": []string{"*"},
123+
"env": map[string]string{"TEST_SECRET": "hunter2"},
124+
"cwd": mcpServerDir,
125+
},
126+
}
127+
128+
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
129+
MCPServers: mcpServers,
130+
})
131+
if err != nil {
132+
t.Fatalf("Failed to create session: %v", err)
133+
}
134+
135+
if session.SessionID == "" {
136+
t.Error("Expected non-empty session ID")
137+
}
138+
139+
message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
140+
Prompt: "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else.",
141+
})
142+
if err != nil {
143+
t.Fatalf("Failed to send message: %v", err)
144+
}
145+
146+
if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "hunter2") {
147+
t.Errorf("Expected message to contain 'hunter2', got: %v", message.Data.Content)
148+
}
149+
150+
session.Destroy()
151+
})
152+
107153
t.Run("handle multiple MCP servers", func(t *testing.T) {
108154
ctx.ConfigureForTest(t)
109155

go/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ type createSessionRequest struct {
636636
WorkingDirectory string `json:"workingDirectory,omitempty"`
637637
Streaming *bool `json:"streaming,omitempty"`
638638
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
639+
EnvValueMode string `json:"envValueMode,omitempty"`
639640
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
640641
ConfigDir string `json:"configDir,omitempty"`
641642
SkillDirectories []string `json:"skillDirectories,omitempty"`
@@ -667,6 +668,7 @@ type resumeSessionRequest struct {
667668
DisableResume *bool `json:"disableResume,omitempty"`
668669
Streaming *bool `json:"streaming,omitempty"`
669670
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
671+
EnvValueMode string `json:"envValueMode,omitempty"`
670672
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
671673
SkillDirectories []string `json:"skillDirectories,omitempty"`
672674
DisabledSkills []string `json:"disabledSkills,omitempty"`

nodejs/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ export class CopilotClient {
606606
configDir: config.configDir,
607607
streaming: config.streaming,
608608
mcpServers: config.mcpServers,
609-
envValueMode: config.mcpServers ? "direct" : undefined,
609+
envValueMode: "direct",
610610
customAgents: config.customAgents,
611611
skillDirectories: config.skillDirectories,
612612
disabledSkills: config.disabledSkills,

python/copilot/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ async def create_session(self, config: Optional[SessionConfig] = None) -> Copilo
504504
mcp_servers = cfg.get("mcp_servers")
505505
if mcp_servers:
506506
payload["mcpServers"] = mcp_servers
507+
payload["envValueMode"] = "direct"
507508

508509
# Add custom agents configuration if provided
509510
custom_agents = cfg.get("custom_agents")
@@ -680,6 +681,7 @@ async def resume_session(
680681
mcp_servers = cfg.get("mcp_servers")
681682
if mcp_servers:
682683
payload["mcpServers"] = mcp_servers
684+
payload["envValueMode"] = "direct"
683685

684686
# Add custom agents configuration if provided
685687
custom_agents = cfg.get("custom_agents")

python/e2e/test_mcp_and_agents.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22
Tests for MCP servers and custom agents functionality
33
"""
44

5+
from pathlib import Path
6+
57
import pytest
68

79
from copilot import CustomAgentConfig, MCPServerConfig
810

911
from .testharness import E2ETestContext, get_final_assistant_message
1012

13+
TEST_MCP_SERVER = str(
14+
(Path(__file__).parents[2] / "test" / "harness" / "test-mcp-server.mjs").resolve()
15+
)
16+
TEST_HARNESS_DIR = str((Path(__file__).parents[2] / "test" / "harness").resolve())
17+
1118
pytestmark = pytest.mark.asyncio(loop_scope="module")
1219

1320

@@ -65,6 +72,35 @@ async def test_should_accept_mcp_server_configuration_on_session_resume(
6572

6673
await session2.destroy()
6774

75+
async def test_should_pass_literal_env_values_to_mcp_server_subprocess(
76+
self, ctx: E2ETestContext
77+
):
78+
"""Test that env values are passed as literals to MCP server subprocess"""
79+
mcp_servers: dict[str, MCPServerConfig] = {
80+
"env-echo": {
81+
"type": "local",
82+
"command": "node",
83+
"args": [TEST_MCP_SERVER],
84+
"tools": ["*"],
85+
"env": {"TEST_SECRET": "hunter2"},
86+
"cwd": TEST_HARNESS_DIR,
87+
}
88+
}
89+
90+
session = await ctx.client.create_session({"mcp_servers": mcp_servers})
91+
92+
assert session.session_id is not None
93+
94+
message = await session.send_and_wait(
95+
{
96+
"prompt": "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else."
97+
}
98+
)
99+
assert message is not None
100+
assert "hunter2" in message.data.content
101+
102+
await session.destroy()
103+
68104

69105
class TestCustomAgents:
70106
async def test_should_accept_custom_agent_configuration_on_session_create(

python/e2e/testharness/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818

1919
def get_cli_path_for_tests() -> str:
20-
"""Get CLI path for E2E tests. Uses node_modules CLI during development."""
20+
"""Get CLI path for E2E tests. Uses COPILOT_CLI_PATH env var if set, otherwise node_modules CLI."""
21+
env_path = os.environ.get("COPILOT_CLI_PATH")
22+
if env_path and Path(env_path).exists():
23+
return str(Path(env_path).resolve())
24+
2125
# Look for CLI in sibling nodejs directory's node_modules
2226
base_path = Path(__file__).parents[3]
2327
full_path = base_path / "nodejs" / "node_modules" / "@github" / "copilot" / "index.js"

0 commit comments

Comments
 (0)