Skip to content

Channels Reference [Beta] โ€‹

Beta Feature: Channels is currently in Beta. The protocol and interfaces may be adjusted based on feedback.

A Channel is a special MCP server that pushes external events (webhooks, chat messages, monitoring alerts, etc.) into a CodeBuddy Code session, allowing CodeBuddy Code to react to things happening outside the terminal.

Overview โ€‹

A Channel runs on the same machine as CodeBuddy Code and communicates via stdio. CodeBuddy Code launches it as a child process.

Typical use cases:

  • Chat platforms (Telegram, Discord): The plugin runs locally, polls the platform API for new messages, and forwards them to CodeBuddy Code
  • Webhooks (CI, monitoring): A server listens on a local HTTP port, receives POST requests from external systems, and pushes them to CodeBuddy Code

Channels operate in two modes:

ModeDescription
One-wayOnly forwards events to CodeBuddy Code (alerts, webhooks), processed in the local session
Two-wayAdditionally exposes a reply tool, allowing CodeBuddy Code to send responses

Using a Channel โ€‹

Startup โ€‹

Use the --channels parameter to specify which channels to load:

bash
# Load a plugin-type channel
codebuddy --channels plugin:fakechat@claude-plugins-official

# Load a channel server configured in .mcp.json
codebuddy --channels server:webhook

# Load multiple channels (comma-separated)
codebuddy --channels plugin:telegram@claude-plugins-official,plugin:discord@claude-plugins-official

Development Mode โ€‹

Use the --dangerously-load-development-channels flag to test custom channels during initial development. This flag allows any channel to run without being on the allowlist:

bash
codebuddy --dangerously-load-development-channels server:my-webhook

This flag only bypasses the allowlist check; the channelsEnabled organization policy still applies. Once a channel is submitted to the official marketplace and passes security review, it gets added to the allowlist and can be loaded directly with --channels.

Channel Messages in Sessions โ€‹

Channel messages are injected into CodeBuddy Code's context as <channel> tags:

xml
<channel source="fakechat" sender="web" chat_id="1">Hello, can you help me look at this issue</channel>

In the TUI, channel messages are displayed in a friendly format:

#fakechat ยท web: Hello, can you help me look at this issue

Settings โ€‹

Channel functionality can be controlled in settings.json:

json
{
  "channelsEnabled": true
}

Set to false to completely disable channel functionality.

Building a Channel โ€‹

Basic Requirements โ€‹

A channel server needs to:

  1. Declare the claude/channel capability so CodeBuddy Code registers a notification listener
  2. Send notifications/claude/channel events
  3. Connect via stdio transport (CodeBuddy Code launches it as a child process)

Minimal Example: Webhook Receiver โ€‹

ts
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

const mcp = new Server(
  { name: 'webhook', version: '0.0.1' },
  {
    capabilities: { experimental: { 'claude/channel': {} } },
    instructions: 'Events from webhook arrive as <channel source="webhook" ...> tags. One-way channel, just read and process.',
  },
)

await mcp.connect(new StdioServerTransport())

Bun.serve({
  port: 8788,
  hostname: '127.0.0.1',
  async fetch(req) {
    const body = await req.text()
    await mcp.notification({
      method: 'notifications/claude/channel',
      params: {
        content: body,
        meta: { path: new URL(req.url).pathname, method: req.method },
      },
    })
    return new Response('ok')
  },
})

Register in .mcp.json:

json
{
  "mcpServers": {
    "webhook": { "command": "bun", "args": ["./webhook.ts"] }
  }
}

Server Configuration Options โ€‹

FieldTypeDescription
capabilities.experimental['claude/channel']objectRequired. Always {}. Once declared, CodeBuddy Code registers a notification listener
capabilities.experimental['claude/channel/permission']objectOptional. Always {}. Declares that this channel can receive permission relay requests
capabilities.toolsobjectRequired for two-way channels. Always {}. Standard MCP tool capability
instructionsstringRecommended. Injected into the system prompt, telling CodeBuddy Code how to handle events from this channel

