${CLAUDE_PROJECT_DIR} not substituted in .mcp.json env for some MCP server launches (literal token reaches server)
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.dbis 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.jsonenvis exposed, not just this plugin.
Evidence / repro status (honest)
- Confirmed artifact: a
${CLAUDE_PROJECT_DIR}/.kg-data/kg.dbdirectory at a project root. The only source of that exact string is the MCPenvabove, 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.jsonheaders not applied (CLOSED; Windows/WSL; HTTP transport). Same class of.mcp.jsonsubstitution gap, but a different field (envvsheaders), 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-kgv0.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?
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