diff --git a/content/docs/meta.json b/content/docs/meta.json index 9c3abf8..c823372 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -12,6 +12,7 @@ "channels", "schedules", "runtime-mcp", + "runtime-mcp/dev-channels-flag", "external-agents", "tokens", "api-reference", diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index 08c5e9e..4f4de19 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -264,6 +264,11 @@ wheel ships the wire shape correctly, but a standard `claude` launch without the flag silently drops the notification — which is why the poll path has to be the floor. +See [Dev-channels flag — tagged-form requirement](/docs/runtime-mcp/dev-channels-flag) +for the exact form the flag must take, the failure mode when it's +wrong, and when operators need to set it manually vs. when the +hosted SaaS / workspace template handles it for them. + Since Claude Code 2.1.x the flag takes a tagged allowlist, not a bare switch. Pass each MCP server you want to push from as `server:` (matching the name you registered the server under in Claude Code's diff --git a/content/docs/runtime-mcp/dev-channels-flag.mdx b/content/docs/runtime-mcp/dev-channels-flag.mdx new file mode 100644 index 0000000..b9597a4 --- /dev/null +++ b/content/docs/runtime-mcp/dev-channels-flag.mdx @@ -0,0 +1,176 @@ +--- +title: "Dev-channels flag — tagged-form requirement" +description: "Why Claude Code 2.1.x+ requires `--dangerously-load-development-channels server:molecule` (not the bare flag) to enable inline channel push from the molecule-mcp wheel." +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +The `molecule-mcp` wheel emits a JSON-RPC `notifications/claude/channel` +notification on every inbound A2A message so Claude Code can render it +as an inline `` synthetic user turn — zero polling, zero +per-turn stall. During the channels research preview, Claude Code only +processes that notification when the host is launched with the +`--dangerously-load-development-channels` flag *and the flag carries a +matching tagged allowlist entry*. + +This page covers the form that flag must take, what breaks when it's +wrong, and when an operator has to think about it. + + +The bare flag (no value) is rejected by the post-2.1 CLI parser, and +the failure mode propagates upstream as a `Control request timeout: +initialize` from any SDK that spawns the CLI — every A2A turn wedges +100% of the time. See [Failure mode](#failure-mode) below. + + +## The flag + +``` +--dangerously-load-development-channels +``` + +Available in Claude Code **2.1.x and later**. It opts the CLI into +processing experimental `notifications/` JSON-RPC methods +emitted by registered MCP servers and plugin channels. Without it, the +CLI silently drops those notifications during the allowlist check, even +though the wheel ships the wire shape correctly. + +## Required form: tagged allowlist entries + +Each entry must carry one of two prefixes: + +| Form | Use for | +|---|---| +| `server:` | Manually configured MCP servers — the name matches what you registered with `claude mcp add ...` or the key under `mcpServers` in `~/.claude.json`. | +| `plugin:@/` | Plugin channels installed from a Claude Code plugin marketplace. | + +Multiple entries are space-separated: + +```bash +claude --dangerously-load-development-channels server:molecule server:telegram +``` + +Untagged values (`molecule` instead of `server:molecule`) are rejected +with `--dangerously-load-development-channels entries must be tagged`. + +## Failure mode + +A bare flag (`--dangerously-load-development-channels` with no value) +walks through three layers of damage before surfacing: + +1. **CLI**: rejects the invocation with + `error: option '--dangerously-load-development-channels ' argument missing`. +2. **SDK**: `claude-agent-sdk` (used by `claude_sdk_executor.py` in the + Claude Code workspace template) renders the kwarg as a bare switch when + the value is `None`. The CLI then never responds to the SDK's first + `initialize` control message. +3. **Workspace agent**: the SDK times out with + `Control request timeout: initialize`. Every A2A turn wedges — 100% + reproducible. Caught live on workspace `dd40faf8` on 2026-05-01. + +Two small fixes prevent this: pass a tagged value (don't let `None` +render as a bare switch), and verify the CLI accepts your specific +entries before going broad. + +## For Molecule operators + +Pass `server:molecule` to enable the inbox bridge → MCP +`notifications/claude/channel` push for the `molecule-mcp` wheel. + +```bash +claude --dangerously-load-development-channels server:molecule +``` + +The `molecule` here matches the name you registered the wheel under in +[Step 2 of the runtime-mcp guide](/docs/runtime-mcp#claude-code) (the +key under `mcpServers`, or the first positional arg to `claude mcp add`). +If you registered the wheel as `mol` or `molecule-prod`, use that name +in the tag. + +When push is live, the session header prints: + +``` +Listening for channel messages from: server:molecule +``` + +…and inbound canvas/peer-agent messages render inline as +`` synthetic user turns instead of +arriving via `inbox_peek`. + +### Embedding in an SDK-driven agent + +If you spawn `claude` through `claude-agent-sdk` (e.g. the Claude Code +workspace template's `claude_sdk_executor.py`), forward the tagged value +through `extra_args`: + +```python +from claude_agent_sdk import ClaudeAgentOptions + +ClaudeAgentOptions( + model=self.model, + permission_mode="bypassPermissions", + cwd=self._resolve_cwd(), + mcp_servers=mcp_servers, + system_prompt=self._build_system_prompt(), + resume=self._session_id, + extra_args={"dangerously-load-development-channels": "server:molecule"}, +) +``` + +The SDK forwards `extra_args` keys as `-- ` to the spawned +CLI. Passing `None` as the value renders as a bare switch and trips the +[Failure mode](#failure-mode) chain above. + +## Verification + +Verified live on 2026-05-02: with the tagged value in `extra_args`, +the in-workspace agent received `` tags inline as synthetic +user turns. No `wait_for_message` poll was needed for delivery. A2A +returned coherent replies on every turn. + +## When this matters + +Only when both of the following apply: + +- You're running Claude Code (any version 2.1.x or later) as the + workspace runtime, AND +- The in-workspace `molecule-mcp` server is configured (it is, by + default, in the `claude-code` workspace template). + +**Hosted Molecule SaaS handles this automatically** — the executor +passes `extra_args={"dangerously-load-development-channels": "server:molecule"}` +when spawning the CLI. Operators on hosted SaaS do not need to do +anything. + +**Self-hosted operators using the Claude Code workspace template** also +get this for free since the template's executor sets `extra_args`. The +flag only needs operator attention when: + +- Forking the Claude Code workspace template and stripping `extra_args` + inadvertently. +- Running `claude` directly outside the template (e.g. interactive + sessions on a developer laptop) and wanting inline `` push. +- Adding a second tagged source (e.g. `server:telegram` alongside + `server:molecule`) — append, don't replace. + +Operators on Cursor, Cline, OpenCode, codex, hermes-agent, or any +non-Claude-Code MCP host are unaffected: those clients ignore the +notification and the wheel's poll path delivers via +`wait_for_message` as the universal fallback. + +## Forward note + +This requirement is a **research-preview gate**. Once Claude Code +graduates `notifications/` from research preview to a default +allowlist, the `--dangerously-load-development-channels` flag will no +longer be required for the `molecule` server. Drop the `extra_args` +entry in `claude_sdk_executor.py` (and any operator launch wrappers) +when that happens — the wheel emits the wire shape correctly today +and will continue to do so post-graduation. + +## See also + +- [Bring Your Own Runtime (MCP) — Inbound delivery](/docs/runtime-mcp#inbound-delivery-universal-poll-optional-push) +- [Bring Your Own Runtime (MCP) — Step 2: Claude Code](/docs/runtime-mcp#claude-code) +- [Troubleshooting — Control request timeout: initialize](/docs/runtime-mcp#control-request-timeout-initialize-from-the-workspace-agent)