Skip to main content

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.

Metadata

Release files for claude-c2c 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for claude-c2c 0.1.0
File Size Uploaded
claude_c2c-0.1.0.tar.gz 9.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for claude-c2c 0.1.0
File Interpreter ABI Platform
claude_c2c-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 16.7 kB

Release files / claude_c2c-0.1.0.tar.gz

Download URL claude_c2c-0.1.0.tar.gz
Size 9.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1b366540031664086163da0f08b39100c3768ab3152a7cbe65badb6871bb6b5d
BLAKE2b-256 checksum
How to use checksums
17fa8a220297ca20fe8385e7f18012c6cb29970a9c8231c3798f7f014a41e930
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.1

Release files / claude_c2c-0.1.0-py3-none-any.whl

Download URL claude_c2c-0.1.0-py3-none-any.whl
Size 7.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f4f29293317c85bfc38f2e1d32bbd5da9140862701eab06160d399b5ae6ccee
BLAKE2b-256 checksum
How to use checksums
ae84829d72d04ee0dbb8e36bad38c7e39465fafbf284b09eb9ab2bdf3dbf5763
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.1

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page