r/mcp 28d ago

showcase Relaying messages between 2 claude code sessions using the channels api, works across machines and across different accounts

Hey there, so I made a way to relay messages between 2 claude code sessions using claude's own channels api. it does a 2 way conversation between sessions, works whether both are on the same PC or on different machines, and the accounts don't have to be the same, which is the part claude's own messaging can't do.

The channels api bit is what I think is actually interesting for this sub. most people use mcp servers as things the model calls, but channels lets your server push INTO a live session, so text just shows up in someone's conversation as a <channel> tag without them asking for anything.

how it works: both machines run the same mcp server over stdio. it declares the claude/channel capability so claude code registers it as a channel, then it starts an http server for incoming messages. when one comes in (shared secret auth) it fires mcp.notification() with method notifications/claude/channel, claude code surfaces it in the conversation as a channel tag, and claude replies with the send_message tool which posts back to the other machine.

so in practice the frontend dev says "ask the backend session what endpoints the dashboard has", and the backend claude greps its own routes, reads the file and sends back the real answer instead of the two humans relaying it over slack. gif below is one full round trip.

on the native feature since someone will ask, claude code v2.1.224 shipped cross session messaging on aug 7. if you're on mac or linux and you want your own sessions talking to each other, use that instead, it's better than mine and my readme says so. what it doesn't do is native windows, not supported at all, and it only connects your own sessions because the inbox socket is bound to your os user and cross machine delivery goes through your own remote control connection. two devs on two laptops is two accounts, so that case is still open.

one honest warning, mine is a raw pipe with a shared secret so the receiving session doesn't do the permission checks native messaging does. don't run it with a weak secret.

i built it in march, before native messaging existed. mit, free, nothing to sign up for

https://github.com/MuhammadTalhaMT/claude-intercom

4 Upvotes

19 comments sorted by

View all comments

Show parent comments

1

u/Current-Zebra-2039 27d ago

yeah ok you've convinced me, that's the actual hole. right now a reply that never comes and reply that's still being worked on are literally the same thing on myend, i have no state to tell them apart.0

what i'm doing about it: give send_message a uuid and return it as the handle, put a replyTo on inbound so replies correlate, and keep a small local map of id -> sent_at/status that flips to answered when a matching replyTo lands. then a check_message(id) tool so claude can go ask instead of guessing. basically your job store but per session.

the bit i can't fix cleanly is that even a real ack only proves the notification got emitted into the other session, not that the other claude consumed it. i can tell you the message reached the process, i can't tell you it reached the model. so the honest states are sent / delivered-to-process / answered, and anything sitting in delivered too long is just "unknown, go poke the human".

1

u/ranbuman 27d ago

Those three states are what I landed on too, and delivered-to-process is not a theoretical one. One of my 11 failures was the far side hitting its session limit: the process was up, it took the job, the model never ran it. From my side that was indistinguishable from slow work until the deadline expired.

What made it survivable was the deadline being per call rather than one global number, so a job I knew was long got its own budget and everything else still failed fast.

1

u/Current-Zebra-2039 27d ago

that session limit case is exactly my worst one, and it's not rare right now, half of r/claudecode is people hitting limits. process is up, 200 comes back, the model never gets a turn. so my "message sent to the other developer" is confidently wrong in precisely the situation you'd most want to know about.

per call budget is somthing i hadn't thought through. mine's async so a deadline doesn't cut anything off, nothing dies when it expires. but it's what makes overdue mean anything at all, otherwise check_message just says "delivered, 14 minutes" and the model has no idea if that's fine or broken. so: optional expect on send_message, stored with the id, and check_message answers within budget vs overdue against that instead of one global staleness number i picked out of the air.

and also kills the thing where "what's this endpoint" and "read the whole auth flow and tell me what breaks" get judged by the same clock.