Referensi Channels
Bangun server MCP yang mendorong webhooks, alerts, dan pesan chat ke dalam sesi Claude Code. Referensi untuk kontrak channel: deklarasi kemampuan, event notifikasi, tools balasan, gating pengirim, dan relay izin.
Channels berada dalam research preview dan memerlukan Claude Code v2.1.80 atau lebih baru. Organisasi Team dan Enterprise harus secara eksplisit mengaktifkannya.
Sebuah channel adalah server MCP yang mendorong event ke dalam sesi Claude Code sehingga Claude dapat bereaksi terhadap hal-hal yang terjadi di luar terminal.
Anda dapat membangun channel satu arah atau dua arah. Channel satu arah meneruskan alerts, webhooks, atau event monitoring untuk Claude bertindak. Channel dua arah seperti chat bridges juga mengekspos tool balasan sehingga Claude dapat mengirim pesan kembali. Sebuah channel dengan jalur pengirim terpercaya juga dapat memilih untuk relay permission prompts sehingga Anda dapat menyetujui atau menolak penggunaan tool dari jarak jauh.
Halaman ini mencakup:
- Overview: bagaimana channels bekerja
- Yang Anda butuhkan: persyaratan dan langkah-langkah umum
- Contoh: bangun penerima webhook: panduan satu arah minimal
- Server options: field konstruktor
- Notification format: payload event dan perilaku pengiriman
- Expose a reply tool: biarkan Claude mengirim pesan kembali
- Gate inbound messages: pemeriksaan pengirim untuk mencegah prompt injection
- Relay permission prompts: teruskan permission prompts ke channels jarak jauh
Untuk menggunakan channel yang sudah ada daripada membangun satu, lihat Channels. Telegram, Discord, iMessage, dan fakechat disertakan dalam research preview.
Overview
Sebuah channel adalah server MCP yang berjalan di mesin yang sama dengan Claude Code. Claude Code menjalankannya sebagai subprocess dan berkomunikasi melalui stdio. Server channel Anda adalah jembatan antara sistem eksternal dan sesi Claude Code:
- Chat platforms (Telegram, Discord): plugin Anda berjalan secara lokal dan polling API platform untuk pesan baru. Ketika seseorang DM bot Anda, plugin menerima pesan dan meneruskannya ke Claude. Tidak ada URL untuk diekspos.
- Webhooks (CI, monitoring): server Anda mendengarkan pada port HTTP lokal. Sistem eksternal POST ke port tersebut, dan server Anda mendorong payload ke Claude.
Yang Anda butuhkan
Satu-satunya persyaratan keras adalah paket @modelcontextprotocol/sdk dan runtime yang kompatibel dengan Node.js. Bun, Node, dan Deno semuanya bekerja. Plugin pra-bangun dalam research preview menggunakan Bun, tetapi channel Anda tidak harus.
Server Anda perlu:
- Mendeklarasikan kemampuan
claude/channelsehingga Claude Code mendaftarkan pendengar notifikasi - Memancarkan event
notifications/claude/channelketika sesuatu terjadi - Terhubung melalui stdio transport (Claude Code menjalankan server Anda sebagai subprocess)
Bagian Server options dan Notification format mencakup masing-masing secara detail. Lihat Contoh: bangun penerima webhook untuk panduan lengkap.
Selama research preview, custom channels tidak ada di approved allowlist. Gunakan --dangerously-load-development-channels untuk menguji secara lokal. Lihat Test during the research preview untuk detail.
Contoh: bangun penerima webhook
Panduan ini membangun server file tunggal yang mendengarkan permintaan HTTP dan meneruskannya ke sesi Claude Code Anda. Pada akhirnya, apa pun yang dapat mengirim HTTP POST, seperti pipeline CI, alert monitoring, atau perintah curl, dapat mendorong event ke Claude.
Contoh ini menggunakan Bun sebagai runtime untuk server HTTP bawaan dan dukungan TypeScript. Anda dapat menggunakan Node atau Deno sebagai gantinya; satu-satunya persyaratan adalah MCP SDK.
Buat proyek
Buat direktori baru dan instal MCP SDK:
mkdir webhook-channel && cd webhook-channel
bun add @modelcontextprotocol/sdk
Tulis server channel
Buat file bernama webhook.ts. Ini adalah seluruh server channel Anda: terhubung ke Claude Code melalui stdio, dan mendengarkan POST HTTP pada port 8788. Ketika permintaan tiba, mendorong body ke Claude sebagai channel event.
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
// Buat server MCP dan deklarasikan sebagai channel
const mcp = new Server(
{ name: 'webhook', version: '0.0.1' },
{
// kunci ini adalah yang membuatnya menjadi channel โ Claude Code mendaftarkan pendengar untuknya
capabilities: { experimental: { 'claude/channel': {} } },
// ditambahkan ke system prompt Claude sehingga tahu cara menangani event ini
instructions: 'Events dari webhook channel tiba sebagai <channel source="webhook" ...>. Mereka satu arah: baca dan bertindak, tidak ada balasan yang diharapkan.',
},
)
// Terhubung ke Claude Code melalui stdio (Claude Code menjalankan proses ini)
await mcp.connect(new StdioServerTransport())
// Mulai server HTTP yang meneruskan setiap POST ke Claude
Bun.serve({
port: 8788, // port terbuka apa pun berfungsi
// localhost-only: tidak ada yang di luar mesin ini dapat POST
hostname: '127.0.0.1',
async fetch(req) {
const body = await req.text()
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: body, // menjadi body dari tag <channel>
// setiap kunci menjadi atribut tag, misalnya <channel path="/" method="POST">
meta: { path: new URL(req.url).pathname, method: req.method },
},
})
return new Response('ok')
},
})
File melakukan tiga hal secara berurutan:
- Server configuration: membuat server MCP dengan
claude/channeldalam capabilities, yang memberi tahu Claude Code ini adalah channel. Stringinstructionsmasuk ke system prompt Claude: beri tahu Claude event apa yang diharapkan, apakah akan membalas, dan bagaimana cara merutekan balasan jika harus. - Stdio connection: terhubung ke Claude Code melalui stdin/stdout. Ini standar untuk server MCP apa pun: Claude Code menjalankannya sebagai subprocess.
- HTTP listener: memulai server web lokal pada port 8788. Setiap body POST diteruskan ke Claude sebagai channel event melalui
mcp.notification().contentmenjadi body event, dan setiap entrymetamenjadi atribut pada tag<channel>. Pendengar memerlukan akses ke instancemcp, jadi berjalan dalam proses yang sama. Anda dapat membaginya menjadi modul terpisah untuk proyek yang lebih besar.
Daftarkan server Anda dengan Claude Code
Tambahkan server ke konfigurasi MCP Anda sehingga Claude Code tahu cara memulainya. Untuk .mcp.json tingkat proyek di direktori yang sama, gunakan jalur relatif. Untuk konfigurasi tingkat pengguna di ~/.claude.json, gunakan jalur absolut lengkap sehingga server dapat ditemukan dari proyek apa pun:
{
"mcpServers": {
"webhook": { "command": "bun", "args": ["./webhook.ts"] }
}
}
Claude Code membaca konfigurasi MCP Anda saat startup dan menjalankan setiap server sebagai subprocess.
Uji
Selama research preview, custom channels tidak ada di allowlist, jadi mulai Claude Code dengan flag pengembangan:
claude --dangerously-load-development-channels server:webhook
Kali pertama Anda memulai sesi dalam proyek ini, Claude Code meminta persetujuan sebelum menggunakan server baru dari .mcp.json. Dialog melaporkan "New MCP server found in this project: webhook". Pilih Use this MCP server untuk melanjutkan.
Ketika Claude Code dimulai, membaca konfigurasi MCP Anda, menjalankan webhook.ts Anda sebagai subprocess, dan pendengar HTTP dimulai secara otomatis pada port yang Anda konfigurasi (8788 dalam contoh ini). Anda tidak perlu menjalankan server sendiri.
Pemberitahuan redup di bawah banner startup mengonfirmasi channel terdaftar: Channels (experimental) messages from server:webhook inject directly in this session ยท restart without --dangerously-load-development-channels to stop.
Jika Anda melihat "blocked by org policy," admin organisasi Anda perlu mengaktifkan channels terlebih dahulu.
Di terminal terpisah, simulasikan webhook dengan mengirim HTTP POST dengan pesan ke server Anda. Contoh ini mengirim alert kegagalan CI ke port 8788 (atau port apa pun yang Anda konfigurasi):
curl -X POST localhost:8788 -d "build failed on main: https://ci.example.com/run/1234"
Payload tiba di sesi Claude Code Anda sebagai tag <channel>:
<channel source="webhook" path="/" method="POST">build failed on main: https://ci.example.com/run/1234</channel>
Di terminal Claude Code Anda, Anda akan melihat Claude menerima pesan dan mulai merespons: membaca file, menjalankan perintah, atau apa pun yang diminta pesan. Ini adalah channel satu arah, jadi Claude bertindak dalam sesi Anda tetapi tidak mengirim apa pun kembali melalui webhook. Untuk menambahkan balasan, lihat Expose a reply tool.
Jika event tidak tiba, diagnosis tergantung pada apa yang dikembalikan curl:
curlberhasil tetapi tidak ada yang mencapai Claude: jalankan/mcpdalam sesi Anda untuk memeriksa status server. "Failed to connect" biasanya berarti kesalahan dependensi atau impor dalam file server Anda; periksa debug log di~/.claude/debug/<session-id>.txtuntuk jejak stderr.curlgagal dengan "connection refused": port tidak terikat lagi atau proses basi dari run sebelumnya memegangnya.lsof -i :<port>menunjukkan apa yang mendengarkan;killproses basi sebelum memulai ulang sesi Anda.
Server fakechat memperluas pola ini dengan UI web, lampiran file, dan tool balasan untuk chat dua arah.
Uji selama research preview
Selama research preview, setiap channel harus ada di approved allowlist untuk mendaftar. Flag pengembangan melewati allowlist untuk entri spesifik setelah prompt konfirmasi. Contoh ini menunjukkan kedua tipe entri:
# Menguji plugin yang sedang Anda kembangkan
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace
# Menguji server .mcp.json telanjang (belum ada wrapper plugin)
claude --dangerously-load-development-channels server:webhook
Bypass per-entri. Menggabungkan flag ini dengan --channels tidak memperluas bypass ke entri --channels. Selama research preview, approved allowlist dikurasi oleh Anthropic, jadi channel Anda tetap pada flag pengembangan saat Anda membangun dan menguji.
Flag ini melewati allowlist saja. Kebijakan organisasi channelsEnabled masih berlaku. Jangan gunakan untuk menjalankan channels dari sumber yang tidak terpercaya.
Server options
Sebuah channel menetapkan opsi ini dalam konstruktor Server. Field instructions dan capabilities.tools adalah MCP standar; capabilities.experimental['claude/channel'] dan capabilities.experimental['claude/channel/permission'] adalah penambahan spesifik channel:
| Field | Type | Description |
|---|---|---|
capabilities.experimental['claude/channel'] |
object |
Diperlukan. Selalu {}. Kehadiran mendaftarkan pendengar notifikasi. |
capabilities.experimental['claude/channel/permission'] |
object |
Opsional. Selalu {}. Mendeklarasikan bahwa channel ini dapat menerima permission relay requests. Ketika dideklarasikan, Claude Code meneruskan permission prompts ke channel Anda sehingga Anda dapat menyetujui atau menolak mereka dari jarak jauh. Lihat Relay permission prompts. |
capabilities.tools |
object |
Dua arah saja. Selalu {}. Kemampuan tool MCP standar. Lihat Expose a reply tool. |
instructions |
string |
Direkomendasikan. Ditambahkan ke system prompt Claude. Beri tahu Claude event apa yang diharapkan, apa arti atribut tag <channel>, apakah akan membalas, dan jika demikian tool mana yang digunakan dan atribut mana yang diteruskan kembali (seperti chat_id). |
Untuk membuat channel satu arah, hilangkan capabilities.tools. Contoh ini menunjukkan setup dua arah dengan kemampuan channel, tools, dan instructions yang ditetapkan:
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
const mcp = new Server(
{ name: 'your-channel', version: '0.0.1' },
{
capabilities: {
experimental: { 'claude/channel': {} }, // mendaftarkan pendengar channel
tools: {}, // hilangkan untuk channels satu arah
},
// ditambahkan ke system prompt Claude sehingga tahu cara menangani event Anda
instructions: 'Messages tiba sebagai <channel source="your-channel" ...>. Balas dengan tool balasan.',
},
)
Untuk mendorong event, panggil mcp.notification() dengan method notifications/claude/channel. Params ada di bagian berikutnya.
Notification format
Server Anda memancarkan notifications/claude/channel dengan dua params:
| Field | Type | Description |
|---|---|---|
content |
string |
Body event. Dikirimkan sebagai body dari tag <channel>. |
meta |
Record<string, string> |
Opsional. Setiap entry menjadi atribut pada tag <channel> untuk konteks routing seperti chat ID, nama pengirim, atau severity alert. Kunci harus identifier: huruf, digit, dan underscore saja. Kunci yang berisi hyphen atau karakter lain secara diam-diam dijatuhkan. |
Server Anda mendorong event dengan memanggil mcp.notification() pada instance Server. Contoh ini mendorong alert kegagalan CI dengan dua kunci meta:
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' },
},
})
Event tiba dalam konteks Claude dibungkus dalam tag <channel>. Atribut source ditetapkan secara otomatis dari nama server yang dikonfigurasi:
<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>
Notifikasi tidak diakui. await pada mcp.notification() diselesaikan ketika pesan ditulis ke transport, bukan ketika Claude telah memprosesnya. Jika sesi belum memuat server Anda sebagai channel, atau kebijakan organisasi membloknya, event dijatuhkan diam-diam tanpa error dikembalikan ke server Anda.
Jika Anda memerlukan konfirmasi pengiriman, lacak state event dalam server Anda dan ekspos reply tool yang dapat dipanggil Claude untuk melaporkan status kembali.
Event antri ke dalam sesi dan diproses secara berurutan. Jika beberapa notifikasi tiba saat Claude sibuk, mereka dikirimkan bersama pada giliran berikutnya dan Claude menangani mereka sebagai grup. Untuk memproses aliran event independen secara bersamaan, jalankan sesi terpisah.
Expose a reply tool
Jika channel Anda dua arah, seperti chat bridge daripada alert forwarder, ekspos tool MCP standar yang dapat dipanggil Claude untuk mengirim pesan kembali. Tidak ada yang spesifik channel tentang registrasi tool. Tool balasan memiliki tiga komponen:
- Entry
tools: {}dalam capabilities konstruktorServerAnda sehingga Claude Code menemukan tool - Tool handlers yang mendefinisikan schema tool dan mengimplementasikan logika pengiriman
- String
instructionsdalam konstruktorServerAnda yang memberi tahu Claude kapan dan bagaimana memanggil tool
Untuk menambahkan ini ke penerima webhook di atas:
Aktifkan penemuan tool
Dalam konstruktor Server Anda di webhook.ts, tambahkan tools: {} ke capabilities sehingga Claude Code tahu server Anda menawarkan tools:
capabilities: {
experimental: { 'claude/channel': {} },
tools: {}, // mengaktifkan penemuan tool
},
Daftarkan tool balasan
Tambahkan berikut ke webhook.ts. import masuk di bagian atas file dengan import lainnya; dua handlers masuk antara konstruktor Server dan mcp.connect(). Ini mendaftarkan tool reply yang dapat dipanggil Claude dengan chat_id dan text:
// Tambahkan import ini di bagian atas webhook.ts
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
// Claude menanyakan ini saat startup untuk menemukan tool apa yang ditawarkan server Anda
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Kirim pesan kembali melalui channel ini',
// inputSchema memberi tahu Claude argumen apa yang harus diteruskan
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Percakapan untuk membalas' },
text: { type: 'string', description: 'Pesan untuk dikirim' },
},
required: ['chat_id', 'text'],
},
}],
}))
// Claude memanggil ini ketika ingin menjalankan 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() adalah outbound Anda: POST ke platform chat Anda, atau untuk pengujian lokal
// broadcast SSE yang ditunjukkan dalam contoh lengkap di bawah.
send(`Reply to ${chat_id}: ${text}`)
return { content: [{ type: 'text', text: 'sent' }] }
}
throw new Error(`unknown tool: ${req.params.name}`)
})
Perbarui instructions
Perbarui string instructions dalam konstruktor Server Anda sehingga Claude tahu merutekan balasan kembali melalui tool. Contoh ini memberi tahu Claude untuk melewatkan chat_id dari tag inbound:
instructions: 'Messages tiba sebagai <channel source="webhook" chat_id="...">. Balas dengan tool balasan, melewatkan chat_id dari tag.'
Berikut adalah webhook.ts lengkap dengan dukungan dua arah. Balasan outbound mengalir melalui GET /events menggunakan Server-Sent Events (SSE), jadi curl -N localhost:8788/events dapat menontonnya secara langsung; chat inbound tiba di POST /:
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
// --- Outbound: tulis ke pendengar curl -N apa pun di /events ---
// Bridge nyata akan POST ke platform chat Anda sebagai gantinya.
const listeners = new Set<(chunk: string) => void>()
function send(text: string) {
const chunk = text.split('\n').map(l => `data: ${l}\n`).join('') + '\n'
for (const emit of listeners) emit(chunk)
}
const mcp = new Server(
{ name: 'webhook', version: '0.0.1' },
{
capabilities: {
experimental: { 'claude/channel': {} },
tools: {},
},
instructions: 'Messages tiba sebagai <channel source="webhook" chat_id="...">. Balas dengan tool balasan, melewatkan chat_id dari tag.',
},
)
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Kirim pesan kembali melalui channel ini',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Percakapan untuk membalas' },
text: { type: 'string', description: 'Pesan untuk dikirim' },
},
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(`Reply to ${chat_id}: ${text}`)
return { content: [{ type: 'text', text: 'sent' }] }
}
throw new Error(`unknown tool: ${req.params.name}`)
})
await mcp.connect(new StdioServerTransport())
let nextId = 1
Bun.serve({
port: 8788,
hostname: '127.0.0.1',
idleTimeout: 0, // jangan tutup aliran SSE idle
async fetch(req) {
const url = new URL(req.url)
// GET /events: aliran SSE sehingga curl -N dapat menonton balasan Claude secara langsung
if (req.method === 'GET' && url.pathname === '/events') {
const stream = new ReadableStream({
start(ctrl) {
ctrl.enqueue(': connected\n\n') // sehingga curl menunjukkan sesuatu segera
const emit = (chunk: string) => ctrl.enqueue(chunk)
listeners.add(emit)
req.signal.addEventListener('abort', () => listeners.delete(emit))
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
})
}
// POST: teruskan ke Claude sebagai channel event
const body = await req.text()
const chat_id = String(nextId++)
await mcp.notification({
method: 'notifications/claude/channel',
params: {
content: body,
meta: { chat_id, path: url.pathname, method: req.method },
},
})
return new Response('ok')
},
})
Server fakechat menunjukkan contoh yang lebih lengkap dengan lampiran file dan pengeditan pesan.
Gate inbound messages
Channel tanpa gate adalah vektor prompt injection. Siapa pun yang dapat menjangkau endpoint Anda dapat menempatkan teks di depan Claude. Channel yang mendengarkan platform chat atau endpoint publik memerlukan pemeriksaan pengirim nyata sebelum memancarkan apa pun.
Periksa pengirim terhadap allowlist sebelum memanggil mcp.notification(). Contoh ini menjatuhkan pesan apa pun dari pengirim yang tidak ada dalam set:
const allowed = new Set(loadAllowlist()) // dari access.json Anda atau setara
// di dalam handler pesan Anda, sebelum memancarkan:
if (!allowed.has(message.from.id)) { // pengirim, bukan ruangan
return // jatuhkan diam-diam
}
await mcp.notification({ ... })
Gate pada identitas pengirim, bukan identitas chat atau ruangan: message.from.id dalam contoh, bukan message.chat.id. Dalam chat grup, ini berbeda, dan gating pada ruangan akan membiarkan siapa pun dalam grup yang diizinkan menyuntikkan pesan ke dalam sesi.
Channel Telegram dan Discord gate pada allowlist pengirim dengan cara yang sama. Mereka bootstrap list dengan pairing: pengguna DM bot, bot membalas dengan kode pairing, pengguna menyetujuinya dalam sesi Claude Code mereka, dan ID platform mereka ditambahkan. Lihat implementasi mana pun untuk alur pairing lengkap. Channel iMessage mengambil pendekatan berbeda: mendeteksi alamat pengguna sendiri dari database Messages saat startup dan membiarkan mereka melewati secara otomatis, dengan pengirim lain ditambahkan oleh handle.
Relay permission prompts
Permission relay memerlukan Claude Code v2.1.81 atau lebih baru. Versi sebelumnya mengabaikan kemampuan claude/channel/permission.
Ketika Claude memanggil tool yang memerlukan persetujuan, dialog terminal lokal terbuka dan sesi menunggu. Channel dua arah dapat memilih untuk menerima prompt yang sama secara paralel dan meneruskannya kepada Anda di perangkat lain. Keduanya tetap aktif: Anda dapat menjawab di terminal atau di ponsel, dan Claude Code menerapkan jawaban mana pun yang tiba terlebih dahulu dan menutup yang lain.
Relay mencakup persetujuan penggunaan tool seperti Bash, Write, dan Edit. Dialog kepercayaan proyek dan persetujuan server MCP tidak relay; hanya muncul di terminal lokal.
Bagaimana relay bekerja
Ketika permission prompt terbuka, loop relay memiliki empat langkah:
- Claude Code menghasilkan ID permintaan pendek dan memberi tahu server Anda
- Server Anda meneruskan prompt dan ID ke aplikasi chat Anda
- Pengguna jarak jauh membalas dengan ya atau tidak dan ID itu
- Handler inbound Anda menguraikan balasan menjadi verdict, dan Claude Code menerapkannya hanya jika ID cocok dengan permintaan terbuka
Dialog terminal lokal tetap terbuka melalui semua ini. Jika seseorang di terminal menjawab sebelum verdict jarak jauh tiba, jawaban itu diterapkan sebagai gantinya dan permintaan jarak jauh yang tertunda dijatuhkan.
Permission request fields
Notifikasi outbound dari Claude Code adalah notifications/claude/channel/permission_request. Seperti channel notification, transport adalah MCP standar tetapi method dan schema adalah ekstensi Claude Code. Objek params memiliki empat field string yang server Anda format ke dalam prompt outgoing:
| Field | Description |
|---|---|
request_id |
Lima huruf kecil diambil dari a-z tanpa l, jadi tidak pernah dibaca sebagai 1 atau I ketika diketik di ponsel. Sertakan dalam prompt outgoing Anda sehingga dapat diulang dalam balasan. Claude Code hanya menerima verdict yang membawa ID yang dikeluarkannya. Dialog terminal lokal tidak menampilkan ID ini, jadi handler outbound Anda adalah satu-satunya cara untuk mempelajarinya. |
tool_name |
Nama tool yang ingin dipanggil Claude, misalnya Bash atau Write. |
description |
Ringkasan yang dapat dibaca manusia tentang apa yang dilakukan panggilan tool spesifik ini, teks yang sama yang ditampilkan dialog terminal lokal. Untuk panggilan Bash ini adalah deskripsi Claude tentang perintah, atau perintah itu sendiri jika tidak ada yang diberikan. |
input_preview |
Argumen tool sebagai string JSON, dipotong menjadi 200 karakter. Untuk Bash ini adalah perintah; untuk Write ini adalah jalur file dan awalan konten. Hilangkan dari prompt Anda jika Anda hanya memiliki ruang untuk pesan satu baris. Server Anda memutuskan apa yang akan ditampilkan. |
Verdict yang dikirim server Anda kembali adalah notifications/claude/channel/permission dengan dua field: request_id mengulangi ID di atas, dan behavior diatur ke 'allow' atau 'deny'. Allow membiarkan panggilan tool melanjutkan; deny menolaknya, sama seperti menjawab No dalam dialog lokal. Tidak ada verdict yang mempengaruhi panggilan masa depan.
Tambahkan relay ke chat bridge
Menambahkan permission relay ke channel dua arah memerlukan tiga komponen:
- Entry
claude/channel/permission: {}di bawah capabilitiesexperimentaldalam konstruktorServerAnda sehingga Claude Code tahu untuk meneruskan prompts - Notification handler untuk
notifications/claude/channel/permission_requestyang memformat prompt dan mengirimnya melalui API platform Anda - Pemeriksaan dalam handler pesan inbound Anda yang mengenali
yes <id>atauno <id>dan memancarkan notifikasinotifications/claude/channel/permissionverdict sebagai gantinya dari meneruskan teks ke Claude
Hanya deklarasikan kemampuan jika channel Anda mengautentikasi pengirim, karena siapa pun yang dapat membalas melalui channel Anda dapat menyetujui atau menolak penggunaan tool dalam sesi Anda.
Untuk menambahkan ini ke chat bridge dua arah seperti yang dirakit dalam Expose a reply tool:
Deklarasikan kemampuan permission
Dalam konstruktor Server Anda, tambahkan claude/channel/permission: {} bersama claude/channel di bawah experimental:
capabilities: {
experimental: {
'claude/channel': {},
'claude/channel/permission': {}, // opt in ke permission relay
},
tools: {},
},
Tangani permintaan masuk
Daftarkan notification handler antara konstruktor Server Anda dan mcp.connect(). Claude Code memanggilnya dengan empat request fields ketika dialog permission terbuka. Handler Anda memformat prompt untuk platform Anda dan menyertakan instruksi untuk membalas dengan ID:
import { z } from 'zod'
// setNotificationHandler merutekan oleh z.literal pada field method,
// jadi schema ini adalah validator dan kunci dispatch
const PermissionRequestSchema = z.object({
method: z.literal('notifications/claude/channel/permission_request'),
params: z.object({
request_id: z.string(), // lima huruf kecil, sertakan verbatim dalam prompt Anda
tool_name: z.string(), // misalnya "Bash", "Write"
description: z.string(), // ringkasan yang dapat dibaca manusia tentang panggilan ini
input_preview: z.string(), // argumen tool sebagai JSON, dipotong menjadi ~200 karakter
}),
})
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
// send() adalah outbound Anda: POST ke platform chat Anda, atau untuk pengujian lokal
// broadcast SSE yang ditunjukkan dalam contoh lengkap di bawah.
send(
`Claude ingin menjalankan ${params.tool_name}: ${params.description}\n\n` +
// ID dalam instruksi adalah apa yang diuraikan handler inbound Anda dalam Langkah 3
`Balas "yes ${params.request_id}" atau "no ${params.request_id}"`,
)
})
Intersep verdict dalam handler inbound Anda
Handler inbound Anda adalah loop atau callback yang menerima pesan dari platform Anda: tempat yang sama di mana Anda gate pada pengirim dan memancarkan notifications/claude/channel untuk meneruskan chat ke Claude. Tambahkan pemeriksaan sebelum panggilan chat-forwarding yang mengenali format verdict dan memancarkan notifikasi permission sebagai gantinya.
Regex cocok dengan format ID yang dihasilkan Claude Code: lima huruf, tidak pernah l. Flag /i mentoleransi autocorrect ponsel memanfaatkan balasan; hurufkan ID yang ditangkap sebelum mengirimnya kembali.
// cocok dengan "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] adalah alfabet ID yang digunakan Claude Code (huruf kecil, lewati 'l')
// /i mentoleransi autocorrect ponsel; hurufkan capture sebelum mengirim
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 pada pengirim terlebih dahulu
const m = PERMISSION_REPLY_RE.exec(message.text)
if (m) {
// m[1] adalah kata verdict, m[2] adalah ID permintaan
// mancarkan notifikasi verdict kembali ke Claude Code daripada chat
await mcp.notification({
method: 'notifications/claude/channel/permission',
params: {
request_id: m[2].toLowerCase(), // normalisasi jika ada autocorrect caps
behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
},
})
return // ditangani sebagai verdict, jangan juga teruskan sebagai chat
}
// tidak cocok format verdict: jatuh melalui ke jalur chat normal
await mcp.notification({
method: 'notifications/claude/channel',
params: { content: message.text, meta: { chat_id: String(message.chat.id) } },
})
}
Claude Code juga menjaga dialog terminal lokal tetap terbuka, jadi Anda dapat menjawab di tempat mana pun, dan jawaban pertama yang tiba diterapkan. Balasan jarak jauh yang tidak cocok dengan format yang diharapkan dengan tepat gagal dalam salah satu dari dua cara, dan dalam kedua kasus dialog tetap terbuka:
- Format berbeda: regex handler inbound Anda gagal cocok, jadi teks seperti
approve itatauyestanpa ID jatuh melalui sebagai pesan normal ke Claude. - Format benar, ID salah: server Anda memancarkan verdict, tetapi Claude Code tidak menemukan permintaan terbuka dengan ID itu dan menjatuhkannya diam-diam.
Contoh lengkap
webhook.ts yang dirakit di bawah menggabungkan ketiga ekstensi dari halaman ini: tool balasan, sender gating, dan permission relay. Jika Anda memulai di sini, Anda juga akan memerlukan project setup dan entry .mcp.json dari panduan awal.
Untuk membuat kedua arah dapat diuji dari curl, pendengar HTTP melayani dua jalur:
GET /events: memegang aliran SSE terbuka dan mendorong setiap pesan outbound sebagai barisdata:, jadicurl -Ndapat menonton balasan Claude dan permission prompts tiba secara langsung.POST /: sisi inbound, handler yang sama seperti sebelumnya, sekarang dengan pemeriksaan format verdict disisipkan sebelum cabang chat-forward.
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'
// --- Outbound: tulis ke pendengar curl -N apa pun di /events ---
// Bridge nyata akan POST ke platform chat Anda sebagai gantinya.
const listeners = new Set<(chunk: string) => void>()
function send(text: string) {
const chunk = text.split('\n').map(l => `data: ${l}\n`).join('') + '\n'
for (const emit of listeners) emit(chunk)
}
// Allowlist pengirim. Untuk panduan lokal kami mempercayai nilai header X-Sender tunggal
// "dev"; bridge nyata akan memeriksa ID pengguna platform.
const allowed = new Set(['dev'])
const mcp = new Server(
{ name: 'webhook', version: '0.0.1' },
{
capabilities: {
experimental: {
'claude/channel': {},
'claude/channel/permission': {}, // opt in ke permission relay
},
tools: {},
},
instructions:
'Messages tiba sebagai <channel source="webhook" chat_id="...">. ' +
'Balas dengan tool balasan, melewatkan chat_id dari tag.',
},
)
// --- reply tool: Claude memanggil ini untuk mengirim pesan kembali ---
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Kirim pesan kembali melalui channel ini',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Percakapan untuk membalas' },
text: { type: 'string', description: 'Pesan untuk dikirim' },
},
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(`Reply to ${chat_id}: ${text}`)
return { content: [{ type: 'text', text: 'sent' }] }
}
throw new Error(`unknown tool: ${req.params.name}`)
})
// --- permission relay: Claude Code (bukan Claude) memanggil ini ketika dialog terbuka
const PermissionRequestSchema = z.object({
method: z.literal('notifications/claude/channel/permission_request'),
params: z.object({
request_id: z.string(),
tool_name: z.string(),
description: z.string(),
input_preview: z.string(),
}),
})
mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
send(
`Claude ingin menjalankan ${params.tool_name}: ${params.description}\n\n` +
`Balas "yes ${params.request_id}" atau "no ${params.request_id}"`,
)
})
await mcp.connect(new StdioServerTransport())
// --- HTTP pada :8788: GET /events mengalirkan outbound, POST merutekan inbound ---
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
let nextId = 1
Bun.serve({
port: 8788,
hostname: '127.0.0.1',
idleTimeout: 0, // jangan tutup aliran SSE idle
async fetch(req) {
const url = new URL(req.url)
// GET /events: aliran SSE sehingga curl -N dapat menonton balasan dan prompts secara langsung
if (req.method === 'GET' && url.pathname === '/events') {
const stream = new ReadableStream({
start(ctrl) {
ctrl.enqueue(': connected\n\n') // sehingga curl menunjukkan sesuatu segera
const emit = (chunk: string) => ctrl.enqueue(chunk)
listeners.add(emit)
req.signal.addEventListener('abort', () => listeners.delete(emit))
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
})
}
// semuanya lainnya adalah inbound: gate pada pengirim terlebih dahulu
const body = await req.text()
const sender = req.headers.get('X-Sender') ?? ''
if (!allowed.has(sender)) return new Response('forbidden', { status: 403 })
// periksa format verdict sebelum memperlakukan sebagai chat
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',
},
})
return new Response('verdict recorded')
}
// chat normal: teruskan ke Claude sebagai channel event
const chat_id = String(nextId++)
await mcp.notification({
method: 'notifications/claude/channel',
params: { content: body, meta: { chat_id, path: url.pathname } },
})
return new Response('ok')
},
})
Uji jalur verdict dalam tiga terminal. Yang pertama adalah sesi Claude Code Anda, dimulai dengan development flag sehingga menjalankan webhook.ts:
claude --dangerously-load-development-channels server:webhook
Di yang kedua, alirkan sisi outbound sehingga Anda dapat melihat balasan Claude dan permission prompts apa pun saat mereka menyala:
curl -N localhost:8788/events
Di yang ketiga, kirim pesan yang akan membuat Claude mencoba menjalankan perintah:
curl -d "list the files in this directory" -H "X-Sender: dev" localhost:8788
Listing file adalah read-only, jadi Claude menjalankannya tanpa persetujuan. Dialog permission terbuka ketika Claude memanggil tool reply untuk mengirim jawabannya kembali. Dialog lokal terbuka di terminal Claude Code Anda, dan sebentar kemudian prompt untuk mcp__webhook__reply muncul dalam aliran /events, termasuk ID lima huruf. Setujui dari sisi jarak jauh:
curl -d "yes <id>" -H "X-Sender: dev" localhost:8788
Dialog lokal menutup, tool reply berjalan, dan balasan Claude mendarat dalam aliran.
Tiga bagian spesifik channel dalam file ini:
- Capabilities dalam konstruktor
Server:claude/channelmendaftarkan pendengar notifikasi,claude/channel/permissionopt in ke permission relay,toolsmembiarkan Claude menemukan tool balasan. - Outbound paths: handler tool
replyadalah apa yang dipanggil Claude untuk respons percakapan; handler notifikasiPermissionRequestSchemaadalah apa yang dipanggil Claude Code ketika dialog permission terbuka. Keduanya memanggilsend()untuk broadcast melalui/events, tetapi dipicu oleh bagian berbeda dari sistem. - HTTP handler:
GET /eventsmemegang aliran SSE terbuka sehingga curl dapat menonton outbound secara langsung;POSTadalah inbound, gated pada headerX-Sender. Bodyyes <id>atauno <id>masuk ke Claude Code sebagai notifikasi verdict dan tidak pernah mencapai Claude; apa pun lainnya diteruskan ke Claude sebagai channel event.
Paket sebagai plugin
Untuk membuat channel Anda dapat diinstal dan dibagikan, bungkus dalam plugin dan publikasikan ke marketplace. Pengguna menginstalnya dengan /plugin install, kemudian mengaktifkannya per sesi dengan --channels plugin:<name>@<marketplace>.
Channel yang dipublikasikan ke marketplace Anda sendiri masih memerlukan --dangerously-load-development-channels untuk berjalan, karena tidak ada di approved allowlist. Allowlist default adalah plugin channel di claude-plugins-official, yang dikurasi Anthropic sesuai kebijaksanaannya. Formulir pengajuan in-app menambahkan plugin ke community marketplace, yang tidak ada di allowlist channel.
Jika Anda bekerja dengan kontak partner Anthropic, hubungi mereka untuk mengoordinasikan listing official-marketplace. Pada rencana Team dan Enterprise, admin dapat sebagai gantinya menyertakan plugin Anda dalam daftar allowedChannelPlugins organisasi, yang menggantikan allowlist Anthropic default.
Lihat juga
- Channels untuk menginstal dan menggunakan Telegram, Discord, iMessage, atau demo fakechat, dan untuk mengaktifkan channels untuk Team atau Enterprise org
- Working channel implementations untuk kode server lengkap dengan alur pairing, tools balasan, dan lampiran file
- MCP untuk protokol dasar yang diimplementasikan server channel
- Plugins untuk mengemas channel Anda sehingga pengguna dapat menginstalnya dengan
/plugin install