チャネル
チャネル(channels)で Telegram・Discord・iMessage や Webhook のイベントを実行中のセッションへ送る設定と、自作チャネルの仕様・権限確認の中継・組織の管理設定をまとめます。
チャネル(channel)は、実行中の Claude Code セッションへイベントを送り込む MCP サーバーです。ターミナルを離れている間に起きたこと(CI の結果・チャットのメッセージ・監視のアラートなど)に Claude が反応できます。チャネルは双方向にもでき、Claude がイベントを読み、同じチャネルから返信します(チャットの橋渡しなど)。イベントが届くのはセッションが開いている間だけなので、常時動かすなら Claude をバックグラウンドのプロセスか常駐するターミナルで動かします。
補足
チャネルは研究プレビュー(research preview)です。claude.ai か Console の API キーによる Anthropic 認証が必要で、Amazon Bedrock・Google Cloud の Agent Platform・Microsoft Foundry では使えません。Team と Enterprise の組織は、明示的に有効にする必要があります。提供は段階的に広がっており、--channels フラグの書き方とプロトコルの取り決めは、フィードバックを受けて変わることがあります。
要点#
- チャネルはプラグインとして入れ、自分の認証情報で設定する。研究プレビューに Telegram・Discord・iMessage が含まれる
- セッションごとに
--channelsで、使うチャネルを指定する(.mcp.jsonにあるだけでは、メッセージは届かない) - 送信者の許可リスト(allowlist)に載った ID だけがメッセージを送れ、ほかは黙って捨てられる
- 自作するには、
claude/channelの capability を宣言し、notifications/claude/channelを送る MCP サーバーを作る - 権限確認を、別の端末へ中継して遠隔で承認することもできる
- 組織は、管理設定の
channelsEnabledとallowedChannelPluginsで制御する
ほかの連携との違い#
| 機能 | 内容 | 向く用途 |
|---|---|---|
| クラウド(Web)のセッション | GitHub からクローンした新しいクラウドのサンドボックスでタスクを動かす | あとで確認する、自己完結した非同期の作業を任せる |
| Claude in Slack | チャネルやスレッドでの @Claude のメンションから、クラウドセッションを起動する |
チームの会話の文脈から、そのままタスクを始める |
| 標準の MCP サーバー | タスク中に Claude がそれに問い合わせる。セッションへ何も送られない | システムを読む・照会するための、必要なときのアクセスを Claude に与える |
| リモートコントロール | claude.ai か Claude のモバイルアプリから、手元のセッションを操作する | 席を離れている間に、進行中のセッションを操る |
チャネルは、Claude 以外の出どころからのイベントを、すでに動いている手元のセッションへ送ることで、この空白を埋めます。
- チャット橋渡し:Telegram・Discord・iMessage で、スマートフォンから Claude に頼むと、作業は手元のマシンで実際のファイルに対して動き、答えは同じチャットに戻る
- Webhook の受信:CI・エラートラッカー・デプロイのパイプライン・その他の外部サービスの Webhook が、Claude がすでにファイルを開き、デバッグ中の内容を覚えているところへ届く
対応するチャネル#
どの対応チャネルも、Bun が必要なプラグインです。実際のプラットフォームをつなぐ前に、プラグインの流れを試すデモには、後述の fakechat を使えます。
Telegram#
- ボットを作る:Telegram で BotFather を開き、
/newbotを送る。表示名と、botで終わる一意のユーザー名を付け、BotFather が返すトークンをコピーする - プラグインを入れる:ターミナルで
claudeを実行して Claude Code を起動し、そのプロンプトで次を入力する。インストールの範囲を聞かれたら、全プロジェクトで使えるようユーザー範囲を選ぶ
/plugin install telegram@claude-plugins-official
- トークンを設定する:BotFather のトークンで設定コマンドを実行する。
~/.claude/channels/telegram/.envに保存される。Claude Code の起動前にシェルの環境変数TELEGRAM_BOT_TOKENを設定してもよい
/telegram:configure <token>
- チャネルを有効にして再起動する:Claude Code を終了し、チャネルのフラグ付きで再起動する。Telegram のプラグインが起動し、ボットへのメッセージのポーリングを始める
claude --channels plugin:telegram@claude-plugins-official
- アカウントをペアリングする:Telegram でボットに何かメッセージを送ると、ボットがペアリングコードを返す。Claude Code に戻って次を実行し、自分のアカウントだけがメッセージを送れるよう許可リストに絞る
/telegram:access pair <code>
/telegram:access policy allowlist
ボットが応答しないときは、前の手順の --channels で Claude Code が動いているか確かめます。ボットが返信できるのは、チャネルが有効な間だけです。
Discord#
- ボットを作る:Discord Developer Portal で「New Application」を押して名前を付ける。「Bot」で、ユーザー名を作り、「Reset Token」を押してトークンをコピーする
- Message Content Intent を有効にする:ボットの設定の「Privileged Gateway Intents」で「Message Content Intent」をオンにする
- ボットをサーバーに招待する:「OAuth2 > URL Generator」で
botのスコープを選び、次の権限をオンにする:View Channels・Send Messages・Send Messages in Threads・Read Message History・Attach Files・Add Reactions。生成された URL を開いてボットをサーバーへ追加する - プラグインを入れる:Claude Code を
claudeで起動し、そのプロンプトで/plugin install discord@claude-plugins-official(インストールの範囲はユーザー範囲を選ぶ) - トークンを設定する:
/discord:configure <token>(~/.claude/channels/discord/.envに保存される。起動前にDISCORD_BOT_TOKENをシェルの環境変数に設定してもよい) - チャネルを有効にして再起動する:
claude --channels plugin:discord@claude-plugins-official - アカウントをペアリングする:Discord でボットに DM を送ると、ボットがペアリングコードを返す。Claude Code で
/discord:access pair <code>を実行し、続けて/discord:access policy allowlistで自分のアカウントだけに絞る
iMessage#
iMessage のチャネルは、Messages のデータベースを直接読み、AppleScript で返信を送ります。macOS が必要で、ボットのトークンも外部サービスも要りません。
- フルディスクアクセスを許可する:
~/Library/Messages/chat.dbの Messages のデータベースは、macOS が保護している。サーバーが初めて読むとき macOS がアクセスの許可を求めるので「Allow」を押す(プロンプトは Bun を起動したアプリ名(Terminal・iTerm・IDE など)を示す)。プロンプトが出なかったり「Don't Allow」を押したりしたときは、「System Settings > Privacy & Security > Full Disk Access」でターミナルを手動で追加する。許可が無いと、サーバーはauthorization deniedですぐ終了する - プラグインを入れる:Claude Code を
claudeで起動し、そのプロンプトで/plugin install imessage@claude-plugins-official(インストールの範囲はユーザー範囲を選ぶ)。インストールの要約にRun /reload-plugins to activate.と出ても、次の手順の再起動でプラグインが読み込まれるので、ここでは何もしなくてよい - チャネルを有効にして再起動する:
claude --channels plugin:imessage@claude-plugins-official - 自分に送る:Apple ID でサインインしているどの端末でも Messages を開いて、自分宛にメッセージを送る。すぐ Claude に届く(自分とのチャットは、設定なしでアクセス制御を迂回する)。Claude が最初に返信するとき、ターミナルが Messages を操作してよいかを尋ねる macOS の Automation の確認が出るので「OK」を押す
- ほかの送信者を許可する:既定では、自分のメッセージだけが通る。別の連絡先を許可するには、ハンドルを追加する:
/imessage:access allow +15551234567(ハンドルは+国番号形式の電話番号か、user@example.comのような Apple ID のメール)
プラグインのインストールに失敗したとき#
表示されたメッセージで判断します。
Marketplace "claude-plugins-official" not found:/plugin marketplace add anthropics/claude-plugins-officialでマーケットプレイスを足し、入れ直す- マーケットプレイスにプラグインが見つからない:プラグイン名を確認する(プラグインを使う参照)
- インストールの要約に
Run /reload-plugins to activate.と出たとき:設定コマンドを使えるようにするには、プラグインの変更を再起動せずに適用する方法を使う
クイックスタート:fakechat で試す#
fakechat は、公式にサポートされるデモのチャネルで、localhost にチャットの UI を出します。認証するものも、設定する外部サービスもありません。入れて有効にすると、ブラウザに入力したメッセージが Claude Code のセッションに届き、Claude の返信がブラウザに戻ります。
必要なもの:
- claude.ai アカウントか Claude Console の API キーで、インストールと認証が済んだ Claude Code
- Bun(事前に作られたチャネルのプラグインは Bun のスクリプト。
bun --versionで確かめる) - Team・Enterprise・管理された Console の組織では、管理者が管理設定でチャネルを有効にしていること
- プラグインを入れる:Claude Code を
claudeで起動し、そのプロンプトで/plugin install fakechat@claude-plugins-official(インストールの範囲はユーザー範囲を選ぶ) - チャネルを有効にして再起動する:Claude Code を終了し、入れた fakechat のプラグインを
--channelsに渡して再起動する
claude --channels plugin:fakechat@claude-plugins-official
fakechat のサーバーは自動で起動します。起動画面に、plugin:fakechat@claude-plugins-official からのメッセージがこのセッションへ直接注入されるというチャネルの通知が出ます。プラグインが入っていないか、承認済みの許可リストに無いときは、その通知の下に問題を示す警告行が出ます。
ヒント
--channels には、スペース区切りで複数のプラグインを渡せます。
- メッセージを送る:fakechat の UI(
http://localhost:8787)を開いて入力する(例:what's in my working directory?)。メッセージは Claude Code のセッションに届きます。ターミナルには← fakechat · web: what's in my working directory?のような受信のチャネル行が出て、モデルは、プラグインのスコープ付きのサーバー名を使った<channel source="plugin:fakechat:fakechat">のイベントとして受け取ります。Claude が読んで作業し、fakechat のreplyツールを呼びます。最初の返信で Claude Code が許可を求めたら承認します。答えがチャットの UI に出ます
- ターミナルから離れている間に Claude が権限の確認に当たると、あなたが応答するまでセッションは止まります。権限確認の中継の capability を宣言したチャネルサーバーは、これらの確認をあなたへ転送して、遠隔で承認・拒否できます。無人で使うなら
--dangerously-skip-permissionsでほとんどの確認を省けますが、信頼できる環境でだけ使います(どのモードも自動承認しない操作は、それでも適用されます。権限モード参照) -pの非対話モードでチャネルを動かすと、複数選択の質問やプランモードの承認のように、ターミナルの入力が要るツールは無効になり、セッションが入力待ちで止まりません
セキュリティ#
承認されたチャネルのプラグインは、どれも送信者の許可リストを保ちます。追加した ID だけがメッセージを送れ、ほかのすべては黙って捨てられます。
- Telegram と Discord は、ペアリングで許可リストを始めます:(1) Telegram か Discord でボットを探して何かメッセージを送る (2) ボットがペアリングコードを返す (3) Claude Code のセッションで、求められたらコードを承認する (4) 自分の送信者 ID が許可リストに加わる
- iMessage は違います:自分に送るメッセージは、自動でゲートを迂回し、ほかの連絡先は
/imessage:access allowでハンドルを指定して追加します - それに加えて、セッションごとにどのサーバーを有効にするかを
--channelsで制御し、組織は、claude.ai の Team・Enterprise プランと、管理設定を配る Console の組織で、channelsEnabledで提供の可否を制御します .mcp.jsonにあるだけではメッセージを送れません。サーバーが--channelsに名指しされている必要もあります- 許可リストは、チャネルが宣言した場合は、権限確認の中継のゲートにもなります。チャネルから返信できる人は、あなたのセッションのツールの使用を承認・拒否できるので、その権限を任せられる送信者だけを許可リストに入れます
組織の管理#
管理者は、ユーザーが上書きできない2つの管理設定で、提供の可否を制御します。既定は認証の方法で決まります。
- claude.ai の Team と Enterprise:Owner が有効にするまでチャネルはブロックされる
- API キー認証の Anthropic Console:チャネルは既定で許可される。この設定が要るのは、組織が管理設定を配っているときだけ
どの場合も、ユーザーがセッションごとに --channels で選ばない限り、どのチャネルも動きません。
| 設定 | 目的 | 未設定のとき |
|---|---|---|
channelsEnabled |
マスタースイッチ。どのチャネルがメッセージを届けるにも true が必要。オフだと、開発用フラグを含むすべてのチャネルをブロックする |
claude.ai の Team と Enterprise:チャネルはブロックされる。Console:組織が管理設定を配っていない限り許可される(配っているなら、このキーを設定するまでブロックされる) |
allowedChannelPlugins |
チャネルが有効なとき、どのプラグインを登録できるか。設定すると、Anthropic が管理する一覧を置き換える | Anthropic の既定の一覧が適用される |
組織を持たない Pro と Max のユーザーは、これらの確認を完全に飛ばします:チャネルは使え、ユーザーはセッションごとに --channels で選びます。
組織でチャネルを有効にする#
claude.ai の「Organization settings > Claude Code > Channels」から有効にします(Owner のロールが必要)。管理設定で channelsEnabled を true にしてもできます。有効になると、組織のユーザーは --channels で、チャネルのサーバーを個々のセッションに選べます。設定が無効か未設定だと、MCP サーバーは接続してそのツールも動きますが、チャネルのメッセージは届きません。起動時の警告が、管理者に設定を有効にしてもらうようユーザーに伝えます。
動かせるチャネルのプラグインを制限する#
既定では、Anthropic が管理する許可リストにあるプラグインなら、チャネルとして登録できます。Team と Enterprise プランの管理者は、管理設定の allowedChannelPlugins で、その許可リストを自分のものに置き換えられます。承認する公式プラグインの制限・自社のマーケットプレイスのチャネルの承認・その両方に使います。各エントリは、プラグインと、その出どころのマーケットプレイスを名指しします。
{
"channelsEnabled": true,
"allowedChannelPlugins": [
{ "marketplace": "claude-plugins-official", "plugin": "telegram" },
{ "marketplace": "claude-plugins-official", "plugin": "discord" },
{ "marketplace": "acme-corp-plugins", "plugin": "internal-alerts" }
]
}
- 空の配列を設定すると、許可リストのチャネルのプラグインをすべてブロックしますが、ローカルのテストには
--dangerously-load-development-channelsでそのブロックを迂回できます。開発用フラグを含めてチャネルを完全にブロックするには、代わりにchannelsEnabledを未設定のままにします - この設定は
channelsEnabled: trueが必要です。ユーザーが--channelsに、一覧に無いプラグインを渡すと、Claude Code は普通に起動しますが、チャネルは登録されず、起動時の通知が、そのプラグインは組織の承認済みの一覧にないと説明します。v2 の MCP クライアントのランタイムでは、Claude Code がプロトコルのリビジョン 2026-07-28 を交渉するチャネルサーバーを登録しないことでも、チャネルが登録に失敗することがあります
研究プレビューの扱い#
- プレビューの間、
--channelsも--dangerously-load-development-channelsも、claude --helpに出ません。一覧にはなくても、フラグは動きます --channelsが受け付けるのは、Anthropic が管理する許可リストのプラグイン、または管理者がallowedChannelPluginsを設定している場合は組織の許可リストのプラグインだけです。claude-plugins-officialのチャネルのプラグインが既定の承認済みの集合です。有効な許可リストに無いものを渡すと、Claude Code は普通に起動しますが、チャネルは登録されず、起動時の通知が理由を伝えます- 作っているチャネルを試すには、
plugin:<name>@<marketplace>かserver:<name>の形で--dangerously-load-development-channelsに渡します(後述)
チャネルを作る#
チャネルは、Claude Code と同じマシンで動く MCP サーバーです。Claude Code がそれをサブプロセスとして起動し、stdio で通信します。チャネルのサーバーは、外部のシステムと Claude Code のセッションの橋渡しをします。
- チャットのプラットフォーム(Telegram・Discord):プラグインがローカルで動き、プラットフォームの API を新しいメッセージのためにポーリングする。だれかがボットに DM すると、プラグインがそれを受け取って Claude に転送する。公開する URL は要らない
- Webhook(CI・監視):サーバーがローカルの HTTP ポートで待ち受ける。外部のシステムがそのポートに POST し、サーバーがペイロードを Claude に送る
一方向のチャネルは、アラート・Webhook・監視のイベントを転送して Claude に処理させます。チャット橋渡しのような双方向のチャネルは、Claude が返信できるよう返信ツールも公開します。送信者の経路が信頼できるチャネルは、権限確認を中継して、遠隔でツールの使用を承認・拒否することも選べます。
必要なもの#
必須なのは、@modelcontextprotocol/sdk パッケージと、Node.js 互換のランタイムだけです。Bun・Node・Deno のどれでも動きます。研究プレビューの事前に作られたプラグインは Bun を使いますが、自作のチャネルは Bun でなくてもかまいません。サーバーが行う必要があること:
claude/channelの capability を宣言し、Claude Code が通知のリスナーを登録できるようにする- 何かが起きたとき
notifications/claude/channelのイベントを送る - stdio トランスポートで接続する
研究プレビューの間、自作のチャネルは承認済みの許可リストにありません。ローカルで試すには --dangerously-load-development-channels を使います。
例:Webhook の受信サーバー#
HTTP リクエストを待ち受けて Claude Code のセッションへ転送する、1ファイルのサーバーです。HTTP POST を送れるもの(CI のパイプライン・監視のアラート・curl コマンド)なら何でも、Claude にイベントを送れます。この例は Bun を使いますが、Node や Deno でもかまいません。
- プロジェクトを作ります(権限中継の例が
zodを直接 import するので、MCP SDK と一緒に入れます)
mkdir webhook-channel && cd webhook-channel
bun add @modelcontextprotocol/sdk zod
webhook.tsを作ります。stdio で Claude Code に接続し、ポート 8788 で HTTP の POST を待ち受けます。リクエストが届くと、本文を channel イベントとして Claude へ送ります
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
// Create the MCP server and declare it as a channel
const mcp = new Server(
{ name: 'webhook', version: '0.0.1' },
{
// this key is what makes it a channel — Claude Code registers a listener for it
capabilities: { experimental: { 'claude/channel': {} } },
// Claude Code delivers this to Claude as context when the server connects, so it knows how to handle these events
instructions: 'Events from the webhook channel arrive as <channel source="webhook" ...>. They are one-way: read them and act, no reply expected.',
},
)
// Connect to Claude Code over stdio (Claude Code spawns this process)
await mcp.connect(new StdioServerTransport())
// Start an HTTP server that forwards every POST to Claude
Bun.serve({
port: 8788, // any open port works
// localhost-only: nothing outside this machine can POST
hostname: '127.0.0.1',
async fetch(req) {
const body = await req.text()
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: body, // becomes the body of the <channel> tag
// each key becomes a tag attribute, e.g. <channel path="/" method="POST">
meta: { path: new URL(req.url).pathname, method: req.method },
},
})
return new Response('ok')
},
})
- サーバーの設定:capabilities に
claude/channelを持つ MCP サーバーを作る。これが、Claude Code にチャネルだと伝える。instructionsの文字列は、サーバーの接続時に Claude への文脈として届くので、どんなイベントが来るか・返信するか・返信するならどう振り分けるかを伝える - stdio の接続:stdin/stdout で Claude Code に接続する。どの MCP サーバーにも標準の形
- HTTP のリスナー:ポート 8788 でローカルの Web サーバーを起動する。POST の本文はすべて
mcp.notification()で channel イベントとして Claude に転送される。contentがイベントの本文になり、metaの各エントリが<channel>タグの属性になる。リスナーはmcpのインスタンスが要るので、同じプロセスで動く
- サーバーを MCP の設定に登録します。プロジェクトの
.mcp.jsonでは相対パス、~/.claude.jsonのユーザー設定ではどのプロジェクトからも見つかるよう絶対パスを使います
{
"mcpServers": {
"webhook": { "command": "bun", "args": ["./webhook.ts"] }
}
}
- 試します。研究プレビューの間、自作のチャネルは許可リストに無いので、開発用フラグで Claude Code を起動します
claude --dangerously-load-development-channels server:webhook
- Claude Code は最初に、読み込む開発用チャネルを並べた全画面の警告ダイアログを出します。続けるには「I am using this for local development」、終了するには「Exit」を選びます
- このプロジェクトでセッションを初めて始めるときは、
.mcp.jsonの新しいサーバーを使う同意も求めます。ダイアログは「New MCP server found in this project: webhook」と出るので、「Use this MCP server」を選びます - 承認すると、Claude Code が
webhook.tsをサブプロセスとして起動し、HTTP のリスナーが設定したポート(この例では 8788)で自動で始まります。サーバーを自分で動かす必要はありません - 起動バナーの下に、チャネルが登録されたことを示す薄い通知が出ます:
Channels (experimental) messages from server:webhook inject directly in this session · restart without --dangerously-load-development-channels to stop - 「blocked by org policy」と出たら、組織の管理者が先にチャネルを有効にする必要があります
別のターミナルで、サーバーへメッセージ付きの HTTP POST を送って Webhook を真似ます(CI の失敗のアラートをポート 8788 に送る例)。
curl -X POST localhost:8788 -d "build failed on main: https://ci.example.com/run/1234"
ペイロードは、Claude の文脈に <channel> タグで届きます。
<channel source="webhook" path="/" method="POST">
build failed on main: https://ci.example.com/run/1234
</channel>
ターミナルには、生のタグでなく、← webhook: build failed on main: https://ci.example.com/run/1234 という1行の要約が出ます。続いて Claude が、ファイルの読み取りやコマンドの実行など、メッセージが求めることに取りかかります。これは一方向のチャネルなので、Claude はセッションの中で動きますが、Webhook へは何も返しません。
イベントが届かないときは、curl が返したもので切り分けます。
curlは成功するが Claude に何も届かない:セッションで/mcpを実行してサーバーの状態を確かめる。failedの状態は、通常、サーバーのファイルの依存関係か import のエラー。stderr のトレースを見るには、claude --debug --dangerously-load-development-channels server:webhookで再起動し、~/.claude/debug/<session-id>.txtのデバッグログを見るcurlが「connection refused」で失敗する:ポートがまだ使われていないか、前の実行の古いプロセスが握っている。lsof -i :<port>で待ち受けているものが分かるので、セッションを再起動する前に、古いプロセスをkillする
開発用フラグでのテスト#
研究プレビューの間、すべてのチャネルは、登録されるために承認済みの許可リストに載っている必要があります。開発用フラグは、確認のプロンプトの後、個々のエントリについて許可リストを迂回します。両方のエントリの種類の例:
# Testing a plugin you're developing
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace
# Testing a bare .mcp.json server (no plugin wrapper yet)
claude --dangerously-load-development-channels server:webhook
迂回はエントリごとです。このフラグを --channels と組み合わせても、--channels のエントリへは迂回が及びません。研究プレビューの間、自作のチャネルは承認済みの許可リストにないので、作って試す間は、開発用フラグのままにします。
補足
このフラグが飛ばすのは許可リストだけです。channelsEnabled の組織のポリシーは、それでも適用されます。信頼できない出どころのチャネルを動かすために使わないでください。
サーバーのオプション#
チャネルは、Server のコンストラクターで次のオプションを設定します。instructions と capabilities.tools は標準の MCP で、capabilities.experimental['claude/channel'] と capabilities.experimental['claude/channel/permission'] がチャネル固有の追加です。
| フィールド | 型 | 内容 |
|---|---|---|
capabilities.experimental['claude/channel'] |
object |
必須。常に {}。あると通知のリスナーが登録される |
capabilities.experimental['claude/channel/permission'] |
object か false |
任意。{} にすると、このチャネルが権限中継のリクエストを受け取れると宣言する。宣言すると、Claude Code がツールの承認確認をチャネルへ転送し、遠隔で承認・拒否できる。無効にするには、キーを省くか false にする。v2.1.234 より前は、false を宣言したものとして扱っていた |
capabilities.tools |
object |
双方向だけ。常に {}。標準の MCP のツールの capability |
instructions |
string |
推奨。サーバーの接続時に Claude Code が Claude に文脈として渡す。どんなイベントが来るか・<channel> タグの属性の意味・返信するか、するならどのツールを使い、どの属性(chat_id など)を返すかを伝える |
一方向のチャネルにするには、capabilities.tools を省きます。チャネルの capability・ツール・instructions を設定した、双方向の例:
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
const mcp = new Server(
{ name: 'your-channel', version: '0.0.1' },
{
capabilities: {
experimental: { 'claude/channel': {} }, // registers the channel listener
tools: {}, // omit for one-way channels
},
// Claude Code delivers this to Claude as context when the server connects, so it knows how to handle your events
instructions: 'Messages arrive as <channel source="your-channel" ...>. Reply with the reply tool.',
},
)
通知の形式#
サーバーは、2つの params を持つ notifications/claude/channel を送ります。
| フィールド | 型 | 内容 |
|---|---|---|
content |
string |
イベントの本文。<channel> タグの本文として届く |
meta |
Record<string, string> |
任意。各エントリが、チャット ID・送信者名・アラートの重大度などの振り分けの文脈として、<channel> タグの属性になる。キーは識別子でなければならない(英字・数字・アンダースコアだけ)。ハイフンなどの文字を含むキーは、黙って捨てられる |
サーバーは、Server のインスタンスの mcp.notification() を呼んでイベントを送ります。2つの meta キーを持つ CI の失敗のアラートを送る例:
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: 'build failed on main: https://ci.example.com/run/1234',
meta: { severity: 'high', run_id: '1234' },
},
})
イベントは、<channel> タグで包まれて Claude の文脈に届きます。source 属性は、サーバーの設定された名前から自動で設定されます。
<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>
- Claude Code は通知に確認応答しません。
mcp.notification()のawaitが解決するのは、メッセージがトランスポートに書かれたときで、Claude が処理したときではありません。セッションがサーバーをチャネルとして読み込んでいないか、組織のポリシーがブロックしていると、Claude Code はイベントを黙って捨て、サーバーにエラーを返しません - 配信の確認が要るなら、サーバーでイベントの状態を追跡し、Claude が状態を報告するために呼べる返信ツールを公開します
- イベントはセッションに積まれ、順に処理されます。Claude が忙しい間に複数の通知が届くと、次のターンでまとめて届き、Claude はそれをひとまとまりとして扱います。独立したイベントの流れを並行して処理するには、別々のセッションを動かします
返信ツールを公開する#
チャネルが双方向(アラートの転送でなくチャットの橋渡し)なら、Claude がメッセージを送り返すために呼べる、標準の MCP ツールを公開します。ツールの登録にチャネル固有のことはありません。返信ツールには3つの要素があります。
Serverのコンストラクターの capabilities にtools: {}を入れ、Claude Code がツールを見つけられるようにする- ツールのスキーマを定義し、送信のロジックを実装するツールのハンドラー
- Claude にいつ・どうツールを呼ぶかを伝える、
Serverのコンストラクターのinstructionsの文字列
上の Webhook の受信サーバーに足す手順です。
- ツールの検出を有効にする:
webhook.tsのServerのコンストラクターで、capabilities にtools: {}を足す
capabilities: {
experimental: { 'claude/channel': {} },
tools: {}, // enables tool discovery
},
- 返信ツールを登録する:
webhook.tsに次を足す。importはファイルの先頭にほかの import と並べ、2つのハンドラーはServerのコンストラクターとmcp.connect()の間に置く。chat_idとtextで Claude が呼べるreplyツールが登録される
// Add this import at the top of webhook.ts
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
// Claude queries this at startup to discover what tools your server offers
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Send a message back over this channel',
// inputSchema tells Claude what arguments to pass
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'The conversation to reply in' },
text: { type: 'string', description: 'The message to send' },
},
required: ['chat_id', 'text'],
},
}],
}))
// Claude calls this when it wants to invoke a tool
mcp.setRequestHandler(CallToolRequestSchema, async req => {
if (req.params.name === 'reply') {
const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
// send() is your outbound: POST to your chat platform, or for local testing an SSE broadcast
send(`Reply to ${chat_id}: ${text}`)
return { content: [{ type: 'text', text: 'sent' }] }
}
throw new Error(`unknown tool: ${req.params.name}`)
})
- instructions を更新する:返信をツール経由で返すよう Claude に伝える。受信のタグの
chat_idを渡すよう伝える例:
instructions: 'Messages arrive as <channel source="webhook" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.'
send()は自分の送信の処理です:チャットのプラットフォームへの POST、またはローカルのテストなら Server-Sent Events(SSE)での配信です。公式の完全な例は、返信をGET /eventsの SSE で流し、curl -N localhost:8788/eventsでライブに見られるようにし、受信のチャットをPOST /で受けます- ファイルの添付とメッセージの編集を含む、より完全な例は、公式の fakechat サーバーにあります
受信メッセージをゲートする#
ゲートのないチャネルは、プロンプトインジェクションの経路です。エンドポイントに届く人なら誰でも、Claude の前にテキストを置けます。チャットのプラットフォームや公開のエンドポイントを待ち受けるチャネルは、何かを送る前に、本物の送信者の確認が必要です。
mcp.notification() を呼ぶ前に、送信者を許可リストと照合します。許可リストに無い送信者のメッセージを捨てる例:
const allowed = new Set(loadAllowlist()) // from your access.json or equivalent
// inside your message handler, before emitting:
if (!allowed.has(message.from.id)) { // sender, not room
return // drop silently
}
await mcp.notification({ ... })
チャットやルームの識別子でなく、送信者の識別子でゲートします(例の message.from.id。message.chat.id ではない)。グループチャットではこの2つが違い、ルームでゲートすると、許可リストに入ったグループの誰もがセッションへメッセージを注入できてしまいます。Telegram と Discord のチャネルは、同じように送信者の許可リストでゲートし、ペアリングで一覧を始めます。iMessage のチャネルは別の方法で、起動時に Messages のデータベースからユーザー自身のアドレスを検出して自動で通し、ほかの送信者はハンドルで追加します。
権限確認を中継する#
Claude が承認の要るツールを呼ぶと、ローカルのターミナルのダイアログが開き、セッションは待ちます。双方向のチャネルは、同じ確認を並行して受け取り、別の端末のあなたへ中継することを選べます。どちらも有効なままで、ターミナルでもスマートフォンでも答えられ、Claude Code は先に届いた答えを適用して、もう一方を閉じます。
- 中継の対象は
Bash・Write・Editなどのツールの使用の承認です。プロジェクトの信頼と MCP サーバーの同意のダイアログは中継されず、ローカルのターミナルにだけ出ます - Claude Code v2.1.234 以降は、権限リクエストを、そのセッションでチャネルとして登録したサーバーにだけ送ります。そのため、中継も、メッセージの配信と同じ、セッションごとの選択と組織の制御の背後にあります。中継には、
--channelsか開発用フラグでサーバーを選ぶことと、サーバーが権限の capability を宣言することも必要です
権限確認が開いたときの中継のループは、4つの手順です。
- Claude Code が短いリクエスト ID を作り、サーバーに通知する
- サーバーが、確認と ID をチャットアプリへ転送する
- 遠隔のユーザーが、yes か no と、その ID で返信する
- 受信のハンドラーが返信を判定に解析し、Claude Code は、開いているリクエストに ID が一致したときだけそれを適用する
その間、ローカルのターミナルのダイアログは開いたままです。ターミナルの人が、遠隔の判定が届く前に答えると、その答えが適用され、保留中の遠隔のリクエストは捨てられます。
権限リクエストのフィールド#
Claude Code からの送信の通知は notifications/claude/channel/permission_request です。チャネルの通知と同じく、トランスポートは標準の MCP ですが、メソッドとスキーマは Claude Code の拡張です。params オブジェクトは、サーバーが送信のプロンプトに整形する4つの文字列のフィールドを持ちます。
| フィールド | 内容 |
|---|---|
request_id |
a〜z から l を除いた小文字5文字。スマートフォンで入力しても 1 や I と読めないため。返信にそのまま返せるよう、送信のプロンプトに含める。Claude Code は、自分が発行した ID を持つ判定だけを受け付ける。ローカルのターミナルのダイアログはこの ID を表示しないので、ID を知る方法は、送信のハンドラーだけ |
tool_name |
Claude が使いたいツールの名前(Bash や Write など) |
description |
この特定のツール呼び出しが何をするかの、人が読める要約。コマンド自体ではない。Bash の呼び出しでは、Claude のコマンドの説明。モデルが説明を付けなかったときは、コマンドの詳細を持たない固定の Run shell command。余裕があれば input_preview を表示する |
input_preview |
ツールの引数を、最上位のフィールドごとのキーで示す、JSON 形の表示テキスト。Bash ではコマンド、Write ではファイルパスと内容。1行のメッセージの余裕しかないなら、プロンプトから省く。何を見せるかはサーバーが決める |
- v2.1.211 以降のクライアントは、中継する前に
descriptionとinput_previewを無害化します。受け取るテキストに3つの変化があります:方向を上書きする文字・不可視文字・引用符と山括弧に似た文字が無害化される/空白の連続が1つの空白にたたまれる/3,500 コードポイントまではそのまま中継され、それより長い値は、数を数えた⋯ N code points elided ⋯の印を挟んだ先頭と末尾が届く(長いコマンドの末尾も、承認する人に届く) input_previewでは、3,500 の上限を引数の最上位のフィールドごとに別々に適用し、JSON 自身の構造の引用符は保ちます。v2.1.211 より前のクライアントは、descriptionをそのまま中継し、input_previewを 200 UTF-16 単位で、末尾に省略記号を付けて切ります- v2.1.234 以降のクライアントは、安全に直列化できない
input_previewのフィールドの値(循環構造・極端に大きな配列など)の代わりに(value unserializable)の印を中継します。そのフィールドのキーは届き、プレビューのほかのフィールドは変わりません - v2.1.234 以降のクライアントは、
descriptionとinput_previewの認証情報も伏せます。API キーや個人用アクセストークンのように、見分けのつくプロバイダーの認証トークンの代わりに[REDACTED]が届きます。フィールドを表示するときは、伏せ字の3つの影響を想定します:input_previewの中の値だけでなくキー名も伏せられるので、表示するキー名が入力のキー名と一致しないことがある/シェルの構文・パスの文字・URL の文字を含む範囲は決して伏せないので、伏せ字は承認されるコマンド・ファイルパス・送信先を隠せない/見分けのつく接頭辞を持たない秘密や、秘密鍵のブロックのように空白をまたぐ秘密は伏せられず、伏せ字なしでサーバーに届く - 伏せ字は、フィールドを受け取る相手を変えません。伏せられずに残るものは、
--channelsか開発用フラグで選んだサーバーにだけ行きます。クライアントの群を自分で管理していない限り、両方のフィールドを信頼できないものとして扱います
サーバーが返す判定は、2つのフィールドを持つ notifications/claude/channel/permission です:上の ID をそのまま返す request_id と、'allow' か 'deny' を設定した behavior です。allow はツール呼び出しを進め、deny は拒否します。どちらの判定も、今後の呼び出しには影響しません。
チャット橋渡しに中継を足す#
双方向のチャネルへ権限中継を足すには、3つの要素が要ります。
Serverのコンストラクターのexperimentalの capabilities にclaude/channel/permission: {}を入れ、Claude Code が確認を転送すると分かるようにするnotifications/claude/channel/permission_requestの通知ハンドラー。確認を整形して、プラットフォームの API から送り出す- 受信メッセージのハンドラーで、
yes <id>かno <id>を認識し、テキストを Claude へ転送する代わりにnotifications/claude/channel/permissionの判定を送る確認
注意
capability を宣言するのは、チャネルが送信者を認証できるときだけにします。チャネルから返信できる人は、あなたのセッションのツールの使用を承認・拒否できるためです。
- 権限の capability を宣言する:
Serverのコンストラクターで、experimentalのclaude/channelと並べてclaude/channel/permission: {}を足す
capabilities: {
experimental: {
'claude/channel': {},
'claude/channel/permission': {}, // opt in to permission relay
},
tools: {},
},
- 受信のリクエストを処理する:
Serverのコンストラクターとmcp.connect()の間に通知ハンドラーを登録する。権限ダイアログが開くと、Claude Code が4つのフィールドでそれを呼ぶ。ハンドラーはプラットフォーム向けにプロンプトを整形し、ID で返信する方法の案内を含める
import { z } from 'zod'
// setNotificationHandler routes by z.literal on the method field,
// so this schema is both the validator and the dispatch key
const PermissionRequestSchema = z.object({
method: z.literal('notifications/claude/channel/permission_request'),
params: z.object({
request_id: z.string(), // five lowercase letters, include verbatim in your prompt
tool_name: z.string(), // e.g. "Bash", "Write"
description: z.string(), // summary of this call. Treat as untrusted.
input_preview: z.string(), // tool args as JSON-shaped text. Treat as untrusted.
}),
})
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
send(
`Claude wants to run ${params.tool_name}: ${params.description}\n` +
// input_preview carries the actual arguments; for Bash the description
// alone may be just "Run shell command" with zero command detail
`${params.input_preview}\n\n` +
// the ID in the instruction is what your inbound handler parses in the next step
`Reply "yes ${params.request_id}" or "no ${params.request_id}"`,
)
})
- 受信ハンドラーで判定を拾う:受信ハンドラーは、プラットフォームからメッセージを受け取るループやコールバックで、送信者でゲートし、チャットを Claude に転送する
notifications/claude/channelを出すのと同じ場所。チャットの転送の前に、判定の形式を認識して、代わりに権限の通知を出す確認を足す。正規表現は、Claude Code が作る ID の形式(5文字、lを含まない)に一致する。/iフラグは、スマートフォンの自動修正が返信を大文字にしても許容し、送り返す前に ID を小文字にする
// matches "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] is the ID alphabet Claude Code uses (lowercase, skips 'l')
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
async function onInbound(message: PlatformMessage) {
if (!allowed.has(message.from.id)) return // gate on sender first
const m = PERMISSION_REPLY_RE.exec(message.text)
if (m) {
// m[1] is the verdict word, m[2] is the request ID
await mcp.notification({
method: 'notifications/claude/channel/permission',
params: {
request_id: m[2].toLowerCase(), // normalize in case of autocorrect caps
behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
},
})
return // handled as verdict, don't also forward as chat
}
// didn't match verdict format: fall through to the normal chat path
await mcp.notification({
method: 'notifications/claude/channel',
params: { content: message.text, meta: { chat_id: String(message.chat.id) } },
})
}
期待する形式に正確に一致しない遠隔の返信は、2通りのどちらかで失敗し、どちらの場合もローカルのターミナルのダイアログは開いたままです。
- 形式が違う:受信ハンドラーの正規表現が一致しないので、
approve itや ID なしのyesのようなテキストは、通常のメッセージとして Claude に流れる - 形式は合っているが ID が違う:サーバーは判定を出すが、Claude Code がその ID を持つ開いているリクエストを見つけられず、黙って捨てる
3つの拡張を合わせた動作の試し方#
公式ドキュメントには、返信ツール・送信者のゲート・権限中継を合わせた完全な webhook.ts があります。HTTP のリスナーは2つのパスを持ちます:GET /events は SSE のストリームを開いたまま、送信の各メッセージを data: 行で流し、curl -N で Claude の返信と権限確認がライブに届くのを見られます。POST / は受信側で、チャットの転送の前に判定形式の確認を挟み、ゲートは X-Sender ヘッダーの値(この例では dev)で行います。判定の経路を3つのターミナルで試します。
- 1つ目は Claude Code のセッション。開発用フラグで起動して
webhook.tsを起動させる(claude --dangerously-load-development-channels server:webhook)。この試しは権限ダイアログ自体を確かめるので、セッションが開いたら、ステータスバーに⏸ manual mode onが出るまで Shift+Tab を押す(auto モードでは、replyの呼び出しを分類器が決め、遠隔が答えるダイアログが開かない) - 2つ目で、送信側のストリームを見る:
curl -N localhost:8788/events - 3つ目で、Claude にコマンドを実行させそうなメッセージを送る:
curl -d "list the files in this directory" -H "X-Sender: dev" localhost:8788
ファイルの一覧は読み取り専用なので、Claude は承認なしで実行します。権限ダイアログが開くのは、Claude が答えを返すために reply ツールを呼ぶときです。ローカルのダイアログが Claude Code のターミナルに開き、少しして、5文字の ID を含む mcp__webhook__reply の確認が /events のストリームに出ます。遠隔から承認します:curl -d "yes <id>" -H "X-Sender: dev" localhost:8788。ローカルのダイアログが閉じ、reply ツールが動き、Claude の返信がストリームに出ます。
このファイルのチャネル固有の3つの部品は次のとおりです。
Serverのコンストラクターの capabilities:claude/channelが通知のリスナーを登録し、claude/channel/permissionが権限中継を選び、toolsが Claude に返信ツールを見つけさせる- 送信の経路:
replyツールのハンドラーは、Claude が会話の応答のために呼ぶもの。PermissionRequestSchemaの通知ハンドラーは、権限ダイアログが開いたとき Claude Code が呼ぶもの。どちらもsend()で/eventsに流すが、システムの別の部分が引き金になる - HTTP のハンドラー:
GET /eventsが SSE のストリームを開いたままにして、curl で送信をライブに見られるようにする。POSTは受信で、X-Senderヘッダーでゲートする。yes <id>かno <id>の本文は、判定の通知として Claude Code に行き、Claude には届かない。それ以外は、channel イベントとして Claude に転送される
プラグインとしてまとめる#
チャネルをインストールして共有できるようにするには、プラグインで包み、マーケットプレイスに公開します。ユーザーは /plugin install で入れ、セッションごとに --channels plugin:<name>@<marketplace> で有効にします。
- 自分のマーケットプレイスに公開したチャネルも、承認済みの許可リストに無いので、動かすには
--dangerously-load-development-channelsが必要です。既定の許可リストは、claude-plugins-officialのチャネルのプラグインです。コミュニティのマーケットプレイスは、チャネルの許可リストに入っていません - Anthropic のパートナーの窓口と作業しているなら、公式のマーケットプレイスへの掲載の調整は、その窓口に相談します。Team と Enterprise のプランでは、管理者が、代わりに、組織自身の
allowedChannelPluginsの一覧にそのプラグインを入れられます(既定の Anthropic の許可リストを置き換えます)
関連#
- 作ったチャネルをまとめて配るならプラグインを作って配る
- チャネルサーバーが実装する元のプロトコルは MCP
- イベントを押し込む代わりに、スマートフォンから手元のセッションを操るならリモートコントロール
- 押されたイベントに反応するのでなく、タイマーでポーリングするなら定期実行と /loop
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。