Create Claude Channel

Step-by-step procedure for building a Claude Code channel integration. Use when creating a new channel plugin that connects Claude Code to external systems via MCP, named pipes, webhooks, or chat platforms.

7 stars

Best use case

Create Claude Channel is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Step-by-step procedure for building a Claude Code channel integration. Use when creating a new channel plugin that connects Claude Code to external systems via MCP, named pipes, webhooks, or chat platforms.

Teams using Create Claude Channel should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/create-claude-channel/SKILL.md --create-dirs "https://raw.githubusercontent.com/jack-michaud/faire/main/jack-software/skills/create-claude-channel/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/create-claude-channel/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Create Claude Channel Compares

Feature / AgentCreate Claude ChannelStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Step-by-step procedure for building a Claude Code channel integration. Use when creating a new channel plugin that connects Claude Code to external systems via MCP, named pipes, webhooks, or chat platforms.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Create a Claude Code Channel

## Overview

A Claude Code channel is an MCP server that pushes events into a Claude Code session so Claude can react to things happening outside the terminal. Channels can be one-way (alerts, webhooks) or two-way (chat bridges with reply tools).

This procedure walks through building, packaging, and testing a channel integration from scratch.

## Prerequisites

- Claude Code v2.1.80+ (`claude --version`)
- Bun, Node, or Deno runtime
- `@modelcontextprotocol/sdk` npm package
- A Claude Code plugin marketplace (or local project)

## Procedure

### Step 1: Create the project structure

Create a new plugin directory with the channel server and config files:

```
my-channel/
  package.json          # Dependencies
  server.ts             # MCP channel server
  plugin.json           # Plugin manifest
  .mcp.json             # MCP server config
```

Initialize the project:

```bash
mkdir my-channel && cd my-channel
bun init -y
bun add @modelcontextprotocol/sdk
```

### Step 2: Write the MCP channel server

The server has three essential parts:

1. **Server declaration** with `claude/channel` capability
2. **Stdio transport** connection (Claude Code spawns the server as a subprocess)
3. **Notification emitter** that pushes events into Claude's context

Minimal one-way server:

```typescript
#!/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: 'my-channel', version: '0.1.0' },
  {
    // claude/channel capability is REQUIRED — this registers the notification listener
    capabilities: { experimental: { 'claude/channel': {} } },
    // Instructions go into Claude's system prompt
    instructions: 'Events arrive as <channel source="my-channel" ...>. Act on them.',
  },
)

await mcp.connect(new StdioServerTransport())

// Push events to Claude:
await mcp.notification({
  method: 'notifications/claude/channel',
  params: {
    content: 'The event body text',
    meta: { key: 'value' },  // Each key becomes a <channel> tag attribute
  },
})
```

### Step 3: Add a reply tool (for two-way channels)

If Claude needs to send messages back, expose an MCP tool:

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

// Add tools capability to the Server constructor:
// capabilities: { experimental: { 'claude/channel': {} }, tools: {} }

mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: 'Send a reply back through this channel',
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: 'Conversation to reply in' },
        text: { type: 'string', description: '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 }
    // Send the reply through your transport (pipe, HTTP, chat API, etc.)
    await sendReply(chat_id, text)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})
```

Update instructions to tell Claude about the reply tool:

```typescript
instructions: 'Messages arrive as <channel source="my-channel" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.'
```

### Step 4: Implement the transport layer

Choose your transport based on the use case:

| Transport | Use Case | Example |
|-----------|----------|---------|
| Named pipes (mkfifo) | Local IPC between processes | fifo-pipe-channel |
| HTTP server | Webhooks, CI alerts | webhook-channel |
| Platform API polling | Chat platforms (Telegram, Discord) | telegram-channel |
| WebSocket | Real-time bidirectional | custom chat bridges |

For named pipes (see `resources/fifo-pipe-example.md` for full implementation):

```typescript
import { execSync } from 'child_process'

// Create pipes
execSync('mkfifo /path/to/inbound.fifo')
execSync('mkfifo /path/to/outbound.fifo')

