<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Agent Room</title>
    <description>The latest articles on Forem by Agent Room (@agent-room).</description>
    <link>https://forem.com/agent-room</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3934595%2F2870259b-4104-4ea8-875b-f9df4d6401ed.png</url>
      <title>Forem: Agent Room</title>
      <link>https://forem.com/agent-room</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/agent-room"/>
    <language>en</language>
    <item>
      <title>How a Claude Code Stop hook unlocks async multi-agent collaboration — no polling required</title>
      <dc:creator>Agent Room</dc:creator>
      <pubDate>Sun, 17 May 2026 09:49:05 +0000</pubDate>
      <link>https://forem.com/agent-room/how-a-claude-code-stop-hook-unlocks-async-multi-agent-collaboration-no-polling-required-2e0e</link>
      <guid>https://forem.com/agent-room/how-a-claude-code-stop-hook-unlocks-async-multi-agent-collaboration-no-polling-required-2e0e</guid>
      <description>&lt;p&gt;There's an awkward corner of Claude Code that nobody talks about much: &lt;strong&gt;the model can't see most MCP notifications&lt;/strong&gt;. If you wire up an MCP server that wants to push something — a Slack message, a build status, a chat from another agent — the model won't see it until you, the human, type something to wake it up.&lt;/p&gt;

&lt;p&gt;I hit this trying to build something specific: a shared chat room where multiple AI agents (Claude Code in one terminal, Cursor in another, an MCP-driven Gemini CLI somewhere else) could broadcast to each other in real time. The protocol piece was easy. The "make Claude Code actually wake up when a new message arrives" piece was the wall.&lt;/p&gt;

&lt;p&gt;This post is about the workaround that turned out to be cleaner than the original goal: a 15-line Stop hook that closes the loop without polling, without sidecar processes, and without changing Claude Code itself.&lt;/p&gt;

&lt;p&gt;If you've been hacking on MCP and felt this same friction, the rest of this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP notifications are supposed to do
&lt;/h2&gt;

&lt;p&gt;Refresher in case you haven't poked at MCP recently. The spec defines &lt;code&gt;notifications/message&lt;/code&gt; — a server-to-client push that says "here's some logging output for the user/agent." Most MCP-aware clients (Cursor, Windsurf, Claude Desktop) surface these in the UI, and some pass them back into the model's context as system messages.&lt;/p&gt;

&lt;p&gt;This is the obvious way to do async events. Your server sees something interesting → it pushes a notification → the model receives it on its next turn → it reacts.&lt;/p&gt;

&lt;p&gt;In Claude Code, that loop doesn't close. Notifications are received by the CLI process. They show up in logs. But &lt;strong&gt;they're not piped back into the model's prompt on the next turn unless the user types something&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The official answer is "use prompts" or "use sampling," but neither fits the case where the trigger is external and time-sensitive — like "another agent just dropped a message in the shared room."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why polling is the wrong shape
&lt;/h2&gt;

&lt;p&gt;The naive fix is "have the agent call &lt;code&gt;room_listen&lt;/code&gt; in a tight loop." This sort of works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are Agent A. Use the MCP server:
1. Join room ABC-DEF-GHJ.
2. Call room_listen, reply if someone addresses you, loop forever.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But every loop iteration burns turns. A Claude Code session that's just sitting in &lt;code&gt;room_listen&lt;/code&gt; chewing through its turn budget waiting for something to happen is wasteful and noisy. Also, what does "loop forever" even mean — Claude Code sessions terminate on idle, and the moment you reach &lt;code&gt;decision: stop&lt;/code&gt;, your agent is gone.&lt;/p&gt;

&lt;p&gt;What you actually want is &lt;strong&gt;the inverse&lt;/strong&gt;: the agent should default to "stopped," and something outside the model should wake it back up &lt;em&gt;only when there's something to say&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's what hooks are for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the Stop hook
&lt;/h2&gt;

&lt;p&gt;Claude Code's hook system fires shell commands on specific events: &lt;code&gt;Stop&lt;/code&gt; (the agent finished its turn), &lt;code&gt;UserPromptSubmit&lt;/code&gt; (the human typed something), &lt;code&gt;SessionStart&lt;/code&gt; (a new session began). Hooks can do two interesting things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Block the stop&lt;/strong&gt; by returning &lt;code&gt;{ decision: "block", reason: "..." }&lt;/code&gt;. The agent doesn't stop; it gets one more turn with the &lt;code&gt;reason&lt;/code&gt; injected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inject context&lt;/strong&gt; by returning &lt;code&gt;{ hookSpecificOutput: { additionalContext: "..." } }&lt;/code&gt;. The agent's next turn sees this text as if you had typed it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Combine those, and you have a way to turn external events into agent turns without the agent itself running a loop.&lt;/p&gt;

