Skip to content
Open
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
17 changes: 12 additions & 5 deletions core/context/mcp/MCPConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,23 +414,30 @@ Org-level secrets can only be used for MCP by Background Agents (https://docs.co
/**
* Resolves the command and arguments for the current platform
* On Windows, batch script commands need to be executed via cmd.exe
* UNLESS we're connected to a WSL remote (where Linux commands should run)
* @param originalCommand The original command
* @param originalArgs The original command arguments
* @returns An object with the resolved command and arguments
*/
private resolveCommandForPlatform(
private async resolveCommandForPlatform(
originalCommand: string,
originalArgs: string[],
): { command: string; args: string[] } {
// If not on Windows or not a batch command, return as-is
): Promise<{ command: string; args: string[] }> {
// Check if we're on Windows host connected to WSL remote
const ideInfo = await this.extras?.ide?.getIdeInfo();
const isWindowsHostWithWslRemote =
process.platform === "win32" && ideInfo?.remoteName === "wsl";

// If not on Windows, or connected to WSL, or not a batch command, return as-is
if (
process.platform !== "win32" ||
isWindowsHostWithWslRemote ||
!WINDOWS_BATCH_COMMANDS.includes(originalCommand)
) {
return { command: originalCommand, args: originalArgs };
}

// On Windows, we need to execute batch commands via cmd.exe
// On Windows (local), we need to execute batch commands via cmd.exe
// Format: cmd.exe /c command [args]
return {
command: "cmd.exe",
Expand Down Expand Up @@ -569,7 +576,7 @@ Org-level secrets can only be used for MCP by Background Agents (https://docs.co
}
}

const { command, args } = this.resolveCommandForPlatform(
const { command, args } = await this.resolveCommandForPlatform(
options.command,
options.args || [],
);
Expand Down
97 changes: 97 additions & 0 deletions core/context/mcp/MCPConnection.vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,101 @@ describe("MCPConnection", () => {
}
});
});

describe("resolveCommandForPlatform", () => {
const baseOptions: InternalStdioMcpOptions = {
name: "test-mcp",
id: "test-id",
type: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
};

it("should NOT wrap with cmd.exe when Windows host connects to WSL remote", async () => {
const mockIde = {
getIdeInfo: vi.fn().mockResolvedValue({ remoteName: "wsl" }),
} as any;

const conn = new MCPConnection(baseOptions, { ide: mockIde });

// Mock process.platform to be win32
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32" });

try {
const result = await (conn as any).resolveCommandForPlatform("npx", [
"-y",
"test",
]);
// Should NOT wrap with cmd.exe when in WSL
expect(result.command).toBe("npx");
expect(result.args).toEqual(["-y", "test"]);
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform });
}
});

it("should wrap with cmd.exe when Windows host is local (not WSL)", async () => {
const mockIde = {
getIdeInfo: vi.fn().mockResolvedValue({ remoteName: "" }),
} as any;

const conn = new MCPConnection(baseOptions, { ide: mockIde });

const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32" });

try {
const result = await (conn as any).resolveCommandForPlatform("npx", [
"-y",
"test",
]);
// Should wrap with cmd.exe for local Windows
expect(result.command).toBe("cmd.exe");
expect(result.args).toEqual(["/c", "npx", "-y", "test"]);
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform });
}
});

it("should NOT wrap with cmd.exe on Linux regardless of batch command", async () => {
const conn = new MCPConnection(baseOptions);

const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "linux" });

try {
const result = await (conn as any).resolveCommandForPlatform("npx", [
"-y",
"test",
]);
expect(result.command).toBe("npx");
expect(result.args).toEqual(["-y", "test"]);
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform });
}
});

it("should NOT wrap non-batch commands even on Windows", async () => {
const mockIde = {
getIdeInfo: vi.fn().mockResolvedValue({ remoteName: "" }),
} as any;

const conn = new MCPConnection(baseOptions, { ide: mockIde });

const originalPlatform = process.platform;
Object.defineProperty(process, "platform", { value: "win32" });

try {
const result = await (conn as any).resolveCommandForPlatform("python", [
"script.py",
]);
// python is not in WINDOWS_BATCH_COMMANDS, so no wrapping
expect(result.command).toBe("python");
expect(result.args).toEqual(["script.py"]);
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform });
}
});
});
});
Loading