// Read loop: watch inbound pipe, push to Claude
async function readLoop() {
  while (true) {
    const file = Bun.file('/path/to/inbound.fifo')
    const stream = file.stream()
    const reader = stream.getReader()
    // Read lines and call mcp.notification() for each
  }
}
```

For HTTP (webhooks):

```typescript
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 } },
    })
    return new Response('ok')
  },
})
```

### Step 5: Create plugin packaging files

**plugin.json:**

```json
{
  "name": "my-channel",
  "version": "0.1.0",
  "description": "Description of what this channel does",
  "author": { "name": "Your Name" },
  "mcpServers": "./.mcp.json"
}
```

**.mcp.json:**

```json
{
  "mcpServers": {
    "my-channel": {
      "command": "bun",
      "args": ["${CLAUDE_PLUGIN_ROOT}/server.ts"]
    }
  }
}
```

**package.json:**

```json
{
  "name": "claude-channel-my-channel",
  "version": "0.1.0",
  "type": "module",
  "bin": "./server.ts",
  "scripts": { "start": "bun install --no-summary && bun server.ts" },
  "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" }
}
```

### Step 6: Register in marketplace (if applicable)

Add an entry to `.claude-plugin/marketplace.json`:

```json
{
  "name": "my-channel",
  "source": "./my-channel",
  "description": "...",
  "version": "0.1.0",
  "category": "communication"
}
```

### Step 7: Install and test

```bash
# Install the plugin
claude plugin install my-channel@your-marketplace

# Start Claude with the development channel flag
claude --dangerously-load-development-channels plugin:my-channel@your-marketplace

# Select "I am using this for local development" when prompted
```

Test the inbound path by sending data through your transport. Verify:
- Claude receives the message (shown as `← my-channel · ...` in the session)
- Claude uses the reply tool (if two-way)
- The reply reaches the other end of the transport

### Step 8: Security considerations

- **Gate inbound messages**: Check sender identity before calling `mcp.notification()` to prevent prompt injection
- **Localhost only**: Bind HTTP servers to `127.0.0.1`, not `0.0.0.0`
- **Permission relay** (optional): Declare `claude/channel/permission` capability to forward tool approval prompts remotely. Only do this if your channel authenticates the sender.

## Key Concepts

### Notification format

Events arrive in Claude's context as XML tags:

```
<channel source="my-channel" key1="val1" key2="val2">
The event body content
</channel>
```

- `source` is set automatically from the server name
- `meta` keys become tag attributes (letters, digits, underscores only)
- `content` becomes the tag body

### Instructions string

The `instructions` field in the Server constructor is injected into Claude's system prompt. It should tell Claude:
- What events to expect
- Whether to reply (and which tool to use)
- How to route replies (e.g., pass `chat_id` from the inbound tag)

### Development flag

Custom channels need `--dangerously-load-development-channels` during the research preview. Format: `plugin:<name>@<marketplace>` or `server:<name>` for bare MCP servers.

## Reference implementations

- **fakechat**: Web UI chat bridge — `external_plugins/fakechat` in claude-plugins-official
- **fifo-pipe-channel**: Named pipe transport — `fifo-pipe-channel/` in this repo
- **webhook example**: HTTP POST receiver — documented in channels reference docs

Related Skills

Ticket Workflow

7
from jack-michaud/faire

Autonomous ticket-to-production lifecycle with promptlet-driven phases. Use when executing /ticket commands or working on ticket-driven development.

setup-sprite

7
from jack-michaud/faire

Set up a reproducible remote dev environment using sprites with credential-free git sync. Use when user asks to "set up a sprite", "create a remote dev environment", "use sprites", or mentions wanting a reproducible remote environment for a project.

Writing python services

7
from jack-michaud/faire

Writing a class with encapsulated logic that interfaces with an external system. Logging, APIs, etc.

stacked-pr-review

7
from jack-michaud/faire

Use when addressing PR review comments on stacked jj branches. Triggered by phrases like "address review comments", "fix PR feedback", "respond to review on stacked branch", or "update the PR with reviewer suggestions".

Python Code Style

7
from jack-michaud/faire

Use when writing python code. Can be used for code review.

Plan

7
from jack-michaud/faire

My planning skill that has additional instructions. Always use when entering plan mode.

Modal

7
from jack-michaud/faire

No description provided.

merging-pr-stack

7
from jack-michaud/faire

Use when merging a stack of stacked PRs with jj. Triggered by phrases like "merge the PR stack", "merge the stacked PRs", "land these PRs", or "merge the stack into main".

Creating Slash Commands

7
from jack-michaud/faire

Build custom slash commands in Claude Code with YAML frontmatter, permissions, and best practices. Use when creating automation workflows, project-specific commands, or standardizing repetitive tasks.

Creating Hooks

7
from jack-michaud/faire

Build event-driven hooks in Claude Code for validation, setup, and automation. Use when you need to validate inputs, check environment state, or automate tasks at specific lifecycle events.

Committing

7
from jack-michaud/faire

Any time committing, pushing, or creating new revisions is mentioned, use this commit flow.

SOLID Principles Code Review

7
from jack-michaud/faire

Review Python code for adherence to SOLID principles (SRP, OCP, LSP, ISP, DIP). Use when reviewing Python code for maintainability, testability, and design quality.