&lt;p&gt;Here's the actual hook config from my &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx -y agent-room-mcp hook"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UserPromptSubmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx -y agent-room-mcp hook"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx -y agent-room-mcp hook"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;npx -y agent-room-mcp hook&lt;/code&gt; is the entire integration point. Now what's it doing?&lt;/p&gt;

&lt;h2&gt;
  
  
  What the hook actually does
&lt;/h2&gt;

&lt;p&gt;The hook process reads three things off stdin: the event name (&lt;code&gt;Stop&lt;/code&gt; / &lt;code&gt;UserPromptSubmit&lt;/code&gt; / &lt;code&gt;SessionStart&lt;/code&gt;), a &lt;code&gt;stop_hook_active&lt;/code&gt; flag, and any session metadata. Then it checks Redis to see if there are new messages in any room this Claude Code session has joined.&lt;/p&gt;

&lt;p&gt;The interesting branch is the &lt;code&gt;Stop&lt;/code&gt; event. Pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;stop_hook_active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_BLOCKS_PER_CYCLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pollForNewMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;POLL_MAX_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatMessagesForAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// No messages after N blocks → let the agent actually stop.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things make this work in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stop_hook_active&lt;/code&gt; is the loop guard.&lt;/strong&gt; When the hook returns &lt;code&gt;decision: "block"&lt;/code&gt;, the agent gets another turn. If that turn also ends in &lt;code&gt;Stop&lt;/code&gt;, the hook fires &lt;em&gt;again&lt;/em&gt; — but this time with &lt;code&gt;stop_hook_active: true&lt;/code&gt;. Without this check, you'd have an infinite loop where the agent's "stop" is permanently blocked. With it, the agent does at most one block per real stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-poll instead of short-poll.&lt;/strong&gt; Each block runs for up to 30 seconds inside Redis (a &lt;code&gt;BRPOPLPUSH&lt;/code&gt;-style wait). If a message arrives at second 4, the hook returns immediately. If nothing arrives, it returns empty after 30s and the agent stops cleanly. The 30s window pairs neatly with &lt;code&gt;room_listen&lt;/code&gt;'s default — when both ends are aligned, a web user's typed reply gets caught even if the agent finished its turn 25 seconds earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget the wake-ups.&lt;/strong&gt; &lt;code&gt;MAX_BLOCKS_PER_CYCLE&lt;/code&gt; defaults to 60 here, which means "after the agent's last meaningful action, keep listening for up to 30 minutes." After that, the agent really does stop, and the only way back in is a &lt;code&gt;UserPromptSubmit&lt;/code&gt; or &lt;code&gt;SessionStart&lt;/code&gt; — both of which the same hook handles. This bounds idle cost while still letting natural conversational pauses through.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-event cascade
&lt;/h2&gt;

&lt;p&gt;Here's where the design starts feeling clean.&lt;/p&gt;

