${CLAUDE_PROJECT_DIR} not substituted in .mcp.json env for some MCP server launches (literal token reaches server)

Open 💬 1 comment Opened Jun 17, 2026 by stonematt

What happens

A plugin-provided stdio MCP server (obra-kg knowledge-graph) declares in its .mcp.json:

"env": {
  "KG_VAULT_PATH": "${CLAUDE_PROJECT_DIR}",
  "KG_DATA_DIR":   "${CLAUDE_PROJECT_DIR}/.kg-data"
}

In normal interactive project sessions Claude Code expands the token correctly — the server writes to <project>/.kg-data/. But at least once the server received the value unexpanded: the literal string ${CLAUDE_PROJECT_DIR}/.kg-data reached process.env.KG_DATA_DIR, and the server did mkdirSync(value, {recursive:true}), creating a directory literally named ${CLAUDE_PROJECT_DIR} at the project root, containing a stray kg.db.

Why it matters

  • Silent. A ${CLAUDE_PROJECT_DIR}/ junk dir appears at the project root, persists across sessions, and accumulates per-project (we found one that sat ~5 weeks).
  • Confusing. The stray kg.db is not the live graph (the real one is at <project>/.kg-data/), which misleads debugging.
  • General. Any MCP server using ${CLAUDE_PROJECT_DIR} or ${ENV} in .mcp.json env is exposed, not just this plugin.

Evidence / repro status (honest)

  • Confirmed artifact: a ${CLAUDE_PROJECT_DIR}/.kg-data/kg.db directory at a project root. The only source of that exact string is the MCP env above, so an unexpanded value definitely reached the server's environment.
  • Not deterministically reproduced. Circumstantial: the junk dir's mtime coincides with a git-worktree agent session (EnterWorktree / subagent worktree isolation), suggesting expansion may be skipped when the MCP server is launched in a worktree/subagent context rather than a top-level interactive session.
  • Related prior art (not a duplicate): #6204 — ${VAR} substitution in .mcp.json headers not applied (CLOSED; Windows/WSL; HTTP transport). Same class of .mcp.json substitution gap, but a different field (env vs headers), transport (stdio vs HTTP), and platform — so this is a distinct, still-open case on macOS/stdio.

Expected

${CLAUDE_PROJECT_DIR} (and ${ENV}) in .mcp.json env values should expand consistently across all MCP launch contexts — interactive, subagent, worktree, headless/-p. If expansion is intentionally unsupported in some context, CC should pass an empty value or error rather than forwarding the literal ${...} token (which downstream code will treat as a real path).

Environment

  • Claude Code: 2.1.178
  • Platform: macOS
  • MCP server: knowledge-graph@obra-kg v0.1.0, stdio, plugin-provided

Question for maintainers

Is ${CLAUDE_PROJECT_DIR} expansion in .mcp.json env supported when the MCP server is launched inside a worktree or subagent? If not, is forwarding the literal token (vs empty/error) intended?

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