What
Add an optional cwd?: string field to McpServerConfig and pass it
through to spawn in connectStdio, resolved against the agent's
working directory.
Why
Today connectStdio (src/mcp/client.ts:182-186) calls spawn(command, args, { env, stdio }) with no cwd. The child therefore inherits
process.cwd(), which is wrong for any MCP server that needs to operate
on a sub-project of a monorepo: users have to hardcode absolute paths
into args, which then breaks the moment the host CLI is invoked from
a different directory. Carryover from the 2026-06-13 brief; the upstream
change is anomalyco/opencode#30676 (commit 7e7ad377, 2026-06-11, +46 -11).
NOTE: this is NOT a duplicate of closed #314, which only added the
ALEXI_PROJECT_DIR env var. That env var lets the child know the
project dir; this issue makes the child actually run from it (or any
declared sub-directory).
How
- Edit
src/mcp/config.ts:
- Extend
McpServerConfig (line 10 region) with:
/**
* Working directory for the spawned stdio server. Relative paths
* are resolved against `options.workdir` (or `process.cwd()` if no
* workdir is supplied to `connect()`). Ignored for non-stdio
* transports.
*/
cwd?: string;
- Edit
src/mcp/client.ts:
- In
connectStdio (around line 161-186), before the spawn call,
compute:
const baseDir = options?.workdir ?? process.cwd();
const resolvedCwd = config.cwd
? path.isAbsolute(config.cwd)
? config.cwd
: path.resolve(baseDir, config.cwd)
: undefined;
- Pass it through:
spawn(config.command, config.args || [], { env, stdio: ['pipe', 'pipe', 'pipe'], cwd: resolvedCwd }).
- When
resolvedCwd is undefined, do NOT pass the field (preserves
today's behaviour for unconfigured servers).
- Add an import for
path at the top of client.ts if not already
present.
- Tests in
src/mcp/__tests__/client.test.ts (or tests/mcp/):
- Mock
child_process.spawn (or the existing wrapper used in the
suite) and assert the cwd option received.
- Case A:
config.cwd absolute — passed verbatim.
- Case B:
config.cwd relative + options.workdir provided —
resolved against options.workdir.
- Case C:
config.cwd relative + options.workdir omitted — resolved
against process.cwd().
- Case D:
config.cwd undefined — cwd key absent from the spawn
options object (so we do not regress today's inherit-cwd behaviour).
- Update
routing-config.example.json ONLY if it contains an MCP
stdio server example that should now demonstrate cwd. If it does
not, skip — do not introduce one in this PR.
Files to modify
src/mcp/config.ts — add cwd?: string to McpServerConfig.
src/mcp/client.ts — resolve and pass cwd to spawn; add path
import if missing.
src/mcp/__tests__/client.test.ts (or tests/mcp/client.test.ts) —
four new test cases.
Done when
Reference
What
Add an optional
cwd?: stringfield toMcpServerConfigand pass itthrough to
spawninconnectStdio, resolved against the agent'sworking directory.
Why
Today
connectStdio(src/mcp/client.ts:182-186) callsspawn(command, args, { env, stdio })with nocwd. The child therefore inheritsprocess.cwd(), which is wrong for any MCP server that needs to operateon a sub-project of a monorepo: users have to hardcode absolute paths
into
args, which then breaks the moment the host CLI is invoked froma different directory. Carryover from the 2026-06-13 brief; the upstream
change is anomalyco/opencode#30676 (commit
7e7ad377, 2026-06-11, +46 -11).NOTE: this is NOT a duplicate of closed #314, which only added the
ALEXI_PROJECT_DIRenv var. That env var lets the child know theproject dir; this issue makes the child actually run from it (or any
declared sub-directory).
How
src/mcp/config.ts:McpServerConfig(line 10 region) with:src/mcp/client.ts:connectStdio(around line 161-186), before thespawncall,compute:
spawn(config.command, config.args || [], { env, stdio: ['pipe', 'pipe', 'pipe'], cwd: resolvedCwd }).resolvedCwdis undefined, do NOT pass the field (preservestoday's behaviour for unconfigured servers).
pathat the top ofclient.tsif not alreadypresent.
src/mcp/__tests__/client.test.ts(ortests/mcp/):child_process.spawn(or the existing wrapper used in thesuite) and assert the
cwdoption received.config.cwdabsolute — passed verbatim.config.cwdrelative +options.workdirprovided —resolved against
options.workdir.config.cwdrelative +options.workdiromitted — resolvedagainst
process.cwd().config.cwdundefined —cwdkey absent from the spawnoptions object (so we do not regress today's inherit-cwd behaviour).
routing-config.example.jsonONLY if it contains an MCPstdio server example that should now demonstrate
cwd. If it doesnot, skip — do not introduce one in this PR.
Files to modify
src/mcp/config.ts— addcwd?: stringtoMcpServerConfig.src/mcp/client.ts— resolve and passcwdtospawn; addpathimport if missing.
src/mcp/__tests__/client.test.ts(ortests/mcp/client.test.ts) —four new test cases.
Done when
McpServerConfig.cwd?: stringexists and is documented.spawninconnectStdioreceives the resolvedcwdwhenconfigured, and omits it when not configured.
npm test,npm run lint,npm run typecheck,npm run format:check,npm run buildare green.process.envinjection (already covered by [mcp] Pass ALEXI_PROJECT_DIR to MCP stdio server child processes #314).Reference
.github/research/2026-06-14-research.mditem feat(cli): add Homebrew tap support #2(carryover from 06-13).
7e7ad377.rootscapability issue (both editsrc/mcp/client.ts); fine to land in one PR.