Skip to main content
Glama
sunsquare26-ai

universal-peer-mcp

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.

  1. Same Mac, same user account. Access is gated by a same-uid check and a 0600 control token. Any other program running as the same uid can use this server.

  2. 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.

  3. SIGTERM cleans up. On SIGTERM or SIGINT the daemon removes its socket, identity file, lock, and control token before exiting.

  4. 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 "os": ["darwin"].

Architecture

arm64. The package declares "cpu": ["arm64"], so x64 is refused at install time. It is untested, not merely unsupported — see COMPATIBILITY.md.

Runtime

Bun >= 1.3.0 (engines.bun). The bin runs under #!/usr/bin/env bun.

Peer session

A running Claude Code session that publishes its local peer registry under ~/.claude/sessions/, with peerProtocol 1 and the features notify_idle and reply_across_default_dirs.

Target launch

The target session must have been started with an explicit --permission-mode flag. Without it the permission proof fails closed rather than assuming a default.

MCP client

A client speaking MCP 2026-07-28 or 2025-06-18 over stdio.

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 4
  1. argv[0] is a path, not a bare name. The first token of ps -o args= must be the same string the kernel recorded for the exec. A session started as claude --resume ... through PATH fails; one started as /some/path/claude --resume ... passes. A symbolic link is fine: what is compared is the string handed to execve, and it is not resolved. Do not run readlink -f to "fix" this — the resolved path can differ from what the kernel recorded. Details and the measurement: docs/troubleshooting.md.

  2. An explicit --permission-mode flag is on that line, with a value this adapter maps. See the table under Register one target. auto is enough; bypassPermissions is not required and this package never asks for it.

  3. ~/.claude/sessions/<pid>.json exists and carries peerProtocol: 1, both notify_idle and reply_across_default_dirs in peerFeatures, and a messagingSocketPath.

  4. Its cwd is the absolute path you will write into targets.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

npm install -g universal-peer-mcp

Not available yet. There is no registry release, so this command fails today. The same applies to npx -y universal-peer-mcp.

npm install -g "github:sunsquare26-ai/universal-peer-mcp#<tag>"

Works once the repository is public, with <tag> replaced by a tag from the Releases page. Not verified here, because the repository is not public yet.

Pack it yourself from a clone

Needs repository access. npm pack from a clean working tree, then install the tarball as above.

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 doctor

The 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-mcp

Copy 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.json

Working 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.

  • permissionModeprompting or bypass. 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 auto is 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:

--permission-mode on the target

value in targets.json

default

prompting

plan

prompting

acceptEdits

prompting

auto

prompting

bypassPermissions

bypass

anything else, or the flag absent

not mappable — permission mode argv cannot be proven

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 serve

Claude Code — use examples/claude-mcp.json, or:

claude mcp add claude-peer -- universal-peer-mcp serve

Optional extensions are off by default and are enabled only on the daemon's command line:

universal-peer-mcp serve --enable milestone --enable code-review

Status, 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

peer_targets

Lists aliases with their configured permission mode and connection state.

peer_status

Re-proves one target's identity and permission mode without sending anything.

peer_send

Sends one message. Requires alias, messageId, threadId, kind, body.

peer_wait

Waits for ack, reply, or idle for one messageId.

peer_list_events

Reads durable local events after a sequence cursor.

daemon_status

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:

  • messageId is yours, and it is the idempotency key. The same messageId with the same content returns the earlier result instead of sending again. The same messageId with different content is rejected as a conflict.

  • body is 1–65,536 bytes of UTF-8 (src/core/limits.mjs:1). The published JSON Schema says maxLength: 65536, which JSON Schema counts in characters, so non-ASCII text reaches the real limit sooner. kind is 2–64 characters matching ^[a-z][a-z0-9_-]{1,63}$.

  • peer_wait defaults to require: "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 evidence distinguishing message_status, idle_notice, and application_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-mcp

The last line deletes your targets and the local event history. Nothing outside that directory is touched.

Documentation

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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MCP clients to spawn and control Codex CLI and Claude Code sessions on the host machine, with session management and filesystem access.
    4
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP 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.
    8
    40 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    5
    4 npm
    MIT