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
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
config?.WorkingDirectory,
config?.Streaming == true ? true : null,
config?.McpServers,
"direct",
config?.CustomAgents,
config?.ConfigDir,
config?.SkillDirectories,
Expand Down Expand Up @@ -468,6 +469,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
config?.DisableResume == true ? true : null,
config?.Streaming == true ? true : null,
config?.McpServers,
"direct",
config?.CustomAgents,
config?.SkillDirectories,
config?.DisabledSkills,
Expand Down Expand Up @@ -1385,6 +1387,7 @@ internal record CreateSessionRequest(
string? WorkingDirectory,
bool? Streaming,
Dictionary<string, object>? McpServers,
string? EnvValueMode,
List<CustomAgentConfig>? CustomAgents,
string? ConfigDir,
List<string>? SkillDirectories,
Expand Down Expand Up @@ -1421,6 +1424,7 @@ internal record ResumeSessionRequest(
bool? DisableResume,
bool? Streaming,
Dictionary<string, object>? McpServers,
string? EnvValueMode,
List<CustomAgentConfig>? CustomAgents,
List<string>? SkillDirectories,
List<string>? DisabledSkills,
Expand Down
1 change: 1 addition & 0 deletions dotnet/test/Harness/E2ETestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public IReadOnlyDictionary<string, string> GetEnvironment()
public CopilotClient CreateClient() => new(new CopilotClientOptions
{
Cwd = WorkDir,
CliPath = GetCliPath(_repoRoot),
Environment = GetEnvironment(),
GithubToken = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) ? "fake-token-for-e2e-tests" : null,
});
Expand Down
48 changes: 48 additions & 0 deletions dotnet/test/McpAndAgentsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,41 @@ public async Task Should_Handle_Multiple_Custom_Agents()
await session.DisposeAsync();
}

[Fact]
public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess()
{
var testHarnessDir = FindTestHarnessDir();
var mcpServers = new Dictionary<string, object>
{
["env-echo"] = new McpLocalServerConfig
{
Type = "local",
Command = "node",
Args = [Path.Combine(testHarnessDir, "test-mcp-server.mjs")],
Env = new Dictionary<string, string> { ["TEST_SECRET"] = "hunter2" },
Cwd = testHarnessDir,
Tools = ["*"]
}
};

var session = await Client.CreateSessionAsync(new SessionConfig
{
McpServers = mcpServers
});

Assert.Matches(@"^[a-f0-9-]+$", session.SessionId);

var message = await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else."
});

Assert.NotNull(message);
Assert.Contains("hunter2", message!.Data.Content);

await session.DisposeAsync();
}

[Fact]
public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents()
{
Expand Down Expand Up @@ -301,4 +336,17 @@ public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents()

await session.DisposeAsync();
}

private static string FindTestHarnessDir()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
var candidate = Path.Combine(dir.FullName, "test", "harness", "test-mcp-server.mjs");
if (File.Exists(candidate))
return Path.GetDirectoryName(candidate)!;
dir = dir.Parent;
}
throw new InvalidOperationException("Could not find test/harness/test-mcp-server.mjs");
}
}
2 changes: 2 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.Provider = config.Provider
req.WorkingDirectory = config.WorkingDirectory
req.MCPServers = config.MCPServers
req.EnvValueMode = "direct"
req.CustomAgents = config.CustomAgents
req.SkillDirectories = config.SkillDirectories
req.DisabledSkills = config.DisabledSkills
Expand Down Expand Up @@ -581,6 +582,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.DisableResume = Bool(true)
}
req.MCPServers = config.MCPServers
req.EnvValueMode = "direct"
req.CustomAgents = config.CustomAgents
req.SkillDirectories = config.SkillDirectories
req.DisabledSkills = config.DisabledSkills
Expand Down
46 changes: 46 additions & 0 deletions go/internal/e2e/mcp_and_agents_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package e2e

import (
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -104,6 +105,51 @@ func TestMCPServers(t *testing.T) {
session2.Destroy()
})

