Skip to content

Commit dbd5cc3

Browse files
Equivalent fixes for other languages
1 parent 82e2b78 commit dbd5cc3

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
@@ -383,6 +383,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
383383
config?.WorkingDirectory,
384384
config?.Streaming == true ? true : null,
385385
config?.McpServers,
386+
"direct",
386387
config?.CustomAgents,
387388
config?.ConfigDir,
388389
config?.SkillDirectories,
@@ -468,6 +469,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
468469
config?.DisableResume == true ? true : null,
469470
config?.Streaming == true ? true : null,
470471
config?.McpServers,
472+
"direct",
471473
config?.CustomAgents,
472474
config?.SkillDirectories,
473475
config?.DisabledSkills,
@@ -1385,6 +1387,7 @@ internal record CreateSessionRequest(
13851387
string? WorkingDirectory,
13861388
bool? Streaming,
13871389
Dictionary<string, object>? McpServers,
1390+
string? EnvValueMode,
13881391
List<CustomAgentConfig>? CustomAgents,
13891392
string? ConfigDir,
13901393
List<string>? SkillDirectories,
@@ -1421,6 +1424,7 @@ internal record ResumeSessionRequest(
14211424
bool? DisableResume,
14221425
bool? Streaming,
14231426
Dictionary<string, object>? McpServers,
1427+
string? EnvValueMode,
14241428
List<CustomAgentConfig>? CustomAgents,
14251429
List<string>? SkillDirectories,
14261430
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
@@ -464,6 +464,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
464464
req.Provider = config.Provider
465465
req.WorkingDirectory = config.WorkingDirectory
466466
req.MCPServers = config.MCPServers
467+
req.EnvValueMode = "direct"
467468
req.CustomAgents = config.CustomAgents
468469
req.SkillDirectories = config.SkillDirectories
469470
req.DisabledSkills = config.DisabledSkills
@@ -581,6 +582,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
581582
req.DisableResume = Bool(true)
582583
}
583584
req.MCPServers = config.MCPServers
585+
req.EnvValueMode = "direct"
584586
req.CustomAgents = config.CustomAgents
585587
req.SkillDirectories = config.SkillDirectories
586588
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
@@ -638,6 +638,7 @@ type createSessionRequest struct {
638638
WorkingDirectory string `json:"workingDirectory,omitempty"`
639639
Streaming *bool `json:"streaming,omitempty"`
640640
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
641+
EnvValueMode string `json:"envValueMode,omitempty"`
641642
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
642643
ConfigDir string `json:"configDir,omitempty"`
643644
SkillDirectories []string `json:"skillDirectories,omitempty"`
@@ -669,6 +670,7 @@ type resumeSessionRequest struct {
669670
DisableResume *bool `json:"disableResume,omitempty"`
670671
Streaming *bool `json:"streaming,omitempty"`
671672
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
673+
EnvValueMode string `json:"envValueMode,omitempty"`
672674
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
673675
SkillDirectories []string `json:"skillDirectories,omitempty"`
674676
DisabledSkills []string `json:"disabledSkills,omitempty"`

nodejs/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ export class CopilotClient {
612612
configDir: config.configDir,
613613
streaming: config.streaming,
614614
mcpServers: config.mcpServers,
615-
envValueMode: config.mcpServers ? "direct" : undefined,
615+
envValueMode: "direct",
616616
customAgents: config.customAgents,
617617
skillDirectories: config.skillDirectories,
618618
disabledSkills: config.disabledSkills,

python/copilot/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ async def create_session(self, config: Optional[SessionConfig] = None) -> Copilo
519519
mcp_servers = cfg.get("mcp_servers")
520520
if mcp_servers:
521521
payload["mcpServers"] = mcp_servers
522+
payload["envValueMode"] = "direct"
522523

523524
# Add custom agents configuration if provided
524525
custom_agents = cfg.get("custom_agents")
@@ -695,6 +696,7 @@ async def resume_session(
695696
mcp_servers = cfg.get("mcp_servers")
696697
if mcp_servers:
697698
payload["mcpServers"] = mcp_servers
699+
payload["envValueMode"] = "direct"
698700

699701
# Add custom agents configuration if provided
700702
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)