From bc3b9f34feadd5e933bef80eb8c38d028718c402 Mon Sep 17 00:00:00 2001 From: Mark Shust Date: Fri, 26 Jun 2026 13:08:01 -0400 Subject: [PATCH] docs: correct multi-instance MCP guidance to lead with per-project scoping The troubleshooting section previously recommended a CLAUDE_CONFIG_DIR per-project wrapper as the fix for MCP servers disconnecting under concurrent Claude Code instances. That wrapper has a significant undocumented downside: Claude Code ties its login to the default config directory (the macOS Keychain credential is not shared across CLAUDE_CONFIG_DIR values), so every isolated config requires its own /login with no clean way to share a session. Rewrite the section to lead with the actually-correct practice: - Scope every MCP server to the project that needs it. marko-mcp/lsp/skills are enabled per project by devai:install via the project's .claude/settings.json; do not also enable them globally (that launches and fails marko-mcp in every non-Marko project). Other project-specific servers belong in a project-root .mcp.json. - Reduce write churn: fewer concurrent instances, CLAUDE_CODE_SKIP_PROMPT_HISTORY=1. - Demote CLAUDE_CONFIG_DIR isolation to an "advanced, rarely worth it" note that discloses the per-project /login requirement. Heading (and its anchor) is unchanged, so the devai:install tip link still resolves. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../troubleshooting.md | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/docs-markdown/docs/ai-assisted-development/troubleshooting.md b/packages/docs-markdown/docs/ai-assisted-development/troubleshooting.md index 1e28362d..f1fa75c3 100644 --- a/packages/docs-markdown/docs/ai-assisted-development/troubleshooting.md +++ b/packages/docs-markdown/docs/ai-assisted-development/troubleshooting.md @@ -100,34 +100,38 @@ The `query_database` tool is only registered when `marko/database` is bound in t ### Multiple Claude Code instances disconnect MCP servers -If you run several Claude Code instances at once (multiple terminals or windows) and notice MCP servers — `marko-mcp` included — repeatedly disconnecting and reconnecting, the cause is **not** Marko. Every Claude Code instance shares a single global `~/.claude.json`, and Claude Code rewrites that file constantly (history, tool-usage counters, session state). Concurrent writes to the one file make Claude Code tear down and reconnect its **entire** MCP fleet in lockstep, so all servers flap together. `marko-mcp` is often the one you notice because it boots a PHP process and is the slowest to re-handshake after each bounce. +If you run several Claude Code instances at once (multiple terminals or windows) and notice MCP servers — `marko-mcp` included — repeatedly disconnecting and reconnecting, the cause is **not** Marko. Every Claude Code instance shares a single global `~/.claude.json`, and Claude Code rewrites that file constantly (history, tool-usage counters, session state). Concurrent writes to the one file make Claude Code tear down and reconnect its **entire** MCP fleet in lockstep, so all servers flap together (a known Claude Code issue — see [anthropics/claude-code#25768](https://github.com/anthropics/claude-code/issues/25768), [#28829](https://github.com/anthropics/claude-code/issues/28829)). `marko-mcp` is often the one you notice because it boots a PHP process and is the slowest to re-handshake after each bounce. -This is a known Claude Code issue (see [anthropics/claude-code#25768](https://github.com/anthropics/claude-code/issues/25768), [#28829](https://github.com/anthropics/claude-code/issues/28829)), independent of Marko. There is no Marko setting that fixes it — the reliable workaround is to give each project its own Claude Code config via the `CLAUDE_CONFIG_DIR` environment variable so concurrent instances stop contending on one file. +You cannot make Marko stop this — it is Claude Code behavior — but you can shrink the blast radius so a reload barely matters, and avoid making it worse. -Add this `claude` wrapper to your shell profile (`~/.zshrc` shown; adapt for bash). It gives each project its own isolated `.claude.json` under `~/.claude-profiles//` while sharing plugins, skills, commands, hooks, and settings via symlinks. Credentials live in the macOS Keychain and are shared automatically — no re-login per project. +**1. Scope every MCP server to the project that needs it (the important one).** -```bash -# Per-project Claude Code config profiles — stops concurrent instances from -# contending on a single ~/.claude.json (which causes MCP servers to flap). -claude() { - emulate -L zsh - local src="$HOME/.claude" globalcfg="$HOME/.claude.json" root profile item - root=$(git rev-parse --show-toplevel 2>/dev/null) || root="$PWD" - profile="$HOME/.claude-profiles/${root:t}" - mkdir -p "$profile" - for item in plugins skills commands hooks settings.json settings.local.json statusline-context.sh config ide; do - [[ -e "$src/$item" && ! -e "$profile/$item" ]] && ln -s "$src/$item" "$profile/$item" - done - # Seed the isolated config once from the real global ~/.claude.json so global - # MCP servers and plugin enablement carry over into the profile. - [[ ! -f "$profile/.claude.json" && -f "$globalcfg" ]] && cp "$globalcfg" "$profile/.claude.json" - CLAUDE_CONFIG_DIR="$profile" command claude "$@" -} -``` +The worst amplifier is loading MCP servers into *every* project. A server enabled globally is started — and bounced — in all your projects, including ones that can't even use it (where it just shows "✘ Failed to connect"). Keep each server scoped to where it belongs: + +- **`marko-mcp` (and `marko-lsp`, `marko-skills`):** these are enabled **per project** by `marko devai:install`, which writes the marketplace registration and `enabledPlugins` into the project's `.claude/settings.json`. Do **not** also enable them in your global `~/.claude/settings.json` — that makes marko-mcp launch (and fail) in every non-Marko project. If an older global enablement is lurking, remove the `marko-*@marko` entries from `~/.claude/settings.json`; each Marko project still loads them from its own committed `.claude/settings.json`. +- **Other project-specific servers** (a database tool, an internal API, an n8n instance, …): add them to a `.mcp.json` at that project's root so they load only there: + + ```json + { + "mcpServers": { + "n8n-mcp": { + "type": "stdio", + "command": "npx", + "args": ["-y", "n8n-mcp"], + "env": { "N8N_API_URL": "https://n8n.example.com", "N8N_API_KEY": "…" } + } + } + } + ``` + + Reserve **user (global) scope** (`claude mcp add --scope user`) for tools you genuinely want everywhere. + +**2. Reduce the write churn / contention.** -Open a new terminal (or `source ~/.zshrc`) and confirm isolation with `claude mcp list` from inside a project — your MCP servers should connect, and `~/.claude.json` should no longer be touched by that instance. +- Run fewer concurrent Claude Code instances against the same global config when you can. +- `export CLAUDE_CODE_SKIP_PROMPT_HISTORY=1` cuts how often `~/.claude.json` is rewritten, which lowers the contention that triggers fleet reloads. Zero downside for most workflows. -> Note: `CLAUDE_CONFIG_DIR` is honored by the Claude Code CLI but ignored by the VS Code extension, which always uses `~/.claude/`. +**Advanced (rarely worth it): full config isolation.** You can give each project its own `~/.claude.json` via the `CLAUDE_CONFIG_DIR` environment variable, which eliminates the contention entirely. **The catch:** Claude Code stores its login in a way tied to the default config directory (the macOS Keychain item is not shared across `CLAUDE_CONFIG_DIR` values), so each isolated config requires its own `/login` — there is no clean way to share one session across them. Because of that re-login friction, prefer scoping (step 1) and churn reduction (step 2); only reach for `CLAUDE_CONFIG_DIR` isolation if fleet reloads are genuinely disrupting you and you accept logging in per project. ## LSP problems