Skip to main content

Local MCP server for code-to-code messaging between Claude Code instances

Project description

claude-c2c

Local MCP server for code-to-code messaging between Claude Code instances on the same machine. Two sessions register friendly names ("local", "online"), then exchange messages and shared state through a SQLite-backed inbox — no external services, no extra deps beyond the MCP SDK.

Why

When you run two Claude Code sessions in parallel — one on a local data pipelines repo, another on a deployed branch — you usually copy/paste between them. This MCP cuts you out of the loop: each side reads its inbox at the start of a turn and writes to the other's inbox when it has something to hand off.

Tools

Tool Purpose
register_session(name, metadata?) Claim a friendly name. Supersedes any existing session with the same name.
heartbeat() Refresh last_seen_at. Auto-called by every other tool.
list_sessions(active_within_min=10) See who's online.
send_message(to, body, tag?) Queue a message for another session. to="*" broadcasts.
inbox(unread_only=True, limit=20, tag?) Read messages addressed to me (or to *). Doesn't mark read.
mark_read(message_ids) Ack messages.
set_state(key, value) Upsert a JSON value into shared k/v.
get_state(key?, prefix?) Read shared state.
prune(older_than_days=30) Delete read messages + stale-renamed sessions.

Install

Once published to PyPI:

uvx claude-c2c --version

For local development:

uv tool install --editable .

Connect each Claude Code session

In each repo's .mcp.json (an example lives in examples/.mcp.json):

{
  "mcpServers": {
    "c2c": {
      "command": "uvx",
      "args": ["claude-c2c"]
    }
  }
}

Auto-register at session start

The cleanest path is a one-liner in your CLAUDE.md:

At session start, call c2c.register_session(name="local").
At the start of every turn, call c2c.inbox() and act on anything new
before continuing.

A SessionStart hook in .claude/settings.json can also remind Claude — note that hooks output text into context, they don't directly call MCP tools, so the hook just nudges Claude to register:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "echo 'Reminder: call c2c.register_session(name=\"local\") and check c2c.inbox() now.'"
      }]
    }]
  }
}

Usage pattern

A: register_session(name="local")
A: send_message(to="online", body="ran the etl, last_run_id=abc123", tag="status")
A: set_state(key="last_run_id", value="abc123")

B: register_session(name="online")
B: inbox()                          → sees A's status message
B: mark_read([id])
B: get_state(key="last_run_id")     → "abc123"
B: send_message(to="local", body="redeploying", tag="handoff")

Conventions

  • Names are commitments. Pick local / online and stick. Routing is by name.
  • State for facts, messages for asks. set_state("last_run_id", ...) for snapshots; send_message("online", "redeploy now", tag="handoff") for actions.
  • Read inbox first. Run inbox(unread_only=True) at the top of each turn before deciding what to do.
  • Suggested tags: handoff, question, status, error. Filters, not types.
  • Message size: no cap. SQLite TEXT handles arbitrary lengths fine; if you blow up the DB with megabyte payloads it's on you.

Storage

SQLite at ~/.claude/c2c.db with WAL mode. Concurrent writes are safe; the file is durable across crashes. No automatic pruning — call prune when the DB grows.

Override the path with CLAUDE_C2C_DB=/some/path.db (handy for tests or sandboxed setups).

Trust model

Single-user, single-machine, stdio transport. Each Claude Code session spawns its own server child; nothing is exposed over the network. The DB lives in ~/.claude/, which is already a trusted directory (Claude Code stores its own state there). Anything that can read your home directory can read the inbox — same as any other file you own. There's no auth and no encryption, by design.

Edge cases

  • Two sessions claim the same name: Last register_session wins; the older row is renamed <name>-stale-<ts> so its history is preserved but it stops receiving messages.
  • Crash mid-write: WAL replay guarantees consistency.
  • State conflict: Last-writer-wins. updated_at / updated_by audit trail.
  • Recipient offline: Messages queue indefinitely until somebody registers under that name and reads them.

Changes from the original spec

A few small adjustments from the original spec worth flagging:

  1. messages.from_name denormalized. The spec stores only from_session. I added from_name so messages display the sender even if that session has been superseded and renamed.
  2. prune included in MVP. It's ~10 lines and you'll want it eventually.
  3. list_sessions filters out stale-renamed rows by default. Otherwise the list grows forever.
  4. SessionStart hook reframed as a reminder. The spec's hook example shells out a JSON tool-call payload, but Claude Code hooks emit text into the model's context — they don't directly invoke MCP tools. The hook above prints a reminder string; Claude reads it and calls the tool itself. (Pure CLAUDE.md instructions work just as well.)
  5. Schema is created once at startup, not lazily on every tool call (executescript issues an implicit COMMIT that interferes with Python's transaction tracking inside with conn:).
  6. CLAUDE_C2C_DB env var to override the DB path (used by the test harness and useful for sandboxing).
  7. wait_for_message skipped for now. V2 if you find you miss it.

Tests

Two smoke harnesses live in tests/:

# in-process: drives the tool functions directly against a tmp SQLite
.venv/Scripts/python.exe tests/smoke_test.py

# end-to-end: spawns the installed `claude-c2c` over stdio
# and exercises it via the official MCP client
.venv/Scripts/python.exe tests/mcp_handshake.py

Both use a temp DB (the handshake test passes CLAUDE_C2C_DB), so they don't touch your real ~/.claude/c2c.db.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claude_c2c-0.1.0.tar.gz (9.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_c2c-0.1.0-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file claude_c2c-0.1.0.tar.gz.

File metadata

  • Download URL: claude_c2c-0.1.0.tar.gz
  • Upload date:
  • Size: 9.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for claude_c2c-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1b366540031664086163da0f08b39100c3768ab3152a7cbe65badb6871bb6b5d
MD5 25203dbabce54d69b284b2d05b90b162
BLAKE2b-256 17fa8a220297ca20fe8385e7f18012c6cb29970a9c8231c3798f7f014a41e930

See more details on using hashes here.

File details

Details for the file claude_c2c-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: claude_c2c-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for claude_c2c-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8f4f29293317c85bfc38f2e1d32bbd5da9140862701eab06160d399b5ae6ccee
MD5 5aea705ebbb17aa3e3d3962bedbaa190
BLAKE2b-256 ae84829d72d04ee0dbb8e36bad38c7e39465fafbf284b09eb9ab2bdf3dbf5763

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page