Notification Format โ€‹

When sending notifications/claude/channel notifications, params contains two fields:

FieldTypeDescription
contentstringEvent content, becomes the body of the <channel> tag
metaRecord<string, string>Optional. Each key-value pair becomes an attribute of the <channel> tag. Key names only allow letters, numbers, and underscores; keys containing hyphens or other characters are silently discarded

Example:

ts
await mcp.notification({
  method: 'notifications/claude/channel',
  params: {
    content: 'build failed on main',
    meta: { severity: 'high', run_id: '1234' },
  },
})

Format when arriving at CodeBuddy Code:

xml
<channel source="webhook" severity="high" run_id="1234">
build failed on main
</channel>

Exposing a Reply Tool โ€‹

Two-way channels need to expose standard MCP tools for CodeBuddy Code to reply to messages:

ts
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'

mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: 'Send a reply message through this channel',
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: 'The conversation ID to reply to' },
        text: { type: 'string', description: 'The message to send' },
      },
      required: ['chat_id', 'text'],
    },
  }],
}))

mcp.setRequestHandler(CallToolRequestSchema, async req => {
  if (req.params.name === 'reply') {
    const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
    // Call your chat platform API to send the message
    await sendToPlatform(chat_id, text)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})

Sender Security Controls โ€‹

An unprotected channel is a prompt injection attack vector. Anyone who can reach your endpoint can inject text into CodeBuddy Code.

Before calling mcp.notification(), you must verify the sender's identity:

ts
const allowed = new Set(loadAllowlist())

// In the message handler, before sending the notification:
if (!allowed.has(message.from.id)) {  // Check sender ID, not group ID
  return  // Silently discard
}
await mcp.notification({ ... })

Important: Always validate based on sender identity (message.from.id) rather than chat room identity (message.chat.id). In group chats, these two values differ, and validating by group would allow anyone in the group to inject messages into the session.

Permission Relay โ€‹

When CodeBuddy Code calls a tool that requires approval (such as Bash, Write, Edit), the local terminal opens a permission dialog. Two-way channels can optionally receive this prompt as well, allowing you to approve or deny from a remote device.

Both the local terminal and remote channel dialogs open simultaneously. The first response wins, and the other is automatically dismissed.

Enabling Permission Relay โ€‹

Add claude/channel/permission in the Server constructor:

ts
capabilities: {
  experimental: {
    'claude/channel': {},
    'claude/channel/permission': {},  // Enable permission relay
  },
  tools: {},
},

Permission Request Fields โ€‹

CodeBuddy Code sends notifications/claude/channel/permission_request with the following fields:

FieldDescription
request_id5 lowercase letters (excluding l), used to match replies
tool_nameThe tool CodeBuddy Code wants to use, e.g., Bash, Write
descriptionHuman-readable description of the tool call
input_previewJSON string of tool parameters, truncated to 200 characters

Sending a Verdict โ€‹

Your channel needs to send a notifications/claude/channel/permission notification:

ts
await mcp.notification({
  method: 'notifications/claude/channel/permission',
  params: {
    request_id: '<received request_id>',
    behavior: 'allow',  // or 'deny'
  },
})

Handling Inbound Verdicts โ€‹

In your inbound message handler, recognize replies in the format yes <id> or no <id>:

ts
// Match "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] is the ID alphabet used by CodeBuddy Code (lowercase, skipping 'l')
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i

const m = PERMISSION_REPLY_RE.exec(message.text)
if (m) {
  await mcp.notification({
    method: 'notifications/claude/channel/permission',
    params: {
      request_id: m[2].toLowerCase(),
      behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
    },
  })
  return  // Handled as a verdict, not forwarded as chat
}

Packaging as a Plugin โ€‹

Wrapping a channel as a plugin makes it easy to share and install. Users install via /plugin install, then enable with --channels plugin:<name>@<marketplace>.

References โ€‹

  • fakechat example: A complete two-way channel implementation with Web UI, file attachments, and reply tool
  • MCP Protocol: Channels are built on the MCP protocol