t.Run("should pass literal env values to MCP server subprocess", func(t *testing.T) {
ctx.ConfigureForTest(t)

mcpServerPath, err := filepath.Abs("../../../test/harness/test-mcp-server.mjs")
if err != nil {
t.Fatalf("Failed to resolve test-mcp-server path: %v", err)
}
mcpServerDir := filepath.Dir(mcpServerPath)

mcpServers := map[string]copilot.MCPServerConfig{
"env-echo": {
"type": "local",
"command": "node",
"args": []string{mcpServerPath},
"tools": []string{"*"},
"env": map[string]string{"TEST_SECRET": "hunter2"},
"cwd": mcpServerDir,
},
}

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
MCPServers: mcpServers,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}

if session.SessionID == "" {
t.Error("Expected non-empty session ID")
}

message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
Prompt: "Use the env-echo/get_env tool to read the TEST_SECRET environment variable. Reply with just the value, nothing else.",
})
if err != nil {
t.Fatalf("Failed to send message: %v", err)
}

if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "hunter2") {
t.Errorf("Expected message to contain 'hunter2', got: %v", message.Data.Content)
}

session.Destroy()
})

t.Run("handle multiple MCP servers", func(t *testing.T) {
ctx.ConfigureForTest(t)

Expand Down
2 changes: 2 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ type createSessionRequest struct {
WorkingDirectory string `json:"workingDirectory,omitempty"`
Streaming *bool `json:"streaming,omitempty"`
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
EnvValueMode string `json:"envValueMode,omitempty"`
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
ConfigDir string `json:"configDir,omitempty"`
SkillDirectories []string `json:"skillDirectories,omitempty"`
Expand Down Expand Up @@ -669,6 +670,7 @@ type resumeSessionRequest struct {
DisableResume *bool `json:"disableResume,omitempty"`
Streaming *bool `json:"streaming,omitempty"`
MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"`
EnvValueMode string `json:"envValueMode,omitempty"`
CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"`
SkillDirectories []string `json:"skillDirectories,omitempty"`
DisabledSkills []string `json:"disabledSkills,omitempty"`
Expand Down
56 changes: 28 additions & 28 deletions nodejs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
"@github/copilot": "^0.0.411-0",
"@github/copilot": "^0.0.411-1",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
Expand Down
4 changes: 3 additions & 1 deletion nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ import type {
ModelInfo,
ResumeSessionConfig,
SessionConfig,
SessionContext,
SessionEvent,
SessionLifecycleEvent,
SessionLifecycleEventType,
SessionLifecycleHandler,
SessionContext,
SessionListFilter,
SessionMetadata,
Tool,
Expand Down Expand Up @@ -529,6 +529,7 @@ export class CopilotClient {
workingDirectory: config.workingDirectory,
streaming: config.streaming,
mcpServers: config.mcpServers,
envValueMode: "direct",
customAgents: config.customAgents,
configDir: config.configDir,
skillDirectories: config.skillDirectories,
Expand Down Expand Up @@ -611,6 +612,7 @@ export class CopilotClient {
configDir: config.configDir,
streaming: config.streaming,
mcpServers: config.mcpServers,
envValueMode: "direct",
customAgents: config.customAgents,
skillDirectories: config.skillDirectories,
disabledSkills: config.disabledSkills,
Expand Down
3 changes: 2 additions & 1 deletion nodejs/test/e2e/harness/sdkTestContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const SNAPSHOTS_DIR = resolve(__dirname, "../../../../test/snapshots");

export async function createSdkTestContext({
logLevel,
}: { logLevel?: "error" | "none" | "warning" | "info" | "debug" | "all" } = {}) {
}: { logLevel?: "error" | "none" | "warning" | "info" | "debug" | "all"; cliPath?: string } = {}) {
const homeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-config-")));
const workDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-work-")));

Expand All @@ -40,6 +40,7 @@ export async function createSdkTestContext({
cwd: workDir,
env,
logLevel: logLevel || "error",
cliPath: process.env.COPILOT_CLI_PATH,
// Use fake token in CI to allow cached responses without real auth
githubToken: process.env.CI === "true" ? "fake-token-for-e2e-tests" : undefined,
});
Expand Down
Loading
Loading