From 79d74977d3854e2ca7cbdc9b898adf138330c830 Mon Sep 17 00:00:00 2001 From: Brian 'bdougie' Douglas Date: Tue, 24 Jun 2025 11:28:10 -0700 Subject: [PATCH 1/4] adds cwd to the MCP yaml as an option. --- core/config/types.ts | 2 + core/config/yaml/loadYaml.cwd.vitest.ts | 125 ++++++++++++++++++++++ core/config/yaml/loadYaml.ts | 1 + core/context/mcp/MCPConnection.test.ts | 19 ++++ core/context/mcp/MCPConnection.ts | 1 + core/index.d.ts | 1 + packages/config-yaml/src/schemas/index.ts | 1 + 7 files changed, 150 insertions(+) create mode 100644 core/config/yaml/loadYaml.cwd.vitest.ts diff --git a/core/config/types.ts b/core/config/types.ts index df29d6071c..406f40dddb 100644 --- a/core/config/types.ts +++ b/core/config/types.ts @@ -1004,6 +1004,8 @@ declare global { type: "stdio"; command: string; args: string[]; + env?: Record; + cwd?: string; } interface WebSocketOptions { diff --git a/core/config/yaml/loadYaml.cwd.vitest.ts b/core/config/yaml/loadYaml.cwd.vitest.ts new file mode 100644 index 0000000000..2b7e3ee612 --- /dev/null +++ b/core/config/yaml/loadYaml.cwd.vitest.ts @@ -0,0 +1,125 @@ +import { describe, it, expect } from "vitest"; +import { validateConfigYaml, AssistantUnrolled } from "@continuedev/config-yaml"; + +describe("MCP Server cwd configuration", () => { + describe("YAML schema validation", () => { + it("should accept valid MCP server with cwd", () => { + const config: AssistantUnrolled = { + name: "test-assistant", + version: "1.0.0", + mcpServers: [ + { + name: "test-server", + command: "node", + args: ["server.js"], + env: { NODE_ENV: "production" }, + cwd: "/path/to/project", + connectionTimeout: 5000, + }, + ], + }; + + const errors = validateConfigYaml(config); + expect(errors).toHaveLength(0); + }); + + it("should accept MCP server without cwd", () => { + const config: AssistantUnrolled = { + name: "test-assistant", + version: "1.0.0", + mcpServers: [ + { + name: "test-server", + command: "python", + args: ["-m", "server"], + }, + ], + }; + + const errors = validateConfigYaml(config); + expect(errors).toHaveLength(0); + }); + + it("should accept relative paths in cwd", () => { + const config: AssistantUnrolled = { + name: "test-assistant", + version: "1.0.0", + mcpServers: [ + { + name: "test-server", + command: "cargo", + args: ["run"], + cwd: "./rust-project", + }, + ], + }; + + const errors = validateConfigYaml(config); + expect(errors).toHaveLength(0); + }); + + it("should accept empty string cwd", () => { + const config: AssistantUnrolled = { + name: "test-assistant", + version: "1.0.0", + mcpServers: [ + { + name: "test-server", + command: "deno", + args: ["run", "server.ts"], + cwd: "", + }, + ], + }; + + const errors = validateConfigYaml(config); + expect(errors).toHaveLength(0); + }); + }); + + describe("MCP server configuration examples", () => { + it("should support common MCP server patterns with cwd", () => { + const configs = [ + { + name: "Local project MCP server", + server: { + name: "project-mcp", + command: "npm", + args: ["run", "mcp-server"], + cwd: "/Users/developer/my-project", + }, + }, + { + name: "Python MCP with virtual environment", + server: { + name: "python-mcp", + command: "python", + args: ["-m", "my_mcp_server"], + env: { PYTHONPATH: "./src" }, + cwd: "/home/user/python-project", + }, + }, + { + name: "Relative path MCP server", + server: { + name: "relative-mcp", + command: "node", + args: ["index.js"], + cwd: "../mcp-servers/filesystem", + }, + }, + ]; + + configs.forEach(({ name, server }) => { + const config: AssistantUnrolled = { + name: "test-assistant", + version: "1.0.0", + mcpServers: [server], + }; + + const errors = validateConfigYaml(config); + expect(errors).toHaveLength(0); + }); + }); + }); +}); \ No newline at end of file diff --git a/core/config/yaml/loadYaml.ts b/core/config/yaml/loadYaml.ts index 0c6436c386..3467c15ddb 100644 --- a/core/config/yaml/loadYaml.ts +++ b/core/config/yaml/loadYaml.ts @@ -70,6 +70,7 @@ function convertYamlMcpToContinueMcp( command: server.command, args: server.args ?? [], env: server.env, + cwd: server.cwd, } as any, // TODO: Fix the mcpServers types in config-yaml (discriminated union) timeout: server.connectionTimeout, }; diff --git a/core/context/mcp/MCPConnection.test.ts b/core/context/mcp/MCPConnection.test.ts index f8f146f079..84fb1a7038 100644 --- a/core/context/mcp/MCPConnection.test.ts +++ b/core/context/mcp/MCPConnection.test.ts @@ -25,6 +25,25 @@ describe("MCPConnection", () => { expect(conn.status).toBe("not-connected"); }); + it("should create instance with stdio transport including cwd", () => { + const options = { + name: "test-mcp", + id: "test-id", + transport: { + type: "stdio" as const, + command: "test-cmd", + args: ["--test"], + env: { TEST: "true" }, + cwd: "/path/to/working/directory", + }, + }; + + const conn = new MCPConnection(options); + expect(conn).toBeInstanceOf(MCPConnection); + expect(conn.status).toBe("not-connected"); + expect(conn.options.transport.cwd).toBe("/path/to/working/directory"); + }); + it("should create instance with websocket transport", () => { const options = { name: "test-mcp", diff --git a/core/context/mcp/MCPConnection.ts b/core/context/mcp/MCPConnection.ts index c66a93ed33..af971d8137 100644 --- a/core/context/mcp/MCPConnection.ts +++ b/core/context/mcp/MCPConnection.ts @@ -329,6 +329,7 @@ class MCPConnection { command, args, env, + cwd: options.transport.cwd, stderr: "pipe", }); diff --git a/core/index.d.ts b/core/index.d.ts index 9b54701fff..ba3f847675 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -1129,6 +1129,7 @@ export interface StdioOptions { command: string; args: string[]; env?: Record; + cwd?: string; requestOptions?: RequestOptions; } diff --git a/packages/config-yaml/src/schemas/index.ts b/packages/config-yaml/src/schemas/index.ts index 917cb9866e..b3d7782f4a 100644 --- a/packages/config-yaml/src/schemas/index.ts +++ b/packages/config-yaml/src/schemas/index.ts @@ -22,6 +22,7 @@ const mcpServerSchema = z.object({ faviconUrl: z.string().optional(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), + cwd: z.string().optional(), connectionTimeout: z.number().gt(0).optional(), requestOptions: requestOptionsSchema.optional(), }); From a2b62aafc6f66bd003ea1f986f115bead413efc3 Mon Sep 17 00:00:00 2001 From: Brian 'bdougie' Douglas Date: Tue, 24 Jun 2025 12:05:56 -0700 Subject: [PATCH 2/4] update the docs --- docs/docs/customize/deep-dives/mcp.mdx | 15 +++++++++++++-- docs/docs/reference.md | 6 +++++- docs/docs/yaml-migration.md | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/docs/customize/deep-dives/mcp.mdx b/docs/docs/customize/deep-dives/mcp.mdx index 9899518e78..589b04798c 100644 --- a/docs/docs/customize/deep-dives/mcp.mdx +++ b/docs/docs/customize/deep-dives/mcp.mdx @@ -110,6 +110,7 @@ MCP blocks follow the established syntax for blocks, with a few additional prope - `type` (optional): The type of the MCP server: `sse`, `stdio`, `streamable-http` - `args` (optional): Arguments to pass to the command. - `env` (optional): Secrets to be injected into the command as environment variables. +- `cwd` (optional): Working directory to run the command in. Can be absolute or relative path. ### Transport Types @@ -132,12 +133,22 @@ For local MCP servers that communicate via standard input and output: ```yaml mcpServers: - - name: Name + - name: SQLite Server type: stdio command: npx args: - "@modelcontextprotocol/server-sqlite" - - "/path/to/your/database.db" + - "./database.db" + cwd: "/path/to/project" + + - name: Local Project Server + command: npm + args: + - "run" + - "mcp-server" + cwd: "./my-project" + env: + NODE_ENV: "production" ``` #### Streamable HTTP Transport diff --git a/docs/docs/reference.md b/docs/docs/reference.md index 9ff1077f95..25328e80dc 100644 --- a/docs/docs/reference.md +++ b/docs/docs/reference.md @@ -384,6 +384,7 @@ prompts, context, and tool use. Continue supports any MCP server with the MCP co - `command` (**required**): The command used to start the server. - `args`: An optional array of arguments for the command. - `env`: An optional map of environment variables for the server process. +- `cwd`: An optional working directory to run the command in. Can be absolute or relative path. - `connectionTimeout`: An optional connection timeout number to the server in milliseconds. **Example:** @@ -395,7 +396,10 @@ mcpServers: args: - mcp-server-sqlite - --db-path - - /Users/NAME/test.db + - ./test.db + cwd: /Users/NAME/project + env: + NODE_ENV: production ``` ### `data` diff --git a/docs/docs/yaml-migration.md b/docs/docs/yaml-migration.md index c07f99c181..1b65aaecc6 100644 --- a/docs/docs/yaml-migration.md +++ b/docs/docs/yaml-migration.md @@ -288,6 +288,7 @@ docs: - `command` (**required**): The command used to start the server. - `args`: An optional array of arguments for the command. - `env`: An optional map of environment variables for the server process. +- `cwd`: An optional working directory to run the command in. Can be absolute or relative path. **Before** From 6e72c496c73e663e8543f4791be9253f00211ea3 Mon Sep 17 00:00:00 2001 From: Brian 'bdougie' Douglas Date: Tue, 24 Jun 2025 12:08:54 -0700 Subject: [PATCH 3/4] npm run format --- core/config/yaml/loadYaml.cwd.vitest.ts | 17 ++++++++++------- core/context/mcp/MCPConnection.test.ts | 5 ++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/config/yaml/loadYaml.cwd.vitest.ts b/core/config/yaml/loadYaml.cwd.vitest.ts index 2b7e3ee612..fd6769f709 100644 --- a/core/config/yaml/loadYaml.cwd.vitest.ts +++ b/core/config/yaml/loadYaml.cwd.vitest.ts @@ -1,10 +1,13 @@ import { describe, it, expect } from "vitest"; -import { validateConfigYaml, AssistantUnrolled } from "@continuedev/config-yaml"; +import { + validateConfigYaml, + AssistantUnrolledNonNullable, +} from "@continuedev/config-yaml"; describe("MCP Server cwd configuration", () => { describe("YAML schema validation", () => { it("should accept valid MCP server with cwd", () => { - const config: AssistantUnrolled = { + const config: AssistantUnrolledNonNullable = { name: "test-assistant", version: "1.0.0", mcpServers: [ @@ -24,7 +27,7 @@ describe("MCP Server cwd configuration", () => { }); it("should accept MCP server without cwd", () => { - const config: AssistantUnrolled = { + const config: AssistantUnrolledNonNullable = { name: "test-assistant", version: "1.0.0", mcpServers: [ @@ -41,7 +44,7 @@ describe("MCP Server cwd configuration", () => { }); it("should accept relative paths in cwd", () => { - const config: AssistantUnrolled = { + const config: AssistantUnrolledNonNullable = { name: "test-assistant", version: "1.0.0", mcpServers: [ @@ -59,7 +62,7 @@ describe("MCP Server cwd configuration", () => { }); it("should accept empty string cwd", () => { - const config: AssistantUnrolled = { + const config: AssistantUnrolledNonNullable = { name: "test-assistant", version: "1.0.0", mcpServers: [ @@ -111,7 +114,7 @@ describe("MCP Server cwd configuration", () => { ]; configs.forEach(({ name, server }) => { - const config: AssistantUnrolled = { + const config: AssistantUnrolledNonNullable = { name: "test-assistant", version: "1.0.0", mcpServers: [server], @@ -122,4 +125,4 @@ describe("MCP Server cwd configuration", () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/core/context/mcp/MCPConnection.test.ts b/core/context/mcp/MCPConnection.test.ts index 84fb1a7038..fb9def8145 100644 --- a/core/context/mcp/MCPConnection.test.ts +++ b/core/context/mcp/MCPConnection.test.ts @@ -1,6 +1,7 @@ import { jest } from "@jest/globals"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import MCPConnection from "./MCPConnection"; +import { StdioOptions } from "../../index.d.js"; describe("MCPConnection", () => { beforeEach(() => { @@ -41,7 +42,9 @@ describe("MCPConnection", () => { const conn = new MCPConnection(options); expect(conn).toBeInstanceOf(MCPConnection); expect(conn.status).toBe("not-connected"); - expect(conn.options.transport.cwd).toBe("/path/to/working/directory"); + if (conn.options.transport.type === "stdio") { + expect(conn.options.transport.cwd).toBe("/path/to/working/directory"); + } }); it("should create instance with websocket transport", () => { From e662a66fab07471ea65f54b2b688c72868c84844 Mon Sep 17 00:00:00 2001 From: Brian 'bdougie' Douglas Date: Thu, 26 Jun 2025 05:20:19 -0700 Subject: [PATCH 4/4] rename test --- core/config/yaml/{loadYaml.cwd.vitest.ts => loadYaml.vitest.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/config/yaml/{loadYaml.cwd.vitest.ts => loadYaml.vitest.ts} (100%) diff --git a/core/config/yaml/loadYaml.cwd.vitest.ts b/core/config/yaml/loadYaml.vitest.ts similarity index 100% rename from core/config/yaml/loadYaml.cwd.vitest.ts rename to core/config/yaml/loadYaml.vitest.ts