Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/services/mcp/McpHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ export class McpHub {
const command = isWindows && !isAlreadyWrapped ? "cmd.exe" : configInjected.command
const args =
isWindows && !isAlreadyWrapped
? ["/c", configInjected.command, ...(configInjected.args || [])]
? ["/c", `"${configInjected.command}"`, ...(configInjected.args || [])]
: configInjected.args

transport = new StdioClientTransport({
Expand Down
64 changes: 62 additions & 2 deletions src/services/mcp/__tests__/McpHub.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ describe("McpHub", () => {
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: "cmd.exe",
args: ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/test/path"],
args: ["/c", '"npx"', "-y", "@modelcontextprotocol/server-filesystem", "/test/path"],
}),
)
})
Expand Down Expand Up @@ -975,7 +975,7 @@ describe("McpHub", () => {
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: "cmd.exe",
args: ["/c", "npx", "-y", "@modelcontextprotocol/server-example"],
args: ["/c", '"npx"', "-y", "@modelcontextprotocol/server-example"],
env: expect.objectContaining({
FNM_DIR: "C:\\Users\\test\\.fnm",
FNM_NODE_DIST_MIRROR: "https://nodejs.org/dist",
Expand Down Expand Up @@ -1046,5 +1046,65 @@ describe("McpHub", () => {
}),
)
})

it("should properly quote commands with special characters like ampersand", async () => {
// Mock Windows platform
Object.defineProperty(process, "platform", {
value: "win32",
writable: true,
enumerable: true,
configurable: true,
})

// Mock StdioClientTransport
const mockTransport = {
start: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
stderr: {
on: vi.fn(),
},
onerror: null,
onclose: null,
}

StdioClientTransport.mockImplementation((config: any) => {
// Store the config for verification
return mockTransport
})

// Mock Client
Client.mockImplementation(() => ({
connect: vi.fn().mockResolvedValue(undefined),
close: vi.fn().mockResolvedValue(undefined),
getInstructions: vi.fn().mockReturnValue("test instructions"),
request: vi.fn().mockResolvedValue({ tools: [], resources: [], resourceTemplates: [] }),
}))

// Create a new McpHub instance
const mcpHub = new McpHub(mockProvider as ClineProvider)

// Mock file system operations
vi.mocked(fs.readFile).mockResolvedValue(
JSON.stringify({
mcpServers: {
"test-server": {
command: "c:\\path with &\\mymcp\\mcp.exe",
args: ["--verbose"],
},
},
}),
)

// Initialize servers (this will trigger connectToServer)
await mcpHub["initializeGlobalMcpServers"]()

// Verify StdioClientTransport was called with properly quoted command
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: "cmd.exe",
args: ["/c", '"c:\\path with &\\mymcp\\mcp.exe"', "--verbose"],
}),
)
})
})
})