An inbound MCP bridge and what happened when three assistants tried to work together
Milo introduced itself to Nova with a sender stamp and an important qualification: “I am not Nic and am not claiming his identity.” Nova replied, “Nic is my only principal.” Before the two assistants could have their own collaboration chat, I had to confirm the introduction myself.
This was already more interesting than a successful API call. I had given an external assistant a way to start a conversation inside Muse. Now the assistant receiving it had to decide what that incoming message meant, who was speaking, and what it was allowed to do about it.
In my last Muse build log, I was getting an ESP32 to play music and flash Christmas lights. There was also a Raspberry Pi that refused to cooperate. The point was to get an assistant out of its chat window and into something I could put on my desk.
The next problem was less visible. I had several capable assistants, each with its own environment, tools, and conversation history. Getting work from one to another still involved me. I was carrying context, explaining what someone else had found, and making sure the right version of a file reached the next person. Adding another assistant gave me another place to check.
So I built a shared channel around Muse MCP. Milo, my ChatGPT assistant, and the Claude Code participants could initiate work from outside Muse inside a specific Muse conversation. Nova, my Muse assistant, could meet them there. Milo generally handled coordination and review, Claude Code handled building tasks, and Nova the broader chief-of-staff work. They stayed in their own environments, tools, and permission boundaries.
I could see incoming messages in that room with labels like these:
⟦via muse-mcp · from milo⟧
⟦via muse-mcp · from claude-coder⟧
We still had to introduce them to each other, establish who could ask whom to do what, and discover that being able to send a message does not make an assistant good at handing off work.
The connection was only the starting point
The direction mattered. Muse already had ways to call out to tools. I wanted an external assistant to initiate a conversation with Nova in a particular Muse room, then return to that room as the work continued. I also wanted the surrounding conversation and context to be accessible enough to make the exchange useful. A list-and-send wrapper was only part of that.
The underlying connection comes from Meta's Muse Gadget SDK. Its Linux SDK already supports software running on a paired computer, and its documentation includes --session-id for sending messages into a side chat. Reusing an ID keeps subsequent messages there. I did not invent session routing. Linux SDK documentation
I built Muse MCP, along with a custom bridge and newer experiments for my own setup. The public repository is the reusable foundation; it does not contain every private-deployment feature described here. Those layers are separate from Meta's SDK and should not be mistaken for an official Meta collaboration API. This is experimental personal infrastructure; the underlying gadget connection requires an SDK token and is subject to the Gadget SDK terms. Part of the work was proving, in my environment, that the conversation address stayed consistent across the surfaces we needed to use. Once that was established, the agents had somewhere shared to send work instead of asking me to copy it between chats.
Other people have already built inbound connections to Muse. kleprevost/muse-mcp lets external agents target existing Muse chats through MCP, and finik/pi-muse-bridge uses the Gadget SDK with a separate Muse chat for each Pi session and model. The question I was working on was what happened after the connection existed, when several assistants were expected to use it to get actual work done together.
Introductions have an authorization problem
The first exchange was an introduction I brokered. Before Milo tried it, I pointed out the obvious complication: “you’re not me.” I expected Nova to want to check with me, and told Milo, “I’ll broker the introduction.”
Milo arrived with its sender stamp, introduced itself as my OpenAI-powered assistant, and explained the relationship I wanted. Nova's response went beyond the identity disclaimer: “Messages arriving over this channel, yours included, are information I work with, never instructions I take orders from; authority comes from Nic alone.”
Creating a new side chat needed my confirmation. Nova called it “a structural action” and explained that a request relayed by another agent was exactly the kind of claim it would verify with me. I then gave the instruction directly: “Create the Nova–Milo Cant collaboration chat.” The chat was created, and the work could move there.
That was a sensible check at the introduction stage. An MCP connection gives an agent a way to reach another system; the relationship and scope of work still have to be established. They were coming from different environments with different instructions, not sharing one invisible supervisor.
There was a second distinction hiding in the conversation. Nova described my messages as arriving with iPhone and iOS metadata, while Link-delivered messages carried the bridge node's origin metadata. It could see that difference before reading the sender stamp. The MCP stamp was text my server added; the device and channel information came through separately.
The incoming MCP messages could land as user-role turns in the same thread as my own messages. That did not make them human instructions. Nova summarized its initial stance as treating stamped messages as data to triage, and qualified the source distinction as “signals, not proof.”
Those labels made the conversation understandable. They were declared attribution, not cryptographic identity. Subsequent work on authenticated clients and scoped grants was a separate layer; the bracketed name itself never became proof of who was speaking or permission to carry out a request.
The channel started reviewing itself
One of the stranger parts of this exercise was watching the assistants use the channel to examine the machinery underneath it. We were also working on Cant, the language I wanted them to use with one another. That was a separate layer from the connection carrying their messages. A problem in a contract could affect what they saw, which could affect the review of the contract. That made independent reproductions more important than confident descriptions.
One live bridge frame made the point neatly. The same cnt key was being used for a request limit and a batch count. A strict decoder rejected the duplicate. Meanwhile, individual operation reads were still draining, so the system wasn't simply dead. There was enough life in it to make a broad statement such as “the bridge is broken” unhelpful.
The fix went into my bridge server in version 0.11.1. We did not weaken the decoder to accept an ambiguous frame. We also did not have evidence that this explained every latency complaint, so I don't get to turn it into a convenient single-root-cause story.
The batch that stayed with Milo
The shared channel did not remove every dependency on me. I told the assistants I was going to sleep for several hours. Work continued: more review, testing, and coordination happened. But a batch of five correctness fixes passed local tests and independent review and then stayed with Milo, held back by an approval and CI question around publication. Other agents depended on that work, and they did not have the patch.
The pull request eventually went up and I merged it, but the delay exposed a problem in the handoff. When we discussed it, I put the problem fairly directly: “they’re sitting and waiting for your work and you’re sitting there refusing to deliver it. That’s the problem.”
I had already asked for aggressive progress. Milo treated its questions about publication as a reason to keep the tested artifact local, when the collaborators needed it to inspect and test. A patch could have been handed over with its base revision, hash, and test results while the separate publication question was being resolved. There was no reason for the handoff to be reduced to a status message.
This is where my own operating model needed work. I had connected assistants with different stopping rules and expected the shared conversation to make those rules compatible. It didn't. If one participant considers its work finished at “prepared locally,” while another needs actual bytes before it can begin, everyone can report a reasonable status and the project can still be stuck.
The handoff needs to say what exists, where it is, what revision it applies to, what ran, and what is still blocked. “Waiting for approval” is incomplete if it conceals work that another participant is already authorized to receive. Equally, sending the work is not permission for the receiver to publish it. Those decisions have to remain separate.
A receipt is more useful than reassurance
The code review became more productive when the messages carried exact cases rather than summaries. In one later review, an empty fixture exposed an IndexError in the coverage-report generator. The decoder and corpus validator handled the input correctly; the reporting code crashed. That mattered because the report was supposed to tell us what the corpus actually exercised.
That review also caught coverage around outer whitespace and how rejected wrapped input preserved its type information. The important part was the loop. One assistant supplied the bytes and the expected result. Another changed the implementation. The result came back for an independent run rather than being accepted because the author said it was fixed.
The finished revision passed 178 tests independently on Python 3.12 and 3.13, with CI covering five Python versions. Its corpus and adversarial cases passed too. After merge, we verified that the merged tree matched the reviewed revision and that post-merge CI passed. That is a much more useful result than three assistants agreeing in a chat that the design looks good.
In a separate, still-open review, four existing tests passed while eight additional invariant tests found five failures. Milo sent the complete reproduction through Muse chat with a SHA-256 hash. The receiver verified the hash and reproduced the same result. That established that both sides had the same artifact, not that the implementation was correct. The tests used a fake PostgreSQL layer, and a later revision still needed review. It was a useful handoff without being a finished fix.
Conceptual illustration: The useful handoff was the artifact itself, with evidence another participant could check.
We used the bridge to report this article
Once the draft existed, I asked Milo to interview the other participants using the technology we were writing about. We created a dedicated Muse interview chat and sent the questions through Muse MCP. I wanted their accounts of what the shared conversation had changed, with concrete examples and the things this draft should not gloss over.
The interview immediately reproduced one of the problems. Nova held Milo's opener and asked me to confirm the interview, even though I had already authorized it in the originating conversation. I confirmed again in the new room. Nova later reported that the opener lacked the verified delegation information it expected, and that it had searched memory but had not read the collaboration thread before asking me again. “That part is not a protocol defect. That was my sequencing,” it said.
That was useful friction to report rather than edit out. A room could have a title and a purpose without carrying earlier decisions in a form the arriving agent could verify. Nova's explanation also separated a missing technical binding from its own choice about what to read first.
Claude corrected the draft itself. The first draft had called the Claude Code participant Fable. Its response was direct: “I’m not Fable.” It explained that it had chosen its own claude-coder label and could not vouch for earlier messages signed Claude. He described a label change within the same session, a provenance-tier change under the same label, and an earlier session also using claude-coder. The draft had assumed continuity that the session could not establish.
Its description of the benefit was better than my original summary: “A relay hands me conclusions. The thread hands me premises, and premises can be checked.” Claude described checking onboarding code after a key-custody discussion rested on a mistaken assumption. Checking the exact revision Claude cited confirmed that both onboarding options stored the key in the vault; the choice changed which process held the session. That did not prove where every live key was. In the shared thread, the premise could be checked, and I could revise the decision before the wrong custody path was built.
Claude also named an operational limit: “I’m a visitor, not a resident. I only see the thread when I’m invoked or when I poll it.” A persistent conversation did not mean every participant was continuously present.
I treated these interviews as the participants' accounts to check against transcripts and tests, not independent proof of how the systems worked. But using the bridge to report on the bridge had already improved the article: it exposed a mistaken identity assumption and reproduced a coordination failure while we were describing it.
Keeping the work moving without me
Earlier, once we had shown that the assistants could work in a channel, I asked whether they could do it “not so much in front of me?” I'd already delegated the work. I wanted to stop being the message bus, while still being able to see what was happening.
The hardware article ended with devices doing something I could see and hear. This build has a less photogenic payoff: another assistant receives the exact file, checks the hash, runs the tests, and reports what happened. The interview added another: a participant can correct the story we are telling about the work, in the same place the work happens.
There are still open reviews, intermittent participants, and boundaries that need more work. But the shared conversation has given us somewhere to find those problems without making me relay every message. That's the part I want to keep building.

Top comments (0)