{"_id":"@aderik/mcp-cross-project-claude","_rev":"5-220e3632f9eb7e96a8bd18a2a7ca8876","name":"@aderik/mcp-cross-project-claude","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.0":{"name":"@aderik/mcp-cross-project-claude","version":"0.1.0","keywords":["mcp","model-context-protocol","claude","claude-code","bridge","subagent","cross-project"],"author":{"name":"Aderik Teekman","email":"aderik@live.nl"},"license":"MIT","_id":"@aderik/mcp-cross-project-claude@0.1.0","maintainers":[{"name":"aderik","email":"aderik@live.nl"}],"homepage":"https://github.com/aderik/mcp-cross-project-claude#readme","bugs":{"url":"https://github.com/aderik/mcp-cross-project-claude/issues"},"bin":{"mcp-cross-project-claude":"dist/index.js"},"dist":{"shasum":"9e98318650e67573cb1c490862983b8308e8e758","tarball":"https://registry.npmjs.org/@aderik/mcp-cross-project-claude/-/mcp-cross-project-claude-0.1.0.tgz","fileCount":9,"integrity":"sha512-CUG94p/yu1t3qu3x9sTa7PTXv0gp/obk0Q6U5H58mRpS4/21Pt2q9m0m8Nt0LsLEHil5t6ah3Cx8l7SUXc7Qvw==","signatures":[{"sig":"MEYCIQCg6NDskcMz4kO2aEllnAkBTPXKMxNZ0j+QG+XFvzehWQIhAMcINU6JHHE8bslh24dGUaXVSabqabi7vBCsb5YfZrYk","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31546},"type":"module","engines":{"node":">=18"},"gitHead":"e94c1cd87d89ae44db224b6c68afa1ea91a15d69","scripts":{"build":"tsc && chmod +x dist/index.js","start":"node dist/index.js","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"aderik","email":"aderik@live.nl"},"repository":{"url":"git+https://github.com/aderik/mcp-cross-project-claude.git","type":"git"},"_npmVersion":"10.9.4","description":"MCP server that bridges two Claude Code projects: ask read-only questions across project boundaries without pulling the other project's source into the caller's context.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"@modelcontextprotocol/sdk":"^1.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.3","@types/node":"^22.7.5"},"_npmOperationalInternal":{"tmp":"tmp/mcp-cross-project-claude_0.1.0_1778762899087_0.809593814714018","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to mcp-cross-project-claude (unscoped). Update your .mcp.json to use \"mcp-cross-project-claude\" instead. See https://github.com/aderik/mcp-cross-project-claude"},"0.2.0":{"name":"@aderik/mcp-cross-project-claude","version":"0.2.0","keywords":["mcp","model-context-protocol","claude","claude-code","bridge","subagent","cross-project"],"author":{"name":"Aderik Teekman","email":"aderik@live.nl"},"license":"MIT","_id":"@aderik/mcp-cross-project-claude@0.2.0","maintainers":[{"name":"aderik","email":"aderik@live.nl"}],"homepage":"https://github.com/aderik/mcp-cross-project-claude#readme","bugs":{"url":"https://github.com/aderik/mcp-cross-project-claude/issues"},"bin":{"mcp-cross-project-claude":"dist/index.js"},"dist":{"shasum":"ab77c9f60dbb37d72a8bce78280193d8feacc6a5","tarball":"https://registry.npmjs.org/@aderik/mcp-cross-project-claude/-/mcp-cross-project-claude-0.2.0.tgz","fileCount":23,"integrity":"sha512-fUaPLU9dP2OxI1HwAPxGTV7MwTU0BwbA8T2ghLrHKegS/5starGZAntyfsodN1q1VOGsbq43S5of6A5PTMz66g==","signatures":[{"sig":"MEQCIGk+QwzfnFAvbw7xOc4h3bEtAyMNrlyw9+lee3kfFKyOAiBj2+hbMErkZ9ReSDNMwCkcwohkFOEhv4vlT30A3+tMlw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98753},"type":"module","engines":{"node":">=18"},"gitHead":"dbbbb980cd215c9a0641c39783687a961cdb4768","scripts":{"build":"tsc && chmod +x dist/index.js","start":"node dist/index.js","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"aderik","email":"aderik@live.nl"},"repository":{"url":"git+https://github.com/aderik/mcp-cross-project-claude.git","type":"git"},"_npmVersion":"10.9.4","description":"Encrypted LAN bridge between two Claude Code projects: ask read-only questions across project boundaries (and across machines) without pulling the other project's source into the caller's context.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"bonjour-service":"^1.3.0","noise-handshake":"^4.2.0","@modelcontextprotocol/sdk":"^1.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.3","@types/node":"^22.7.5"},"_npmOperationalInternal":{"tmp":"tmp/mcp-cross-project-claude_0.2.0_1778765954722_0.4554760199607497","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to mcp-cross-project-claude (unscoped). Update your .mcp.json to use \"mcp-cross-project-claude\" instead. See https://github.com/aderik/mcp-cross-project-claude"},"0.3.0":{"name":"@aderik/mcp-cross-project-claude","version":"0.3.0","keywords":["mcp","model-context-protocol","claude","claude-code","bridge","subagent","cross-project"],"author":{"name":"Aderik Teekman","email":"aderik@live.nl"},"license":"MIT","_id":"@aderik/mcp-cross-project-claude@0.3.0","maintainers":[{"name":"aderik","email":"aderik@live.nl"}],"homepage":"https://github.com/aderik/mcp-cross-project-claude#readme","bugs":{"url":"https://github.com/aderik/mcp-cross-project-claude/issues"},"bin":{"mcp-cross-project-claude":"dist/index.js"},"dist":{"shasum":"d0e167b41534a492d62128e3be8a5e8b42a4b553","tarball":"https://registry.npmjs.org/@aderik/mcp-cross-project-claude/-/mcp-cross-project-claude-0.3.0.tgz","fileCount":22,"integrity":"sha512-r8KEeS4X+66ERuNWkFNW3dn6Y2ksUL65KMe8zO8nbMXXjdJmpj6r96h42B4IwRhyJmtKDJVmLvCJrBeEioVLSw==","signatures":[{"sig":"MEUCID4stGhIOIcdHicUoQOWg+ODY8+K7k1Wr98uMF60GG8pAiEAnZBG8DyApjapuRlNhWUIyu1rHx8yhx0hEkbh0ig8LT8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95708},"type":"module","engines":{"node":">=18"},"gitHead":"731ca0e6dde822e910ae7a9dce223717ed35dcbe","scripts":{"build":"tsc && chmod +x dist/index.js","start":"node dist/index.js","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"aderik","email":"aderik@live.nl"},"repository":{"url":"git+https://github.com/aderik/mcp-cross-project-claude.git","type":"git"},"_npmVersion":"10.9.4","description":"Encrypted LAN bridge between two Claude Code projects: ask read-only questions across project boundaries (and across machines) without pulling the other project's source into the caller's context.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"bonjour-service":"^1.3.0","noise-handshake":"^4.2.0","@modelcontextprotocol/sdk":"^1.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.3","@types/node":"^22.7.5"},"_npmOperationalInternal":{"tmp":"tmp/mcp-cross-project-claude_0.3.0_1778772772569_0.9631098030830125","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to mcp-cross-project-claude (unscoped). Update your .mcp.json to use \"mcp-cross-project-claude\" instead. See https://github.com/aderik/mcp-cross-project-claude"},"0.3.1":{"name":"@aderik/mcp-cross-project-claude","version":"0.3.1","keywords":["mcp","model-context-protocol","claude","claude-code","bridge","subagent","cross-project"],"author":{"name":"Aderik Teekman","email":"aderik@live.nl"},"license":"MIT","_id":"@aderik/mcp-cross-project-claude@0.3.1","maintainers":[{"name":"aderik","email":"aderik@live.nl"}],"homepage":"https://github.com/aderik/mcp-cross-project-claude#readme","bugs":{"url":"https://github.com/aderik/mcp-cross-project-claude/issues"},"bin":{"mcp-cross-project-claude":"dist/index.js"},"dist":{"shasum":"7f150eb029f22b97bda2fc9d8a80ce4aad86f705","tarball":"https://registry.npmjs.org/@aderik/mcp-cross-project-claude/-/mcp-cross-project-claude-0.3.1.tgz","fileCount":22,"integrity":"sha512-H/iMtmNCo/Q8jFenua6NqILj94c3D+JnAOy2kwWGjJ2jDwBVyWQbA1+JQ48rQnWjN5A6ofLDuzPCj7xSTVGgfw==","signatures":[{"sig":"MEUCIBbioO7lylM9X4Nlutfaf68p2dQJ+RveOiib8pQwf5GiAiEArni21MrkRUzz84KkMO54vMlY88mhNCCIeJZ+vrQH9H4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":97055},"type":"module","engines":{"node":">=18"},"gitHead":"e62b75c7b0e345040aeef09f21505a1cda668419","scripts":{"build":"tsc && chmod +x dist/index.js","start":"node dist/index.js","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"aderik","email":"aderik@live.nl"},"repository":{"url":"git+https://github.com/aderik/mcp-cross-project-claude.git","type":"git"},"_npmVersion":"10.9.4","description":"Encrypted LAN bridge between two Claude Code projects: ask read-only questions across project boundaries (and across machines) without pulling the other project's source into the caller's context.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"bonjour-service":"^1.3.0","noise-handshake":"^4.2.0","@modelcontextprotocol/sdk":"^1.0.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.3","@types/node":"^22.7.5"},"_npmOperationalInternal":{"tmp":"tmp/mcp-cross-project-claude_0.3.1_1778773575399_0.4175248269637599","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Renamed to mcp-cross-project-claude (unscoped). Update your .mcp.json to use \"mcp-cross-project-claude\" instead. See https://github.com/aderik/mcp-cross-project-claude"}},"time":{"created":"2026-05-14T12:48:18.955Z","modified":"2026-05-14T16:06:22.338Z","0.1.0":"2026-05-14T12:48:19.229Z","0.2.0":"2026-05-14T13:39:14.884Z","0.3.0":"2026-05-14T15:32:52.711Z","0.3.1":"2026-05-14T15:46:15.549Z"},"bugs":{"url":"https://github.com/aderik/mcp-cross-project-claude/issues"},"author":{"name":"Aderik Teekman","email":"aderik@live.nl"},"license":"MIT","homepage":"https://github.com/aderik/mcp-cross-project-claude#readme","keywords":["mcp","model-context-protocol","claude","claude-code","bridge","subagent","cross-project"],"repository":{"url":"git+https://github.com/aderik/mcp-cross-project-claude.git","type":"git"},"description":"Encrypted LAN bridge between two Claude Code projects: ask read-only questions across project boundaries (and across machines) without pulling the other project's source into the caller's context.","maintainers":[{"name":"aderik","email":"aderik@live.nl"}],"readme":"# @aderik/mcp-cross-project-claude\n\nAn encrypted LAN bridge that lets an AI session in one project ask read-only\nquestions to a Claude Code agent running in another project — on the same\nmachine or another machine on the same network. The calling session only ever\nsees the answer text; none of the other project's files enter its context.\n\n```\n┌──────────────────────────┐                  LAN                ┌──────────────────────────┐\n│  AI client (project A)   │                                     │  Bridge (project B)      │\n│   stdio MCP ↕            │                                     │  ↕ spawns claude -p in B │\n│  Bridge (project A) ─────┼── Noise IK / ChaCha20-Poly1305 ────►│   read-only, ephemeral   │\n│                          │◄────────────── answer text ─────────│                          │\n└──────────────────────────┘                                     └──────────────────────────┘\n```\n\nOnly the **question string** crosses the boundary. The receiving side answers\nwith its own `Read`/`Grep`/`Glob` on its own files; tool control stays local\non each side.\n\n## How it works\n\nEach bridge runs as a single process per project, with four roles:\n\n1. **MCP stdio server** for the local AI client. Exposes one tool:\n   `ask_cross_project(question: string)`.\n2. **TCP listener** for incoming peer questions. Authenticates each peer with\n   a Noise IK handshake against the long-term public key stored at pairing\n   time; unknown public keys are dropped.\n3. **Network client** when the local tool is invoked. Looks up the paired\n   peer's fingerprint in mDNS (or uses `PEER_HOST`/`PEER_PORT`).\n4. **Answering engine**. For each authenticated incoming question, spawns a\n   fresh, ephemeral `claude -p` in the bridge's `cwd`.\n\nIdentity (label, long-term keypair, paired peer) lives in a per-project state\nfile under `~/.config/mcp-cross-project-claude/<basename-of-cwd>-<sha8-of-cwd>/state.json`.\nThe path is keyed by absolute `cwd` so each project gets its own identity,\nkeypair, and paired peer — even when the bridge is registered at user-scope\nin Claude Code and shared across all your projects. Override with `STATE_DIR`\nif you want a different path or to share state across project dirs.\n\n## Install\n\nDrop this snippet into the calling project's `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"cross-project\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aderik/mcp-cross-project-claude\", \"serve\"]\n    }\n  }\n}\n```\n\nThat's it. The project dir is whatever `cwd` the AI client launches the\nbridge in (Claude Code, Cursor, etc. set this to the project root). On\nfirst run the bridge generates a stable label and X25519 keypair into the\nstate file.\n\nPrerequisites on each participating machine: Node ≥ 18 and the `claude` CLI\nauthenticated on `PATH`.\n\n## Pairing\n\nA bridge can be paired with **exactly one** peer at a time. Pairing is a\none-time, human-confirmed exchange of long-term public keys, bound by a\nPIN-derived PSK (Noise `XXpsk0`).\n\nOn the **receiving** machine:\n\n```bash\ncd /path/to/project-A\nnpx -y @aderik/mcp-cross-project-claude pair-receive\n```\n\nSample output:\n\n```\n=========================================================\n  Pairing PIN:  4729\n=========================================================\n  Our label:    project-A-3f2a\n  Our fp:       7c2a91d4...\n  Listening:    0.0.0.0:54213\n  mDNS:         _mcp-bridge-pair._tcp.local\n  Window:       60s (single attempt)\n\nOn the OTHER machine, run:\n  npx -y @aderik/mcp-cross-project-claude pair-send --peer-label project-A-3f2a --pin 4729\n```\n\nOn the **sending** machine, within 60 seconds:\n\n```bash\ncd /path/to/project-B\nnpx -y @aderik/mcp-cross-project-claude pair-send --peer-label project-A-3f2a --pin 4729\n```\n\nBoth sides print `Pairing succeeded` and persist the peer's public key,\nfingerprint, and label. From that point on, the two `serve` processes can\ntalk and the `.mcp.json` snippet above is enough on both sides.\n\nIf mDNS is blocked, add `--no-mdns --host <ip> --port <port>` to `pair-send`.\nThe receiver prints the port it bound.\n\n`pair-receive` and `pair-send` both refuse if a peer is already paired —\nrun `unpair` first.\n\n## Commands\n\n| Subcommand     | Purpose                                                       |\n|----------------|---------------------------------------------------------------|\n| `serve`        | Default. MCP stdio server + TCP listener.                     |\n| `pair-receive` | Show PIN, wait for one pairing attempt.                       |\n| `pair-send`    | Connect to a peer's pairing listener and complete pairing.    |\n| `peers`        | Print our label + fingerprint and the paired peer.            |\n| `unpair`       | Remove the paired peer.                                       |\n| `help`         | Show usage.                                                   |\n\n## Environment variables (operational only)\n\n| Variable                    | Default            | Purpose                                                              |\n|-----------------------------|--------------------|----------------------------------------------------------------------|\n| `LISTEN_PORT`               | `0` (ephemeral)    | TCP port for incoming peer sessions.                                 |\n| `LISTEN_HOST`               | `0.0.0.0`          | Bind interface.                                                      |\n| `NO_MDNS`                   | (off)              | Set to `1` to disable mDNS advertise/discover.                       |\n| `PEER_HOST`                 | (mDNS)             | Manual peer host. Required when `NO_MDNS=1`.                         |\n| `PEER_PORT`                 | (mDNS)             | Manual peer port. Required when `NO_MDNS=1`.                         |\n| `SESSION_TIMEOUT_MS`        | `30000`            | Per-frame timeout on Noise handshake and request/response.           |\n| `CLAUDE_TIMEOUT_MS`         | `180000`           | Per-question hard timeout on the spawned `claude -p`.                |\n| `CLAUDE_BIN`                | `claude`           | Path to the Claude Code CLI binary.                                  |\n| `ALLOWED_TOOLS`             | `Read,Grep,Glob`   | Forwarded to `claude --allowedTools`. Keep read-only.                |\n| `MODEL`                     | (Claude default)   | Override the spawned session's model.                                |\n| `MAX_BUDGET_USD`            | (no cap)           | Per-call spend cap. Forwarded to `claude --max-budget-usd`.          |\n| `MAX_CONCURRENT_QUESTIONS`  | `3`                | Cap on simultaneous `claude -p` spawns answered by this bridge.      |\n| `STATE_DIR`                 | per-cwd subdir     | Override location of the persisted state file. Default is keyed by absolute cwd. |\n\n## Security model\n\nAfter pairing:\n\n- Sessions use Noise `IK` (X25519, ChaCha20-Poly1305) with both sides'\n  long-term static keys. Forward-secret per session via ephemeral keys.\n- A connection from an unknown public key is dropped: the responder closes\n  the socket as soon as the initiator-static does not match the paired peer.\n- Session routing on mDNS uses the **fingerprint** in TXT records — labels\n  are cosmetic. A label-impersonating advertisement with a different\n  fingerprint is ignored.\n\nAt pairing time:\n\n- The PIN never leaves the local machine. It seeds a 32-byte PSK via HKDF;\n  that PSK is mixed into the `XXpsk0` handshake.\n- The pairing window is 60 seconds and accepts exactly one attempt.\n- An eavesdropper who captures the pairing handshake can, in principle,\n  offline-bruteforce the 4-digit PIN. They cannot derive session keys (those\n  depend on ephemeral ECDH they did not participate in), and by the time\n  they crack it, it has been burned. They also cannot replay against a\n  paired bridge because the responder rejects unknown peer public keys.\n- Vetted primitives only: `noise-handshake` (Noise framework, Mathias Buus /\n  Hypercore), Node stdlib `crypto` for HKDF and randomness. No custom crypto.\n\n## Spawned `claude -p` flags\n\n```\nclaude -p \\\n  --strict-mcp-config \\\n  --output-format text \\\n  --allowedTools <ALLOWED_TOOLS>\n```\n\n- `--strict-mcp-config` without `--mcp-config` ⇒ zero MCP servers loaded\n  inside the spawn. Primary recursion guard.\n- `--allowedTools \"Read,Grep,Glob\"` ⇒ no `Edit`, `Write`, `Bash` etc.\n- `CROSS_PROJECT_BRIDGE_DEPTH=1` is bumped in the spawn env. A bridge that\n  sees `>= 1` in its own env refuses to start.\n\n`--bare` is deliberately **not** used: it skips OAuth and the keychain and\nrequires `ANTHROPIC_API_KEY`, which breaks Max-plan auth. Non-bare keeps the\nproject's `CLAUDE.md` in scope, which is exactly the right place for any\nproject-specific answering posture or constraints.\n\n## Verification\n\nScripts under `tests/` (require a real `claude` CLI; each question costs a\nsmall amount of API credit). Run from the repo root:\n\n```bash\nbash tests/e2e-pair-and-ask.sh\nbash tests/e2e-recursion-and-wire.sh\nbash tests/e2e-reverse.sh\nnode tests/transport-large.mjs\n```\n\nThey verify, in order: pairing, a question end-to-end, wrong-PIN rejection,\nthat the spawned subagent has no bridge tool, that `Bash`/`Edit`/`Write` are\nblocked, that wire bytes contain no readable cleartext, that the reverse\ndirection also works, and that the transport layer handles application\npayloads above the Noise 64KB-cipher limit (large-payload chunking).\n\n## Limitations\n\n- **Local-network only.** TCP. The receiving side must accept incoming on\n  its `LISTEN_PORT`. Firewalls, Tailscale, ZeroTier, etc., are out of scope.\n- **mDNS over loopback can be flaky**; for two bridges on the same machine\n  prefer `NO_MDNS=1` + `PEER_HOST=127.0.0.1` + explicit `LISTEN_PORT`/`PEER_PORT`.\n- **No streaming.** The bridge returns the full text once `claude -p` exits.\n- **PIN entropy is 4 digits.** Security relies on single-use + 60s window +\n  rejection of unknown peer keys, not on the PIN's entropy itself.\n\n## Development\n\n```bash\ngit clone https://github.com/aderik/mcp-cross-project-claude.git\ncd mcp-cross-project-claude\nnpm install\nnpm run build\n```\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","readmeFilename":"README.md"}