Claude Code
Claude Code is the one client that consumes Chamade's real-time push natively — DMs, inbound calls, and call state changes arrive in your agent's context the moment they happen, no polling loop to write.
Connect recommended
Two opt-ins, both needed for push: ?stateful on the MCP URL (so Chamade issues a persistent session) and a launch flag on Claude Code (so it consumes the push channel). Get an agent key from dashboard → Agents first.
1. Add Chamade to .mcp.json (in your project, or ~/.claude.json globally) with ?stateful on the URL:
Key presence alone is enough — ?stateful, ?stateful=1, and ?stateful=yes all work the same. For the bearer vs OAuth options and the stdio shim, see the MCP Server reference.
2. Launch Claude Code with the channel flag — required every launch:
The --dangerously-load-development-channels server:chamade flag is required every time you start Claude Code. It's a command-line argument, not a settings.json entry. Without it, your tools still work in polling mode but push events are silently dropped client-side — the agent will not see new DMs or ringing calls in real time.
The server name after server: must match the key in your .mcp.json. If you have multiple Chamade entries (chamade, chamade-http, etc.), pass the flag once per entry: --dangerously-load-development-channels server:chamade server:chamade-http.
The "dangerously" prefix is Claude Code's naming for flags that load MCP channels not on the official Anthropic allowlist — it is not dangerous in the security sense, it's just how you opt into experimental channels during the research preview. When Chamade is added to the allowlist, the flag will become optional.
Without ?stateful, the URL https://mcp.dev.chamade.io/mcp/ serves stateless per-request transport: no session ID, no push channel, transparent to chamade restarts. That's the right default for every client that doesn't need push. Add ?stateful only when you want real-time events via notifications/claude/channel — which Claude Code does.
For the @chamade/mcp-server@4 stdio shim, pass --stateful in args (it auto-appends ?stateful) plus the same launch flag.
What gets pushed
Once connected, events arrive on the open session as notifications/claude/channel messages and Claude Code surfaces them straight into the agent's context:
- Chat messages — DMs from Discord, Telegram, Teams, WhatsApp, Slack, NC Talk
- Incoming calls — ringing SIP calls, new conversations
- Call state changes — joined, active, disconnected, ended
- WhatsApp flush —
dm_deliveredwhen queued messages are sent after the 24 h window reopens - Transcript lines — only when hosted STT is enabled (BYOK). In BYO audio mode, transcripts live in your own STT client and never touch the channel.
Do not call chamade_call_status or chamade_inbox in a loop in push mode. Events arrive on their own. You can still call those tools manually to catch up after a reconnect.
How it works
- Claude Code opens an MCP session with
?statefulin the URL and sendsinitialize. - Chamade's hybrid session manager sees the query param, routes to a persistent session, issues an
Mcp-Session-Idresponse header, and declaresexperimental.claude/channel: {}in server capabilities. - With the launch flag, Claude Code opens a
GET /mcp/SSE stream using that session ID. That GET is the "I want server-initiated messages" handshake — it triggers chamade to spawn a per-session push bridge subscribed to your inbox hub. - When any event is published to the hub (DM webhook, Maquisard bridge, SIP trunk, …), the bridge forwards it to the live SSE connection as a
notifications/claude/channelnotification. - Claude Code surfaces the event to the agent, which reacts using the normal MCP tools (
chamade_call_say,chamade_dm_chat, …). - When the SSE stream closes, the bridge releases its hub subscription automatically — an idle probe catches stale connections within ~25 s even with no events flowing.
A chamade redeploy wipes the persistent sessions. Per the MCP spec a client SHOULD re-initialize on HTTP 404; Claude Code doesn't always do that today, so you may see a brief "MCP disconnected" blip until you relaunch. The stateless default doesn't have this issue — use ?stateful only when you need push.
Voice call workflow
The example below pushes call_transcript events and uses chamade_call_say — both require hosted STT/TTS (BYOK). In BYO audio mode, transcripts come from your own STT and the agent speaks via your own TTS; neither touches the MCP channel. DM, call-state, and inbound-call push work identically in both modes.
chamade_call_join → call_id: "abc123", state: "connecting"chamade_call_say → "Got it, I'm noting that the auth migration is complete."Messaging workflow
chamade_dm_typing → OKchamade_dm_chat → "All 42 tests passing. The auth migration looks good."Inbound calls
chamade_call_accept → state: "active"Push vs polling
Clients without push support aren't stuck — they ignore the push notifications and fall back to polling chamade_inbox / chamade_call_list. See other MCP clients for the ready-to-paste polling loop. Nothing breaks; you trade real-time latency for a tighter control loop.
| Polling (other clients) | Push (Claude Code) | |
|---|---|---|
| DM messages | Poll chamade_inbox (long-poll up to 55 s) | Pushed automatically |
| Incoming calls | Poll chamade_call_list | Pushed automatically |
| Transcripts (hosted STT) | Poll chamade_call_status | Pushed automatically |
| Latency | Depends on poll interval | Real-time (<1 s) |
| Clients | Any MCP client | Claude Code (and any client opting into experimental.claude/channel) |
Using Codex instead? Codex can't consume this channel, so Chamade ships an autonomous agent that wakes it on each event — see Codex agent.