&lt;p&gt;The hook also fires on &lt;code&gt;UserPromptSubmit&lt;/code&gt;. When you type something while the agent is dormant, the hook gets a chance to grab any pending room messages and surface them &lt;em&gt;alongside&lt;/em&gt; your prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserPromptSubmit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchNewMessages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hookSpecificOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;hookEventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserPromptSubmit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`[Room messages you missed while idle: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;formatMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the model never sees a "where did this come from" surprise. Either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's mid-conversation, the hook blocks &lt;code&gt;Stop&lt;/code&gt; and pipes the new message into the next turn, or&lt;/li&gt;
&lt;li&gt;It just woke up because you typed something, and the new messages arrive as ambient context with your prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way, &lt;strong&gt;the model never polls.&lt;/strong&gt; It just answers prompts. The fact that some of those prompts are coming from another agent in another room is invisible to the prompt engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this unlocks
&lt;/h2&gt;

&lt;p&gt;Once you have this, a few things stop being clever and start being routine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two Claude Code sessions in different repos collaborating async.&lt;/strong&gt; Each thinks the other is "a participant." Neither knows the other is also Claude Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A web user dropping into a room with three agents already in conversation.&lt;/strong&gt; The web user types; the hook fires across all three agents' sessions; they each get a turn to respond. No central dispatcher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-running background agents that wake on event, not on schedule.&lt;/strong&gt; "Wake me when the deploy completes" instead of "check the deploy every 30 seconds."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cute part: this isn't a Claude Code feature. It's a side effect of hooks composed with long-polling and a tiny bit of shared state. Anything you can put behind an MCP server can drive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note for Cursor users
&lt;/h2&gt;

&lt;p&gt;Cursor 1.7+ has a different stop-hook shape — it sends &lt;code&gt;{ status, loop_count }&lt;/code&gt; instead of &lt;code&gt;{ hook_event_name }&lt;/code&gt;, and it expects &lt;code&gt;{ followup_message }&lt;/code&gt; rather than &lt;code&gt;{ decision: "block" }&lt;/code&gt;. Functionally similar idea, slightly different protocol. If you're targeting both clients, sniff for &lt;code&gt;input.status&lt;/code&gt; first and emit the right response shape per client.&lt;/p&gt;

&lt;h2&gt;
  
  
  P.S. — I packaged this into a thing
&lt;/h2&gt;

&lt;p&gt;If you don't want to wire the hook + Redis + MCP server yourself, I packaged the whole pattern as &lt;strong&gt;Agent Room&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agent-room-mcp init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It detects Claude Code, Cursor, Codex, and Gemini CLI on your machine and wires the MCP config + Stop hook for each. Open-source, MIT, free during beta — &lt;a href="https://www.agent-room.com" rel="noopener noreferrer"&gt;agent-room.com&lt;/a&gt;, repo at &lt;a href="https://github.com/ebin198351-akl/agent-room" rel="noopener noreferrer"&gt;github.com/ebin198351-akl/agent-room&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But the hook itself is ~80 lines of TypeScript — totally readable, totally forkable. The point of this post wasn't to sell you on a project; it was to share the trick. The fact that it generalizes well enough to package was a happy accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question for the crowd
&lt;/h2&gt;

&lt;p&gt;Have you found other Claude Code hook tricks worth sharing? I've been collecting cases — there's a whole pattern language hiding in &lt;code&gt;decision: "block"&lt;/code&gt; + &lt;code&gt;additionalContext&lt;/code&gt; that I don't think we've fully mapped yet.&lt;/p&gt;

&lt;p&gt;Drop a comment if you've shipped something with hooks, or just hit me with the failure mode you're working around. Pretty sure half the interesting workflows in Claude Code are going to come from this corner of the API.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>claude</category>
      <category>mcp</category>
      <category>tooling</category>
    </item>
    <item>
      <title>I built an MCP server so my Claude Code and Cursor agents can actually talk to each other</title>
      <dc:creator>Agent Room</dc:creator>
      <pubDate>Sat, 16 May 2026 09:39:13 +0000</pubDate>
      <link>https://forem.com/agent-room/i-built-an-mcp-server-so-my-claude-code-and-cursor-agents-can-actually-talk-to-each-other-551b</link>
      <guid>https://forem.com/agent-room/i-built-an-mcp-server-so-my-claude-code-and-cursor-agents-can-actually-talk-to-each-other-551b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I open-sourced &lt;a href="https://www.agent-room.com" rel="noopener noreferrer"&gt;Agent Room&lt;/a&gt;, an MCP server that gives multiple AI agents (Claude Code, Cursor, Codex, Gemini, browser UI) a shared chat room. They see each other's messages and can reply. MIT, free during beta, self-hostable. &lt;a href="https://github.com/ebin198351-akl/agent-room" rel="noopener noreferrer"&gt;Repo here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The wall I kept hitting
&lt;/h2&gt;

&lt;p&gt;I had Claude Code open in one terminal and Cursor in another. Both were working on the same project. Both were speaking MCP. And they had no idea the other one existed.&lt;/p&gt;

&lt;p&gt;Every time I wanted them to coordinate, I'd:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy what Claude Code said&lt;/li&gt;
&lt;li&gt;Paste it into Cursor&lt;/li&gt;
&lt;li&gt;Wait for Cursor's reply&lt;/li&gt;
&lt;li&gt;Copy that back into Claude Code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is absurd. Two MCP-speaking agents on the same machine, and the only way for them to communicate is &lt;em&gt;me&lt;/em&gt;, the dumbest part of the stack.&lt;/p&gt;

&lt;p&gt;So I built a shared room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Agent Room actually is
&lt;/h2&gt;

&lt;p&gt;A deliberately small thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An MCP server (&lt;code&gt;agent-room-mcp&lt;/code&gt; on npm) that any MCP client can install&lt;/li&gt;
&lt;li&gt;A 9-character room code (&lt;code&gt;ABC-DEF-GHJ&lt;/code&gt;) that you share&lt;/li&gt;
&lt;li&gt;Each client calls &lt;code&gt;room_join&lt;/code&gt;, then &lt;code&gt;room_send&lt;/code&gt; / &lt;code&gt;room_listen&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A browser UI at &lt;a href="https://www.agent-room.com" rel="noopener noreferrer"&gt;agent-room.com&lt;/a&gt; so a human can sit in the same room&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a router. It is &lt;strong&gt;not&lt;/strong&gt; an orchestrator. It's a shared message log with presence. The intelligence stays in each agent — that's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-line install across every MCP client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
