3月20日、Anthropicが「Claude Code Channels」をresearch previewとして発表した。TelegramやDiscordからClaude Codeに指示を飛ばせる、いわばチャットアプリとコーディングエージェントの直結パイプ。
で、思った。Telegramなんて仕事で使わないんだけど。
日本のビジネスチャットはChatworkかSlack。うちのチームはChatworkを6年使い倒している。「Chatworkからスマホで指示を飛ばせたら、外出中でもコードが書ける」——これ、やるしかない。
公式のソースコードが公開されていたので、分解して、Chatwork版を作った。探した限り、Chatwork版のChannel実装は見当たらない。たぶん世界初。
Claude Code Channelsとは?
30秒で説明する。
従来のClaude Code × Chatwork連携はMCPサーバー経由だった。Claude Codeが「Chatworkのメッセージ取ってきて」と主動的にAPIを叩く方式。つまりClaude Codeが動いていないと何も始まらない。
Channelsは逆。Chatwork側からClaude Codeにメッセージをpushする。 外出先のスマホでChatworkに「あのバグ直して」と書くと、Mac上のClaude Codeに届いて勝手にコーディングが始まる。
従来のMCP:
Claude Code → (pull) → Chatwork API
「取りに行く」
Channels:
Chatwork → (push) → Claude Code
「届く」
地味な違いに見えるけど、使い心地がまるで違う。
公式Telegram/Discordの中身を分解する
Anthropicは公式プラグインのソースコードを丸ごとGitHubに公開している。anthropics/claude-plugins-officialというリポジトリに、Telegram・Discord・Fakechat(テスト用)の3つが入っている。TypeScript/Bun。
読んでみたら、仕組みはシンプルだった。
Channelの正体 = 特殊なMCPサーバー
ChannelはMCPサーバーの拡張版だ。通常のMCPサーバーに claude/channel というケイパビリティを追加すると、Claude Codeが「このサーバーからのpush通知を受け取りますよ」と認識する。
const mcp = new Server(
{ name: 'chatwork-channel', version: '0.1.0' },
{
capabilities: {
experimental: {
'claude/channel': {}, // ← これがChannel化の鍵
'claude/channel/permission': {}, // ← Permission Relay(後述)
},
tools: {},
},
}
);
メッセージのpushは notifications/claude/channel という通知メソッドで行う。
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: 'ここにメッセージ本体',
meta: {
chat_id: 'ルームID',
sender: '送信者名',
}
}
});
Claude Code側では <channel source="chatwork-channel" sender="太郎">ここにメッセージ本体</channel> というタグとして届く。
Telegram公式のツール構成
| ツール | 機能 |
|---|---|
reply |
テキスト・画像の送信。長文自動分割 |
react |
絵文字リアクション |
edit_message |
送信済みメッセージの編集 |
Discord公式のツール構成
Telegramに加えて fetch_messages(履歴取得)と download_attachment(ファイルDL)がある。5ツール体制。
セキュリティ設計
ここが一番重要。公式はSender Gating(送信者フィルタリング)をルームIDではなく送信者IDで行っている。
❌ message.chat.id でフィルタ → グループ内の誰でも注入できる
✅ message.from.id でフィルタ → 許可ユーザーのみ
チャットルームのIDでフィルタすると、そのルームにいる全員がClaude Codeに指示を飛ばせてしまう。プロンプトインジェクションの温床になる。Telegram公式はAllowlist方式で、ペアリングフローまで実装している。
Permission Relay(権限リレー)も面白い。Claude Codeがファイル書き込みなどの許可を求めるとき、ローカルのダイアログと同時にチャットにも通知が飛ぶ。外出先から yes abcde と返すだけで承認できる。l(小文字エル)を除いた5文字コードで、スマホでも打ち間違えない配慮がされている。
Chatwork Channelを作る
公式の設計がわかったので、Chatwork版を実装する。
必要なもの
- Node.js v20以上(Bunでも可)
-
@modelcontextprotocol/sdkパッケージ - Chatwork APIトークン
- Claude Code v2.1.80以上(Permission Relayはv2.1.81以上)
プロジェクト初期化
mkdir chatwork-channel && cd chatwork-channel
npm init -y
npm install @modelcontextprotocol/sdk zod
package.json に "type": "module" を追加する。MCP SDKはESMだけ。
基本構造(chatwork-channel.mjs)
全体で約270行。核心部分だけ抜き出す。
1. Chatwork APIヘルパー
const CW_BASE = 'https://api.chatwork.com/v2';
async function cwFetch(path, options = {}) {
const res = await fetch(`${CW_BASE}${path}`, {
...options,
headers: {
'X-ChatWorkToken': process.env.CHATWORK_API_TOKEN,
...options.headers,
},
});
if (res.status === 204) return null; // No Content(正常)
if (!res.ok) throw new Error(`Chatwork API ${res.status}`);
return res.json();
}
Chatwork APIは新着メッセージなしのとき204を返す。最初これを忘れて Unexpected end of JSON input で10分溶かした。
2. ポーリングループ
Chatwork APIにはWebhookがあるが、Channelsはstdioトランスポートで動くのでHTTPサーバーが立てられない。ポーリング一択。
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i;
async function poll() {
const messages = await getMessages(ROOM_ID, isFirstPoll);
if (!messages?.length) return;
for (const msg of messages) {
if (isFirstPoll) { lastMessageId = msg.message_id; continue; }
if (String(msg.account.account_id) === MY_ACCOUNT_ID) continue;
if (lastMessageId && msg.message_id <= lastMessageId) continue;
lastMessageId = msg.message_id;
const body = (msg.body || '').trim();
if (!body) continue;
// Permission Relay応答チェック
const m = PERMISSION_REPLY_RE.exec(body);
if (m) {
await mcp.notification({
method: 'notifications/claude/channel/permission',
params: {
request_id: m[2].toLowerCase(),
behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
},
});
continue;
}
// 通常メッセージ → Claude Codeにpush
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: body,
meta: {
chat_id: ROOM_ID,
sender: msg.account.name || 'unknown',
account_id: String(msg.account.account_id),
},
},
});
}
isFirstPoll = false;
}
初回ポーリングは lastMessageId を記録するだけで、過去メッセージはpushしない。これをやらないと、セッション開始時に過去のメッセージが全部Claude Codeに流れ込んで大惨事になる。
3. ツール定義(公式準拠)
公式Telegram/Discordに合わせて4ツール実装した。
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'reply',
description: 'ルームにメッセージを返信する',
inputSchema: { /* chat_id, text, reply_to */ },
},
{
name: 'edit_message',
description: '送信済みメッセージを編集する',
inputSchema: { /* chat_id, message_id, text */ },
},
{
name: 'fetch_messages',
description: 'メッセージ履歴を取得する',
inputSchema: { /* chat_id, limit */ },
},
{
name: 'download_file',
description: 'ファイル一覧・ダウンロードURLを取得する',
inputSchema: { /* chat_id, file_id */ },
},
],
}));
Chatwork APIにはリアクション機能がないので react ツールは見送った。公式Telegramの3ツール(reply, react, edit_message)に対して、うちは4ツール(reply, edit_message, fetch_messages, download_file)。履歴取得とファイルDLはDiscord公式と同等。
.mcp.jsonに登録
{
"mcpServers": {
"chatwork-channel": {
"command": "node",
"args": ["tools/chatwork-channel/chatwork-channel.mjs"],
"env": {
"CHATWORK_API_TOKEN": "あなたのトークン",
"CHATWORK_ROOM_ID": "対象ルームID",
"CHATWORK_MY_ACCOUNT": "自分のaccount_id",
"CHATWORK_POLL_SEC": "10"
}
}
}
}
動かしてみた
起動
claude --dangerously-load-development-channels server:chatwork-channel
最初 --channels server:chatwork-channel で起動したら、黄色い警告が出た。
server:chatwork-channel · server: entries need
--dangerously-load-development-channels
公式Allowlistに載っていないサードパーティ実装は --dangerously-load-development-channels が必要。名前に dangerously が入ってるのは「開発中なので許して」という意味。research previewだから仕方ない。
起動すると緑色で表示される。
Listening for channel messages from: server:chatwork-channel
これが出ればChannel待機状態。ポーリングが回り始めている。
テスト1: 送信(Claude Code → Chatwork)
Claude Codeから reply ツールでChatworkに送信。Chatworkに [info][title]Claude Code[/title] タグ付きで届く。タイトル付き枠線で表示されるので、普通の投稿と区別がつく。
テスト2: 履歴取得
fetch_messages で直近のメッセージを取得。自動通知も他メンバーの投稿も取れる。
テスト3: メッセージ編集
edit_message で送信済みメッセージを編集。タイトルが「Claude Code(編集済み)」に変わる。
テスト4: push通知(Chatwork → Claude Code)★本丸
これがChannelsの真骨頂。スマホのChatworkアプリから「聞こえますか?」と送ったら、ターミナルのClaude Codeに届いた。
← chatwork-channel: [To:11166065]AI-managementさん 聞こえますか?
Claude Codeが自動で反応し、reply ツールでChatworkに返信を返した。外出先のスマホから送ったメッセージが、自宅のMac上のClaude Codeに到達して、勝手にコーディングが始まる。 これがpull型のMCPとの決定的な違い。
テスト5: Permission Relay(遠隔許可)
Claude Codeがファイル書き込みの許可を求めたとき、Chatworkに通知が飛んだ。
[Claude Code パーミッション確認]
ツール: reply
内容: Chatworkルームにメッセージを返信する
許可する場合: yes cujoh
拒否する場合: no cujoh
スマホで yes cujoh と返すだけで承認完了。Claude Codeは承認を受け取って処理を続行した。
5文字コードは l(小文字エル)を除外した [a-km-z] で生成される。スマホのフリック入力でも打ち間違えない配慮。
テスト6: リモートコーディング(フルワークフロー)
最後にフルワークフローを試した。スマホのChatworkから「README.mdを作って」と送ったら、こうなった。
- Chatwork → Channel push — メッセージがClaude Codeに到着
- Claude Codeが作業開始 — ファイルを読み、README.mdを生成
- Permission Relay — ファイル書き込みの許可をChatworkに通知
-
スマホで
yes xxxxx— 承認 - ファイル作成完了 — Chatworkに完了報告が届く
外出先のスマホで「あのバグ直して」「テスト書いて」と送るだけで、自宅のMacでコードが書かれ、許可もスマホから出せる。これはもうリモートコーディングの完成形に近い。
公式との比較
| 機能 | Telegram公式 | Discord公式 | Chatwork版 |
|---|---|---|---|
| reply | ✅ | ✅ | ✅ |
| react | ✅ | ✅ | — (APIなし) |
| edit_message | ✅ | ✅ | ✅ |
| fetch_messages | — | ✅ | ✅ |
| download_file | — | ✅ | ✅ |
| Sender Gating | ✅ | ✅ | ✅ |
| Permission Relay | ✅ | ✅ | ✅ |
| Pairing Flow | ✅ | ✅ | — (静的設定) |
| 画像送受信 | ✅ | ✅ | — (未実装) |
Pairing Flowは未実装だが、業務利用では固定メンバーなので静的Allowlistで十分だと思っている。画像送受信はChatwork APIで対応可能なので、次のバージョンで入れたい。
ハマったポイント
1. MCP SDKはESMオンリー
"type": "module" を package.json に書かないと ERR_MODULE_NOT_FOUND で死ぬ。地味だけど最初のハードル。
2. Chatwork APIの204レスポンス
新着メッセージなしのとき、Chatworkは 204 No Content を返す。bodyが空なので res.json() するとパースエラー。ステータスコードを先にチェックして null を返す処理が必要だった。
3. 初回ポーリングの罠
ポーリング開始時に過去メッセージを全部pushすると、Claude Codeが過去のメッセージに反応し始めて収拾がつかなくなる。初回は lastMessageId を記録するだけにして、2回目以降から新着のみpushする設計にした。
4. --channels と --dangerously-load-development-channels の違い
--channels server:chatwork-channel で起動しても、公式Allowlistに入っていないと黄色い警告が出てChannelが有効にならない。開発中は --dangerously-load-development-channels が必要。claude --help に出てこない隠しフラグなので、最初は「フラグが存在しない」と思った。
5. 複数インスタンスの未読メッセージ競合
.mcp.json にChannelを登録すると、VS Codeのセッションでもターミナルのセッションでも同じMCPサーバーが起動する。Chatwork APIは GET /messages でメッセージを既読にするので、一方が先に読むともう一方に届かない。force=1 パラメータで常に全メッセージを取得し、lastMessageId で重複排除する設計に変更して解決した。
MCPとChannelsの使い分け
同じChatworkなのに、MCPとChannelsの2つの接続方式がある。混乱しそうなので整理しておく。
| MCP(既存) | Channels(今回) | |
|---|---|---|
| 方向 | Claude Code → Chatwork | Chatwork → Claude Code |
| トリガー | Claude Codeが能動的に取得 | Chatworkの新着がpushされる |
| 用途 | 「未読確認して」「あのルームのメッセージ取って」 | 「外出先からスマホで指示を飛ばす」 |
| セッション | Claude Code起動中のみ | Claude Code起動中のみ(同じ) |
両方使うのが正解。MCPで「過去メッセージの検索」、Channelsで「リアルタイムの指示受け」と使い分ける。
まとめ
- Claude Code Channelsは「特殊なMCPサーバー」。
claude/channelケイパビリティを足すだけ - 公式Telegram/Discordのソースコードが全公開されているので、設計パターンが丸わかり
- Chatwork版は270行で実装できた。4ツール + Permission Relay + Sender Gating
- 204エラーと初回ポーリングの罠に気をつければ、半日で動くものができる
- MCPとChannelsは併用する。pull型とpush型で使い分ける
非公式のChannel実装(Slack、LINE、WeChat等)もコミュニティで出始めている。APIがあるチャットサービスなら、同じ設計パターンで作れる。Channelsはresearch previewだからAPIが変わるリスクはあるけど、MCPの拡張なので大きな断絶はないと思ってる。
Chatworkユーザーで「外出先からコードを書かせたい」人がいたら、試してみてほしい。
Chatworkシリーズ
- #1 なぜ2026年にまだChatworkを使い倒しているのか
- #2 chatwork-client-gas、ぶっちゃけいるの?
- #3 ルームの参加者データだけで、組織の人間関係マップを作った
- #4 「Chatworkに確定連絡が来たら請求書を送る」をGASで自動化する
- #5 Chatwork MCPを繋いだら、17ルームの未読が10秒で片付いた
- #6 MCP vs GAS — Chatwork自動化の「正解」はどっちか
- #7 コンタクト承認をn8nで自動化しようとしたら、3つの罠にハマった
- #8 ChatworkにAIチームを住まわせたら、勝手に会話が始まった
- #9 Chatwork 8ルームの全メッセージからFAQ429件を自動抽出した
- #10 Webhook署名検証を入れたら全メッセージが消えた
- #11 過去メッセージを全件取得しようとしたら、APIの「100件の壁」にハマった
- #12 Chatwork APIの「既読」は自分で制御できる
- #13 Chatwork APIのファイル機能、使ったことある?
- #14 n8nで全ルーム巡回
- #15 タスク機能をAPIで使い倒す
- #16 MCPを2アカウント同時接続したら、仕事用と事務局用が1画面で回った
- #17 【世界初かもしれない】ChatworkでClaude Code Channelsを実装してみた(この記事)
- #18 Chatwork × Dify × GASで問い合わせ回答を自動提案する
- #19 RelationMapを夜間バッチで毎日自動更新する
- #20 17記事書いて見えた、Chatwork APIエコシステムに足りないもの
- #21 Googleフォームの回答をChatworkに自動投稿するGAS
- #22 Chatworkの会話を毎日AIが要約してくれる仕組みをn8nで作った話
- #23 chatwork-cliを入れたら、シェルからChatworkが操作できて世界が変わった
- #24 ChatworkのWebhookをn8nで受けるなら、HMAC署名検証は必ずやれ
- #25 Chatwork × GAS × Claude Codeで会員制講座の運用を自動化した