universal-peer-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@universal-peer-mcpAsk my running Claude Code session to review this diff"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
universal-peer-mcp
Claude Code can hand work to another Claude Code session on the same computer. universal-peer-mcp lets a local MCP client — Codex, or another Claude Code — join that conversation. It connects only to sessions that are already running and that you have put on an allowlist yourself.
It is a local stdio MCP server plus a per-user daemon. Nothing listens on a network port.
Trust boundary
Read these four before installing. They are properties of the design, not settings.
Same Mac, same user account. Access is gated by a same-uid check and a
0600control token. Any other program running as the same uid can use this server.Admin mode is decided when the daemon starts. It comes from the daemon's own startup environment (
CLAUDE_PEER_MCP_ADMIN=1). No later request, tool argument, or config file can turn it on.SIGTERMcleans up. OnSIGTERMorSIGINTthe daemon removes its socket, identity file, lock, and control token before exiting.Changing target config requires restarting the daemon. Targets are read once at startup and are not reloaded while the daemon runs.
Related MCP server: aichat-mcp
What it is not
Not an official Anthropic or OpenAI project. See TRADEMARKS.md.
Not a permission escalator. The permission mode you launched Claude Code with is the ceiling; this server has no command that changes it.
Not a retry queue. A failed send is never retried automatically.
Not a tamper-proof ledger. The event file is an append-only local file for surviving restarts, nothing more.
Not a session launcher. It never starts, stops, or focuses a Claude Code window.
Not a remote or multi-user tool. There is no network transport.
Requirements
Item | Requirement |
OS | macOS. The package declares |
Architecture | arm64. The package declares |
Runtime | Bun >= 1.3.0 ( |
Peer session | A running Claude Code session that publishes its local peer registry under |
Target launch | The target session must have been started with an explicit |
MCP client | A client speaking MCP |
Before you install
Four facts have to be true of the session you intend to talk to. Every one of them is enforced fail-closed, so none can be loosened in configuration, and a session that fails one cannot be repaired in place — relaunching it gives it a new session UUID. Check all four before you install anything.
PID=<the target session's pid>
ps -p "$PID" -o args= # 1 and 2, read the line yourself
cat ~/.claude/sessions/"$PID".json # 3 and 4argv[0]is a path, not a bare name. The first token ofps -o args=must be the same string the kernel recorded for the exec. A session started asclaude --resume ...throughPATHfails; one started as/some/path/claude --resume ...passes. A symbolic link is fine: what is compared is the string handed toexecve, and it is not resolved. Do not runreadlink -fto "fix" this — the resolved path can differ from what the kernel recorded. Details and the measurement: docs/troubleshooting.md.An explicit
--permission-modeflag is on that line, with a value this adapter maps. See the table under Register one target.autois enough;bypassPermissionsis not required and this package never asks for it.~/.claude/sessions/<pid>.jsonexists and carriespeerProtocol: 1, bothnotify_idleandreply_across_default_dirsinpeerFeatures, and amessagingSocketPath.Its
cwdis the absolute path you will write intotargets.json, after symlinks.
If a session fails 1 or 2 and you own it, relaunch it. An interactive session needs a terminal:
started from a plain background job it has no controlling tty and exits. tmux gives it one.
tmux new-session -d -s peer-target '<path-to-claude> --permission-mode auto'Measured 2026-09-07: a child of tmux new-session -d reports a tty from ps -o tty=; the same
command backgrounded from a script reports ??.
Install
Start here: install a tarball you were handed. That is the shortest path and the only one a
test in this repository exercises — test/install.test.mjs installs a tarball into an empty
prefix with the network unavailable, runs the installed bin, and uninstalls. You do not need
this repository, a clone, or git to install.
shasum -a 256 ./universal-peer-mcp-0.1.0.tgz # compare against the hash you were given
npm install -g "./universal-peer-mcp-0.1.0.tgz"To keep the machine's global npm prefix untouched, install into a prefix of your own. This is the exact shape the install test runs.
PREFIX="$HOME/.universal-peer-mcp-local"
mkdir -p "$PREFIX"
npm install -g --prefix "$PREFIX" --no-audit --no-fund --ignore-scripts \
"./universal-peer-mcp-0.1.0.tgz"
BIN="$PREFIX/bin/universal-peer-mcp"The package has no dependencies; the installed tree contains no node_modules.
The other ways in, for when you have repository access or once there is a release:
Path | Status |
| Not available yet. There is no registry release, so this command fails today. The same applies to |
| Works once the repository is public, with |
Pack it yourself from a clone | Needs repository access. |
npm pack
npm install -g "./universal-peer-mcp-$(node -p "require('./package.json').version").tgz"Check the install. doctor only reads; it prints no token, no process arguments, and no absolute path other than the ~ shorthand for your own home.
universal-peer-mcp doctorThe summary fields of a healthy result:
{
"ok": true,
"platform": "darwin",
"arch": "arm64",
"runtime": "Bun 1.3.11",
"stateDirectory": "~/Library/Application Support/claude-peer-mcp",
"stateDirectorySource": "default",
"note": "doctor does not print tokens or process arguments",
"writes": "none — doctor never creates the state directory or any file"
}The full document also carries system, runtimes, state, targets, claudeRegistry, and codexWake. ok is true only when every one of those is satisfied. Field by field: docs/troubleshooting.md.
Register one target
State lives outside the package, in ~/Library/Application Support/claude-peer-mcp/ — the directory keeps this package's former name, claude-peer-mcp, so that an existing event log is not stranded. The directory must be 0700 and the files 0600; the daemon refuses to start otherwise.
mkdir -p ~/Library/Application\ Support/claude-peer-mcp
chmod 700 ~/Library/Application\ Support/claude-peer-mcpCopy the shipped example out of the installed package and edit it. npm root -g prints where global packages live:
cp "$(npm root -g)/universal-peer-mcp/targets.example.json" ~/Library/Application\ Support/claude-peer-mcp/targets.json
chmod 600 ~/Library/Application\ Support/claude-peer-mcp/targets.jsonWorking from a clone instead of an install, copy targets.example.json out of the checkout.
{
"frontend-review": {
"sessionId": "10000000-0000-4000-8000-000000000001",
"cwd": "/path/to/project",
"expectedDisplayName": "Frontend review",
"permissionMode": "prompting"
}
}sessionId— the exact session UUID of the running Claude Code session. This is the address. The name shown on screen is never used as an address.cwd— an absolute path that must resolve, after symlinks, to the session's own working directory.permissionMode—promptingorbypass. It must match the mode proved from the target process's real arguments, otherwise the send fails. This file holds the mapped value, not the flag: a session launched with--permission-mode autois written here as"prompting".expectedDisplayName— optional, diagnostic only. A mismatch is recorded and shown; it never blocks a send.
The whole mapping, from src/adapters/claude-native-v1/darwin-procargs.mjs:90:
| value in |
|
|
|
|
|
|
|
|
|
|
anything else, or the flag absent | not mappable — |
auto is enough. Nothing in this package needs bypassPermissions, and nothing here can raise
the mode a session was launched with. Claude Code 2.1.260 also accepts manual and dontAsk;
this adapter does not map them, and no mapping is added to make an error go away.
The shipped cwd is the placeholder /path/to/project, which does not exist. Until you replace it with a real absolute path, doctor reports targets.schemaValid: false — that is the file being checked, not a broken install.
Full schema, all environment variables, and how to find a session UUID: docs/configuration.md. Korean walkthrough for opening the session and answering it: docs/threads-launch-ko.md.
Restart the daemon after any edit to targets.json. The daemon reads targets once at startup (src/daemon.mjs:21) and the stdio entry point reads them once as well (src/server.mjs:7). Neither watches the file.
kill "$(plutil -extract pid raw -- ~/Library/Application\ Support/claude-peer-mcp/daemon.json)"The next tool call starts a fresh daemon. Restart your MCP client too, so the tool list picks up the new aliases.
Register the server with your MCP client
This changes your client's own configuration, so it is the client owner's decision, not a step
this package requires. Nothing here writes a client config; the commands below are shown so the
owner can see exactly what would change, and the equivalent config blocks are shown so the change
can be reviewed before it is made. claude mcp add defaults to local scope — do not reach for
-s user unless the owner asked for a machine-wide change.
Codex — add the block from examples/codex-config.toml to ~/.codex/config.toml, or:
codex mcp add claude-peer -- universal-peer-mcp serveClaude Code — use examples/claude-mcp.json, or:
claude mcp add claude-peer -- universal-peer-mcp serveOptional extensions are off by default and are enabled only on the daemon's command line:
universal-peer-mcp serve --enable milestone --enable code-reviewStatus, send, wait
There is no CLI for messaging. Everything below is an MCP tool your client calls. The tool list order is fixed.
Tool | What it does |
| Lists aliases with their configured permission mode and connection state. |
| Re-proves one target's identity and permission mode without sending anything. |
| Sends one message. Requires |
| Waits for |
| Reads durable local events after a sequence cursor. |
| Reads redacted daemon health and the event cursor. |
daemon_shutdown appears only when the daemon was started in admin mode.
Notes that change how you read the results:
messageIdis yours, and it is the idempotency key. The samemessageIdwith the same content returns the earlier result instead of sending again. The samemessageIdwith different content is rejected as a conflict.bodyis 1–65,536 bytes of UTF-8 (src/core/limits.mjs:1). The published JSON Schema saysmaxLength: 65536, which JSON Schema counts in characters, so non-ASCII text reaches the real limit sooner.kindis 2–64 characters matching^[a-z][a-z0-9_-]{1,63}$.peer_waitdefaults torequire: "reply"and a 30,000 ms timeout, with a 300,000 ms ceiling. It never resends.ACK means a defined marker arrived, not that the other side agreed. The result carries
evidencedistinguishingmessage_status,idle_notice, andapplication_ack.After a failed send the result is
DELIVERY_UNCERTAIN. Delivery is unknown and nothing is retried for you.
One call, with the arguments your client sends:
{
"alias": "frontend-review",
"messageId": "10000000-0000-4000-8000-000000000021",
"threadId": "10000000-0000-4000-8000-000000000020",
"kind": "review_request",
"body": "Run the test suite in /path/to/project and report pass or fail."
}Then wait for the answer to that exact message:
{ "messageId": "10000000-0000-4000-8000-000000000021", "require": "reply", "timeoutMs": 30000 }The other side answers with a first line of PEER_ACK v=1 ... or PEER_REPLY v=1 ... verdict=pass. The whole round trip, end to end: docs/demo-ack.md.
Checking what actually happened
Read the ledger file first. It is append-only and fsynced before a connection ends, so it holds every event including the ones the public contract cannot carry.
grep '<the messageId you sent>' \
"${UNIVERSAL_PEER_MCP_STATE_DIR:-$HOME/Library/Application Support/claude-peer-mcp}/events.jsonl"peer_list_events is the second look, not the first, and it can fail where the file does not.
A response carrying a refusal event can be rejected as a whole — see
docs/known-issues.md §4. That failure is not a broken install and not a
reason to reinstall or resend: it is the known defect being observed, and the events it could not
return are in the file above.
Bytes written is not delivery. To confirm arrival, look for the messageId in the receiving
session's own transcript under ~/.claude/projects/<cwd slug>/<sessionId>.jsonl.
Uninstall
kill "$(plutil -extract pid raw -- ~/Library/Application\ Support/claude-peer-mcp/daemon.json)"
codex mcp remove claude-peer
claude mcp remove claude-peer
npm uninstall -g universal-peer-mcp
rm -rf ~/Library/Application\ Support/claude-peer-mcpThe last line deletes your targets and the local event history. Nothing outside that directory is touched.
Documentation
docs/configuration.md — target schema, environment variables, state layout.
docs/troubleshooting.md — restarts, extension mismatch, dead targets, permission errors.
docs/known-issues.md — what is not closed: refused write-and-close senders, the hold bound, how a refusal reads, and why its reason is on the ledger only. A handoff package ships a separate root-level
KNOWN-ISSUES.mdwith the measurements and review residue for that specific handoff; the two are different documents and neither replaces the other.docs/trial-findings.md — what broke when this package was first handed to someone outside the project, and what the install documents were changed to (Korean).
docs/demo-ack.md — one sanitized round trip: send, ACK, reply, wait.
docs/architecture.md — how the pieces fit (Korean).
docs/threads-launch-ko.md — Korean quick start.
SECURITY.md · COMPATIBILITY.md · CONTRIBUTING.md · TRADEMARKS.md
License
Apache-2.0. See LICENSE. Copyright 이형석 (Hyungseok Lee).
Anthropic, Claude, OpenAI, and Codex are trademarks of their respective owners. This project is not an official Anthropic or OpenAI project and is not affiliated with, endorsed by, or sponsored by either company.
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
- QuallaaOAuthcom.quallaa
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
Enable secure connectivity between Sentry issues and debugging data, and LLM clients, using a Model Context Protocol (MCP) server.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables MCP clients to spawn and control Codex CLI and Claude Code sessions on the host machine, with session management and filesystem access.4MIT
- AlicenseAqualityDmaintenanceMCP server for inter-agent communication. Gives multiple Claude Code sessions a shared message board, agent registry, and orchestration layer — backed by a cloud relay so agents can coordinate across machines, repos, and teams.840 npmMIT
- AlicenseAqualityAmaintenanceEnables real-time cross-machine communication for Claude Code agents using a shared MCP relay server.1398 PyPI12MIT
- AlicenseAqualityBmaintenanceEnables multiple coding agents (Claude Code, Codex, Cursor) to discover each other's sessions, search transcripts, ask questions, and handoff tasks through a shared MCP server.54 npmMIT