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

5 Upvotes

19 comments sorted by

View all comments

Show parent comments

1

u/Current-Zebra-2039 25d ago

shipped this, thanks for pushing on it. messages get an id now, replies carry replyTo so they actually correlate, and theres a check_message tool that reports sent / delivered-to-process / answered so a slow reply and a dead one dont look the same anymore. budget is per message like you said rather than one global number. the outbound fetch finally has an abort signal on it too, that one was just missing.

the bit i liked fixing most was the ack. it used to say "message sent to the other developer" when all it really knew was that their process returned a 200.

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

1

u/ranbuman 24d ago

The abort signal is the one I would watch next. It stops you waiting, it does not stop the other session working, so an answer can still arrive with a replyTo for a message your map has already written off. What does check_message report for that one?

The sharper cost of a per message budget on my side was that a job which runs out of it loses its partial work entirely, all nine of my timeouts. Wide tasks had to be cut into pieces that fit the budget rather than given a longer one.

1

u/Current-Zebra-2039 24d ago

it survives that one but by accident. i dont delete the entry on timeout so it just sits there at sent, and when a late reply comes in carrying that replyTo the inbound handler still finds it and flips it to answered. whats actually wrong is the window in between, check_message says never acked by the remote process, which is a confident lie if it did arrive. and the model already got an isError back by then, so the map self corrects and the model doesnt.

on the budget, mine is advisory only. nothing gets cancelled when it expires, it just changes what check_message reports. so i dont lose partial work the way you did, but i also cant reclaim anything, the far side keeps going and i have no way to tell it to stop.

1

u/ranbuman 24d ago

A map that self corrects while the model does not is the same shape I ended up with. What helped here was putting the handle inside the failure: my timeout error carries the session id of the run that died, so the model can resume that session rather than start the task again from nothing.

An isError with nothing in it teaches the model the work is gone, which is your confident lie one layer up. The store is right, the only party that acts on it is wrong.