You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
title: "Cross-Host CWD Sanity Guard for Cursor and Claude Code MCP"
3
+
status: accepted
4
+
tags:
5
+
- "cursor"
6
+
- "claude-code"
7
+
- "multi-host"
8
+
- "plugin"
9
+
---
10
+
11
+
## Idea
12
+
13
+
Extend the existing Codex cache-cwd guard in `bin/archcore` (Step 0b) with a cross-host sanity check (Step 0c) that refuses to start the archcore MCP server when cwd does not look like a user project root, plus ship a `cursor.mcp.json` template and README guidance so users register the server with `cwd: "${workspaceFolder}"` from the start.
14
+
15
+
Three cooperating pieces:
16
+
17
+
1.**`bin/archcore` Step 0c — cross-host sanity guard.** When invoked as `archcore mcp` and the bypass env vars are unset, refuse if cwd is `/`, `$HOME`, `$CLAUDE_PLUGIN_ROOT`, `$CURSOR_PLUGIN_ROOT`, or a directory without any of these project markers: `.git`, `.archcore`, `package.json`, `go.mod`, `pyproject.toml`, `Cargo.toml`, `pom.xml`, `build.gradle`, `build.gradle.kts`. The refusal prints per-host fix instructions (Cursor: add `cwd: "${workspaceFolder}"`; Claude: launch from project dir; Codex: install shell wrapper). $HOME and the plugin-root env vars are compared via `cd "$VAR" && pwd -P` so symlinked install dirs match too.
18
+
19
+
2.**`cursor.mcp.json` template at the plugin root.** Cursor plugin manifests do not register MCP servers — users configure them in `~/.cursor/mcp.json` or `.cursor/mcp.json`. We ship a canonical snippet with `cwd: "${workspaceFolder}"`*and*`env.ARCHCORE_CWD: "${workspaceFolder}"` (belt-and-braces; the launcher honors `ARCHCORE_CWD` in Step 0 even on hosts that ignore `cwd`).
20
+
21
+
3.**`bin/archcore` stderr diagnostic.** On every `archcore mcp` start, one line on stderr: `[archcore mcp] cwd=<X> archcore_dir=<X>/.archcore (exists|missing)`. Surfaces in Cursor's MCP server panel and Claude Code's `/mcp` output, so users can verify which project the server actually attached to.
22
+
23
+
## Value
24
+
25
+
**Before.** A single global `~/.cursor/mcp.json` entry without `cwd` made archcore stick to whichever workspace Cursor opened first, leaking that project's `.archcore/` into every other project the user opened later. Diagnosis time: hours — the MCP responded successfully with plausible-looking but wrong documents. Same failure class hit Claude Code in multi-repo workspaces and when launched not from a project root.
26
+
27
+
**After.** Wrong cwd is converted from a silent successful read of the wrong project into a loud refusal with a per-host one-line fix. The diagnostic line on every start gives users a positive confirmation of which project the server is attached to. The template snippet makes the correct setup the obvious copy-paste.
28
+
29
+
## Possible Implementation
30
+
31
+
Shipped:
32
+
33
+
-`bin/archcore`:
34
+
- Resolves `ARCHCORE_ALLOW_ANY_CWD` (canonical) with `ARCHCORE_ALLOW_PLUGIN_CWD` (back-compat alias) into `_archcore_allow_any_cwd`. Both Step 0b and Step 0c honor it.
35
+
-**Step 0c**: cross-host sanity guard with five refuse conditions (filesystem root, `$HOME`, `$CLAUDE_PLUGIN_ROOT`, `$CURSOR_PLUGIN_ROOT`, no project markers). Refusal message lists the per-host fix and the escape hatch. Step 0b (cache-cwd refusal) remains the first line of defense for Codex and prints the wrapper recipe inline.
36
+
-**Diagnostic**: one stderr line per `archcore mcp` start with the resolved cwd and `.archcore/` presence.
37
+
-`cursor.mcp.json` at the plugin root: snippet that users copy into their `.cursor/mcp.json` (or `~/.cursor/mcp.json` if pinning with `cwd: "${workspaceFolder}"`).
38
+
-`README.md`: new "Cursor: project-scoped MCP setup" block under the Cursor install steps with the snippet, the rationale, and a pointer to the refusal log line.
39
+
-`Makefile`: `cursor.mcp.json` added to `JSON_FILES` so `make check-json` validates it.
40
+
-`test/unit/launcher.bats`: new cases for Step 0c (HOME refuse, plugin-root refuse, no-marker refuse, `.git`-marker pass, `.archcore`-marker pass, `package.json`-marker pass, `ARCHCORE_CWD`-rebase pass, `ARCHCORE_ALLOW_ANY_CWD` bypass, legacy `ARCHCORE_ALLOW_PLUGIN_CWD` alias still works, diagnostic-log presence/absence). Existing cases adapted by adding `.git` to fake plugin install / user project fixtures and `2>/dev/null` to assertions that pin exact stdout.
41
+
-`test/structure/cursor-plugin.bats` (new): pins the `cursor.mcp.json` contract — `cwd: "${workspaceFolder}"` and `env.ARCHCORE_CWD: "${workspaceFolder}"` must both be present; `command` must invoke `archcore` with `args[0] == "mcp"`; README must reference the template.
42
+
43
+
## Risks and Constraints
44
+
45
+
-**False positives on minimal projects.** A user opening a brand-new directory before `git init` (no project markers yet) hits the guard. Mitigation: error message includes the escape hatch (`ARCHCORE_ALLOW_ANY_CWD=1`) and lists the markers; the common workflow is `git init` before invoking archcore anyway.
46
+
-**Resolved-symlink comparison.**`$HOME`, `$CLAUDE_PLUGIN_ROOT`, `$CURSOR_PLUGIN_ROOT` are all compared via `cd "$VAR" 2>/dev/null && pwd -P` so symlinked install dirs match too. If the env var points at a non-existent path, the check silently skips (no false refusal). Important on macOS where `$HOME=/Users/<u>` but `pwd -P` on `cd $HOME` yields the same — and on Linux distros where `/home/<u>` is symlinked.
47
+
-**Stderr noise from the diagnostic.** One line per MCP start. Cursor's MCP server panel and Claude Code's `/mcp` show stderr — that is exactly where users want this. Not visible to chat output.
48
+
-**Cross-host neutrality.** Step 0c has no host detection — it refuses based on cwd alone. The fix instructions cover all three hosts. Codex was already protected by Step 0b; Step 0c piggybacks safely (escape hatch + Codex-specific cwd marker `*/plugins/cache/*` already handled in 0b first).
49
+
-**`cursor.mcp.json` is a template, not an auto-installed file.** Cursor does not pick it up from the plugin root. Users copy the snippet into their own config. Auto-install would require a host hook (Cursor `sessionStart`) and risk overwriting user configs — rejected for now.
50
+
51
+
## Verification
52
+
53
+
A/B reproducer against the launcher (no MCP host required). **Important:** isolate `CLAUDE_PLUGIN_DATA` / `XDG_DATA_HOME` to a scratch dir — otherwise the launcher exec's any locally cached real CLI binary and you end up testing the live server instead of the launcher.
Bats coverage: `bats test/unit/launcher.bats test/structure/cursor-plugin.bats` exercises the same matrix in isolation (mock archcore + scratch tmpdirs). 42 tests across the two files, all green.
> The Cursor plugin manifest does not register MCP servers (Cursor manages MCP through `~/.cursor/mcp.json` or `.cursor/mcp.json` instead). You configure `archcore` once, in one of those files. **Always pin the working directory** — otherwise a global registration leaks one project's docs into every other project.
38
+
>
39
+
> Use this snippet (also shipped as [`cursor.mcp.json`](./cursor.mcp.json) at the plugin root):
40
+
>
41
+
> ```json
42
+
> {
43
+
> "mcpServers": {
44
+
> "archcore": {
45
+
> "command": "archcore",
46
+
> "args": ["mcp"],
47
+
> "cwd": "${workspaceFolder}",
48
+
> "env": { "ARCHCORE_CWD": "${workspaceFolder}" }
49
+
> }
50
+
> }
51
+
> }
52
+
> ```
53
+
>
54
+
> - `cwd: "${workspaceFolder}"` — Cursor expands this to the active project root on every server spawn, so each window's MCP attaches to the right `.archcore/`.
55
+
> - `env.ARCHCORE_CWD` — belt-and-braces. If Cursor or another host ever fails to honor `cwd` (Claude Code's `cwd` field is silently ignored, [#17565](https://github.com/anthropics/claude-code/issues/17565)), the bundled `bin/archcore` launcher `cd`s to `$ARCHCORE_CWD` before exec.
56
+
> - `command: "archcore"` — assumes the CLI is on `PATH` (via `curl install.sh`, `go install`, etc.). To use the launcher bundled with the plugin instead, set `command` to the absolute path of `bin/archcore` inside the installed plugin directory.
57
+
>
58
+
> The launcher refuses to start the MCP server if cwd looks wrong (`$HOME`, `/`, plugin install dir, or a directory with no project markers) and prints actionable instructions on stderr. If you see `[archcore launcher] Refusing to start MCP …` in Cursor's MCP server panel, the `cwd` field is missing or wrong.
0 commit comments