MCP stdio servers are spawned with cwd=/ and no `roots` capability, breaking workspace-aware servers

Problem

Qoder spawns every stdio MCP server with cwd=/ and does not advertise the MCP roots capability. As a result, any MCP server that locates its project by walking up from the current working directory silently fails to resolve a project — it starts, registers its tools, answers queries, but its project-scoped background features never initialize.

Verified on macOS (Qoder 1.106.3, arm64). Every child process of Qoder inherits cwd=/ from launchd, and no layer ever calls chdir:

Qoder Electron main   (31585)  cwd=/
Qoder native backend  (31593)  cwd=/
├─ npm exec mcp-remote coop     cwd=/
├─ npm exec @playwright/mcp     cwd=/
├─ npm exec mcp-remote aone-km  cwd=/
├─ npm exec mcp-remote sls-mcp  cwd=/
├─ npm exec mcp-remote code     cwd=/
└─ codegraph serve --mcp        cwd=/   (PWD=/ confirmed in child env)

This goes unnoticed for remote-proxy servers (mcp-remote) and browser drivers (playwright) because they don’t care about the working directory. It breaks local indexing servers.

Concrete case — CodeGraph (v1.5.0), a local code-graph MCP server. Its engine resolves the project via findNearestCodeGraphRoot(process.cwd()). With cwd=/ this returns null, so the engine’s project handle stays null and startWatching() returns on its first guard. The file watcher never starts, and the index silently goes stale. Tool queries still succeed (each call carries an explicit projectPath), so there is no visible error — the agent just keeps reading an outdated code graph. Confirmed via lsof: the process holds zero registered kqueue/FSEvents watches, and modifying an indexed source file produces no sync.

Two related gaps make this impossible to work around from user config:

  1. cwd in ~/.qoder/mcp.json is silently dropped. The config-to-runtime mapping only forwards command, args, and env, so adding a cwd field has no effect.
  2. Because roots is not advertised, servers cannot fall back to the protocol-level mechanism for discovering the workspace.

The only working workaround today is hardcoding an absolute --path into args, which breaks as soon as the same MCP server is used across multiple projects.

Solution

Any one of these fixes the issue; ideally the first two:

  1. Set the working directory of spawned stdio MCP servers to the active workspace folder instead of inheriting /. This is what VS Code’s native MCP implementation does, and the stdio launch structure already present in the Qoder bundle ({type, cwd, command, args, env, envFile}) has the field for it.
  2. Advertise the MCP roots capability during initialize and respond to roots/list. This is the protocol-standard way for a server to discover the workspace and fixes the whole class of workspace-dependent MCP servers at once, not just this one. Many servers already implement the client-side branch for it — CodeGraph, for example, only falls back to process.cwd() when the client does not support roots.
  3. Honor the cwd field in mcp.json, with ${workspaceFolder} variable expansion, so users can override per project when needed.

Use Case

Any locally-indexing MCP server used across multiple repositories: code knowledge graphs (CodeGraph, GitNexus, code-review-graph), local search/index servers, language-server-backed tools, and repo-scoped linters. These all determine “which project am I serving” from the working directory or from roots.

The multi-project aspect matters: a global mcp.json entry is shared by every workspace, so a hardcoded --path is not a viable substitute. Users switching between repositories in Qoder need the server to resolve the project dynamically.

Priority

:yellow_circle: Medium - Important improvement

Not blocking Qoder itself, but the failure mode is silent: affected servers report healthy, answer queries, and return stale results. Users get subtly wrong context with no error surfaced, which is worse than a hard failure. Bumping to High would be reasonable given the silent-staleness aspect.

Additional Info

Environment: Qoder 1.106.3 (commit 74db3afcdca97fe9616835c523658a89076a0a96, 2026-08-06) · macOS 26.5.2 · arm64 · CodeGraph 1.5.0

Reproduction:

  1. Add a local MCP server to ~/.qoder/mcp.json, e.g. {“codegraph”: {“command”: “codegraph”, “args”: [“serve”, “–mcp”]}}
  2. Open an indexed project in Qoder and trigger any tool call from that server
  3. Inspect the spawned process:
PID=$(pgrep -f "codegraph.js serve --mcp" | head -1)
lsof -a -p $PID -d cwd          # → cwd = /   (expected: the workspace folder)
lsof -p $PID | grep KQUEUE      # → count=0  (no file watches registered)
  1. Modify any indexed source file, wait, then run codegraph status — the index still reports up to date, and codegraph sync finds nothing until run manually from a shell with the correct cwd.

Adding “cwd”: “${workspaceFolder}” to the server entry and restarting Qoder does not change the result, confirming the field is not read.