--dangerously-load-development-channels does not register bare server: channels (inbound notifications silently dropped)
Summary
A .mcp.json MCP server that declares capabilities.experimental["claude/channel"] connects fine and its tools work (outbound reply succeeds), but inbound channel notifications are silently dropped when the session is launched with --dangerously-load-development-channels server:<name>.
Every session, --debug logs:
MCP server "myserver": Channel notifications skipped: server myserver not in --channels list for this session
This is the kind:"session" gate — the server has already passed the capability check, the tengu_harbor feature-flag gate, auth (claude.ai OAuth, confirmed logged in), and the org-policy gate. It fails purely because the dev-flag server was never registered into the session channel list.
Decompiling the binary (v2.1.195) shows the cause: the startup flag-assembly code parses the --dangerously-load-development-channels list but never registers it — it only registers the --channels list and then sends the dev-flag list to telemetry. The result is that --dangerously-load-development-channels is effectively a no-op for a bare server:<name> channel: outbound works, inbound is never delivered, and there is no working configuration on this version.
Environment
- Claude Code v2.1.195 (current latest as of this report)
- macOS, arm64
- Authenticated via claude.ai OAuth (not an API key), logged in and confirmed
- A third-party MCP server (declared in
<project-root>/.mcp.json) that advertises theclaude/channelcapability. This class of server exists in the wild as community channel bridges (Slack/Telegram/Discord, etc.); the bug reproduces with any bareserver:channel.
Repro steps
- Add a minimal stdio MCP server to
<project-root>/.mcp.jsonthat advertises the channel capability:
``json``
{
"mcpServers": {
"myserver": {
"command": "node",
"args": ["./my-channel-server.js"]
}
}
}
The server, in its initialize response, declares:
``json``
{
"capabilities": {
"experimental": { "claude/channel": {} }
}
}
and exposes an outbound reply tool.
- Launch Claude Code with the development-channels flag:
````
claude --dangerously-load-development-channels server:myserver --debug
- Observe:
- The server connects, and its tools (including outbound
reply) work normally. - No inbound channel notification (
notifications/claude/channel) is ever delivered to the conversation. - The debug log shows the skip line every session (see Debug evidence).
Expected vs Actual
Expected: Passing --dangerously-load-development-channels server:myserver registers myserver as a development channel for the session, so inbound notifications/claude/channel messages are delivered to the conversation (this is the documented local-dev path for channels).
Actual: Inbound notifications are silently skipped every session. The server is never added to the session channel list, so the session gate never matches it. Outbound tools still work, which makes the failure look like a connectivity issue when it is actually a registration gap.
Debug evidence
With --dangerously-load-development-channels server:myserver:
MCP server "myserver": Channel notifications skipped: server myserver not in --channels list for this session
If you instead ALSO pass --channels server:myserver (so it does get registered), it clears the session gate but then fails the next gate, because the entry is registered as non-dev and a bare server: channel has no settings-based allowlist:
MCP server "myserver": Channel notifications skipped: server myserver is not on the approved channels allowlist (use --dangerously-load-development-channels for local dev)
...even though --dangerously-load-development-channels server:myserver is also present on the same command line — because that flag's bypass never takes effect (its entries were never registered as dev entries).
Root cause
From the de-minified v2.1.195 startup flag-assembly code. Both flags are parsed with the same tokenizer, but only --channels is registered into the session channel list; the dev-flag list is parsed and then used only for telemetry:
const channels = opts.channels; // --channels
const devChannels = opts.dangerouslyLoadDevelopmentChannels; // --dangerously-load-development-channels
let parsedChannels = [];
if (channels?.length) {
parsedChannels = parseChannels(channels, "--channels");
registerChannels(parsedChannels); // ← registered
}
let parsedDev;
if (!isNonInteractive && devChannels?.length) {
parsedDev = parseChannels(devChannels, "--dangerously-load-development-channels"); // ← parsed...
}
if (parsedChannels.length || parsedDev?.length) {
telemetry("tengu_mcp_channel_flags", { /* counts + plugin names only */ }); // ← ...only used here
}
// NOTE: there is no registerChannels(parsedDev) call — the dev-flag entries never enter the session channel list.
The session-gate matcher then looks the server up in the registered list and finds nothing:
function findEntry(name, list) {
const parts = name.split(":");
return list.find(e =>
e.kind === "server"
? name === e.name
: parts[0] === "plugin" && parts[1] === e.name);
}
// list never contains the dev-flag server → returns undefined → skip kind:"session"
The downstream allowlist gate confirms the consequence: when the entry is registered (via --channels) it is marked non-dev, so it fails the allowlist gate, and for a bare server: channel there is no settings-based allowlist (only allowedChannelPlugins exists, which is plugin-only). So neither path works.
Impact / why there is no workaround on this version
--dangerously-load-development-channels server:<name>is a no-op for inbound on bareserver:channels — its parsed entries are never registered, so the session gate always skips.- Adding
--channels server:<name>registers the entry but as non-dev, so it then fails the approved-allowlist gate. - There is no
allowedChannelPluginsequivalent for bareserver:channels (that setting is plugin-only), so the allowlist gate cannot be satisfied either.
Net: on v2.1.195 there is no working configuration to receive inbound channel notifications for a bare server: channel. The documented local-dev path is broken.
Suggested fix
Register the parsed dev-flag entries into the session channel list, marked as development entries so they bypass the approved-allowlist gate — i.e. add the missing call:
if (!isNonInteractive && devChannels?.length) {
parsedDev = parseChannels(devChannels, "--dangerously-load-development-channels");
registerChannels(parsedDev, { dev: true }); // ← currently missing
}
With the entries registered as dev, they clear the kind:"session" gate (now present in the list) and bypass the allowlist gate (marked dev), restoring the intended local-dev behavior.
Possibly related
- #36503 —
--channelsplugin shows "Channels are not currently available" but inbound notifications are ignored (open; plugin-path variant of an inbound-drop). - #51845 —
--channelsflag never matches plugin server identifier, channel notifications always skipped (closed; same family of session-gate/identifier-matching failures, plugin-scoped).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