תיעוד 121
מדריך עזר לערוצים (Channels reference)
בנה שרת MCP שדוחף webhooks, התראות והודעות צ'אט לתוך הפעלה (session) של Claude Code. מדריך עזר לחוזה הערוץ: הצהרת יכולות (capabilities), אירועי הודעה (notification events), כלי מענה (reply tools), סינון שולחים (sender gating) וממסר הרשאות (permission relay).
הערה: ערוצים נמצאים ב-research preview. ארגוני Team ו-Enterprise חייבים להפעיל אותם במפורש.
ערוץ (channel) הוא שרת MCP שדוחף אירועים לתוך הפעלה של Claude Code, כך ש-Claude יוכל להגיב לדברים שמתרחשים מחוץ למסוף.
באפשרותך לבנות ערוץ חד-כיווני או דו-כיווני. ערוצים חד-כיווניים מעבירים התראות, webhooks או אירועי ניטור כדי ש-Claude יפעל לפיהם. ערוצים דו-כיווניים, כמו גשרי צ'אט, גם חושפים כלי מענה כדי ש-Claude יוכל לשלוח הודעות בחזרה. ערוץ עם נתיב שולח מהימן יכול גם לבחור להעביר בקשות הרשאה בממסר, כך שתוכל לאשר או לדחות שימוש בכלים מרחוק.
דף זה מכסה:
- סקירה כללית: כיצד ערוצים עובדים
- מה שאתה צריך: דרישות ושלבים כלליים
- דוגמה: בניית מקבל webhook: הדרכה מעשית מינימלית לערוץ חד-כיווני
- אפשרויות שרת: שדות הבנאי (constructor)
- פורמט הודעות: מטען האירוע (payload) והתנהגות המסירה
- חשיפת כלי מענה: מתן אפשרות ל-Claude לשלוח הודעות בחזרה
- סינון הודעות נכנסות: בדיקות שולח למניעת הזרקת הנחיות (prompt injection)
- העברת בקשות הרשאה בממסר: העברת בקשות אישור כלים לערוצים מרוחקים
כדי להשתמש בערוץ קיים במקום לבנות אחד, ראה ערוצים. Telegram, Discord, iMessage ו-fakechat כלולים ב-research preview.
#סקירה כללית
ערוץ הוא שרת MCP שרץ על אותה מכונה שבה רץ Claude Code. Claude Code מפעיל אותו כתהליך משנה (subprocess) ומתקשר איתו דרך stdio. שרת הערוץ שלך מהווה את הגשר בין מערכות חיצוניות לבין ההפעלה של Claude Code:
- פלטפורמות צ'אט (
Telegram,Discord): התוסף שלך רץ מקומית ודוגם (polls) את ה-API של הפלטפורמה להודעות חדשות. כאשר מישהו שולח הודעה ישירה (DM) לבוט שלך, התוסף מקבל את ההודעה ומעביר אותה אלClaude. אין כתובת URL לחשוף. - Webhooks (
CI, ניטור): השרת שלך מאזין ביציאת HTTP מקומית. מערכות חיצוניות שולחות בקשותPOSTליציאה זו, והשרת שלך דוחף את המטען (payload) אלClaude.
#מה שאתה צריך
הדרישה הקשיחה היחידה היא החבילה @modelcontextprotocol/sdk וסביבת ריצה תואמת Node.js. Bun, Node ו-Deno כולן עובדות. התוספים המובנים מראש ב-research preview משתמשים ב-Bun, אבל הערוץ שלך לא חייב לעשות זאת.
השרת שלך צריך:
- להצהיר על יכולת
claude/channelכדי ש-Claude Codeירשום מאזין הודעות (notification listener) - לשדר אירועי
notifications/claude/channelכאשר משהו קורה - להתחבר דרך תעבורת stdio
הסעיפים אפשרויות שרת ו-פורמט הודעות מכסים כל אחד מאלה בפירוט. ראה דוגמה: בניית מקבל webhook להדרכה מלאה.
במהלך ה-research preview, ערוצים מותאמים אישית אינם נמצאים ב-רשימת המורשים המאושרת. השתמש ב---dangerously-load-development-channels כדי לבדוק מקומית. ראה בדיקה במהלך ה-research preview לפרטים.
#דוגמה: בניית מקבל webhook
הדרכה מעשית זו בונה שרת בקובץ יחיד שמאזין לבקשות HTTP ומעביר אותן לתוך הפעלת ה-Claude Code שלך. בסיום, כל מה שיכול לשלוח בקשת HTTP POST, כמו צינור CI, התראת ניטור או פקודת curl, יוכל לדחוף אירועים אל Claude.
דוגמה זו משתמשת ב-Bun כסביבת ריצה בזכות שרת ה-HTTP המובנה שלו והתמיכה ב-TypeScript. באפשרותך להשתמש ב-Node או ב-Deno במקום; הדרישה היחידה היא MCP SDK.
יצירת הפרויקט: דוגמאות ממסר ההרשאות בהמשך דף זה מייבאות את
zodישירות, לכן היא מותקנת לצד ה-SDK של MCP. צור ספרייה חדשה והתקן את שתיהן:mkdir webhook-channel && cd webhook-channel bun add @modelcontextprotocol/sdk zodכתיבת שרת הערוץ: צור קובץ בשם
webhook.ts. זהו שרת הערוץ המלא שלך: הוא מתחבר ל-Claude Codeדרךstdio, ומאזין לבקשות HTTP POST ביציאה 8788. כאשר בקשה מגיעה, הוא דוחף את גוף הבקשה אל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') }, })הקובץ מבצע שלושה דברים לפי הסדר:
- הגדרת השרת: יוצר את שרת ה-
MCPעםclaude/channelבתוך היכולות שלו, וזה מה שאומר ל-Claude Codeשמדובר בערוץ.Claude Codeמעביר את מחרוזת ה-instructionsאלClaudeכהקשר כאשר השרת מתחבר: אמור ל-Claudeלאילו אירועים לצפות, האם לענות, וכיצד לנתב תשובות אם נדרש. - חיבור Stdio: מתחבר אל
Claude Codeדרךstdin/stdout. זהו תקן רגיל עבור כל שרת MCP. - מאזין HTTP: מפעיל שרת רשת מקומי ביציאה 8788. כל גוף של בקשת
POSTמועבר אלClaudeכאירוע ערוץ באמצעותmcp.notification(). ה-contentהופך לגוף האירוע, וכל ערך ב-metaהופך לתכונה (attribute) בתגית<channel>. המאזין זקוק לגישה למופע ה-mcp, ולכן הוא רץ באותו תהליך. ניתן לפצל אותו למודולים נפרדים בפרויקט גדול יותר.
- הגדרת השרת: יוצר את שרת ה-
רישום השרת שלך מול Claude Code: הוסף את השרת להגדרות ה-
MCPשלך כדי ש-Claude Codeיידע כיצד להפעיל אותו. עבור.mcp.jsonברמת הפרויקט באותה ספרייה, השתמש בנתיב יחסי. עבור הגדרות ברמת המשתמש ב-~/.claude.json, השתמש בנתיב מוחלט מלא כדי שניתן יהיה למצוא את השרת מכל פרויקט:{ "mcpServers": { "webhook": { "command": "bun", "args": ["./webhook.ts"] } } }Claude Codeקורא את הגדרות ה-MCPשלך בעת ההפעלה ומריץ כל שרת כתהליך משנה.בדיקה: במהלך ה-research preview, ערוצים מותאמים אישית אינם נמצאים ברשימת המורשים, לכן הפעל את
Claude Codeעם דגל הפיתוח:claude --dangerously-load-development-channels server:webhookClaude Codeמציג תחילה תיבת דו-שיח של אזהרה במסך מלא המפרטת את ערוצי הפיתוח שאתה טוען. בחר באפשרות I am using this for local development כדי להמשיך, או ב-Exit כדי לצאת.בפעם הראשונה שאתה מתחיל הפעלה בפרויקט זה,
Claude Codeמבקש גם הסכמה לפני שימוש בשרת החדש מתוך.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", מנהל הארגון שלך צריך להפעיל ערוצים תחילה.
במסוף נפרד, בצע סימולציה של webhook על ידי שליחת בקשת HTTP POST עם הודעה לשרת שלך. דוגמה זו שולחת התראת כשל ב-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, במקום התגית הגולמית. לאחר מכן תראה ש-Claudeמתחיל להגיב: קריאת קבצים, הרצת פקודות, או כל מה שההודעה דורשת. זהו ערוץ חד-כיווני, לכןClaudeפועל בהפעלה שלך אך אינו שולח דבר בחזרה דרך ה-webhook. להוספת מענה, ראה חשיפת כלי מענה.אם האירוע אינו מגיע, האבחון תלוי במה ש-
curlהחזיר:curlמצליח אך שום דבר אינו מגיע אלClaude: הרץ/mcpבהפעלה שלך כדי לבדוק את סטטוס השרת. סטטוסfailedמעיד בדרך כלל על שגיאת תלות או שגיאת ייבוא בקובץ השרת שלך; בדוק את יומן הניפוי (debug log) בנתיב~/.claude/debug/<session-id>.txtעבור מעקב ה-stderr.curlנכשל עם השגיאה "connection refused": היציאה עדיין אינה מאוגדת או שתהליך ישן מהרצה קודמת תופס אותה. הפקודהlsof -i :<port>מציגה מה מאזין; בצעkillלתהליך הישן לפני הפעלה מחדש של ההפעלה שלך.
שרת fakechat מרחיב דפוס זה עם ממשק משתמש ובי, קבצים מצורפים, וכלי מענה לצ'אט דו-כיווני.
#בדיקה במהלך ה-research preview
במהלך ה-research preview, כל ערוץ חייב להיות ב-רשימת המורשים המאושרת כדי להירשם. דגל הפיתוח עוקף את רשימת המורשים עבור רשומות ספציפיות לאחר בקשת אישור. דוגמה זו מציגה את שני סוגי הרשומות:
# בדיקת תוסף שאתה מפתח
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace
# בדיקת שרת .mcp.json ישיר (ללא מעטפת תוסף עדיין)
claude --dangerously-load-development-channels server:webhookהעקיפה היא פר-רשומה. שילוב דגל זה עם --channels אינו מרחיב את העקיפה לרשומות של --channels. במהלך ה-research preview, רשימת המורשים המאושרת נאצרת על ידי Anthropic, לכן הערוץ שלך נשאר עם דגל הפיתוח בזמן שאתה בונה ובודק.
הערה: דגל זה מדלג על רשימת המורשים בלבד. מדיניות הארגון
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, Claude Code התייחס ל-false כאל מוצהר. ראה העברת בקשות הרשאה בממסר. |
capabilities.tools | object | דו-כיווני בלבד. תמיד {}. יכולת כלים סטנדרטית של MCP. ראה חשיפת כלי מענה. |
instructions | string | מומלץ. Claude Code מעביר זאת אל Claude כהקשר כאשר השרת מתחבר. אמור ל-Claude לאילו אירועים לצפות, מה משמעות התכונות של תגית <channel>, האם לענות, ואם כן באיזה כלי להשתמש ואיזו תכונה להעביר בחזרה (כגון chat_id). |
כדי ליצור ערוץ חד-כיווני, השמט את capabilities.tools. דוגמה זו מציגה הגדרה דו-כיוונית עם יכולת הערוץ, הכלים וההנחיות מוגדרים:
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.',
},
)#פורמט הודעות
השרת שלך משדר notifications/claude/channel עם שני פרמטרים:
| שדה | טיפוס | תיאור |
|---|---|---|
content | string | גוף האירוע. נמסר כגוף התגית <channel>. |
meta | Record<string, string> | אופציונלי. כל רשומה הופכת לתכונה בתגית <channel> עבור הקשר ניתוב כגון מזהה צ'אט, שם שולח, או חומרת התראה. מפתחות חייבים להיות מזהים: אותיות, ספרות וקווים תחתונים בלבד. מפתחות המכילים מקפים או תווים אחרים מושמטים בשקט. |
השרת שלך דוחף אירועים על ידי קריאה ל-mcp.notification() על מופע ה-Server. דוגמה זו דוחפת התראת כשל ב-CI עם שני מפתחות 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' },
},
})האירוע מגיע להקשר של Claude עטוף בתגית <channel>. התכונה source מוגדרת אוטומטית מתוך השם המוגדר של השרת שלך:
<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>Claude Code אינו מאשר קבלת הודעות. ה-await על mcp.notification() מסתיים כאשר ההודעה נכתבת לתעבורה, ולא כאשר Claude עיבד אותה. אם ההפעלה לא טענה את השרת שלך כערוץ, או שמדיניות הארגון חוסמת אותו, Claude Code משמיט את האירועים בשקט ואינו מחזיר שגיאה לשרת שלך.
אם אתה זקוק לאישור מסירה, עקוב אחר מצב האירועים בשרת שלך וחשוף כלי מענה ש-Claude יוכל לקרוא לו כדי לדווח סטטוס בחזרה.
אירועים נכנסים לתור בתוך ההפעלה ומעובדים לפי הסדר. אם מספר הודעות מגיעות בזמן ש-Claude תפוס, הן נמסרות יחד בתור הבא ו-Claude מטפל בהן כקבוצה. כדי לעבד זרמי אירועים עצמאיים במקביל, הפעל הפעלות נפרדות.
#חשיפת כלי מענה
אם הערוץ שלך דו-כיווני, כמו גשר צ'אט ולא רק מעביר התראות, חשוף כלי MCP רגיל ש-Claude יוכל לקרוא לו כדי לשלוח הודעות בחזרה. שום פרט ברישום הכלי אינו ייחודי לערוץ. לכלי מענה יש שלושה מרכיבים:
- רשומת
tools: {}ביכולות של בנאי ה-Serverשלך כדי ש-Claude Codeיגלה את הכלי - פונקציות טיפול (handlers) בכלי שמגדירות את הסכמה שלו ומממשות את לוגיקת השליחה
- מחרוזת
instructionsבבנאי ה-Serverשלך שמסבירה ל-Claudeמתי וכיצד לקרוא לכלי
להוספת מרכיבים אלה ל-מקבל ה-webhook שלמעלה:
אפשור גילוי כלים: בבנאי ה-
Serverשלך ב-webhook.ts, הוסף אתtools: {}ליכולות כדי ש-Claude Codeיידע שהשרת שלך מציע כלים:capabilities: { experimental: { 'claude/channel': {} }, tools: {}, // enables tool discovery },רישום כלי המענה: הוסף את הקוד הבא ל-
webhook.ts. שורת ה-importנכנסת בראש הקובץ לצד שאר הייבואים; שני ה-handlers נכנסים בין בנאי ה-Serverלביןmcp.connect(). קוד זה רושם כליreplyש-Claudeיכול לקרוא לו עםchat_idו-text:// 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 the SSE broadcast shown in the full example below. send(`Reply to ${chat_id}: ${text}`) return { content: [{ type: 'text', text: 'sent' }] } } throw new Error(`unknown tool: ${req.params.name}`) })עדכון ההנחיות: עדכן את מחרוזת ה-
instructionsבבנאי ה-Serverשלך כדי ש-Claudeיידע לנתב תשובות בחזרה דרך הכלי. דוגמה זו מנחה אתClaudeלהעביר אתchat_idמתוך התגית הנכנסת:instructions: 'Messages arrive as <channel source="webhook" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.'
הנה הקובץ webhook.ts המלא עם תמיכה דו-כיוונית. תשובות יוצאות מוזרמות דרך GET /events באמצעות Server-Sent Events (SSE), כך ש-curl -N localhost:8788/events יכול לצפות בהן בשידור חי; צ'אט נכנס מגיע ב-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: write to any curl -N listeners on /events --------------------
// A real bridge would POST to your chat platform instead.
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 arrive as <channel source="webhook" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.',
},
)
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Send a message back over this channel',
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'],
},
}],
}))
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, // don't close idle SSE streams
async fetch(req) {
const url = new URL(req.url)
// GET /events: SSE stream so curl -N can watch Claude's replies live
if (req.method === 'GET' && url.pathname === '/events') {
const stream = new ReadableStream({
start(ctrl) {
ctrl.enqueue(': connected\n\n') // so curl shows something immediately
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: forward to Claude as a 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')
},
})שרת fakechat מציג דוגמה שלמה יותר עם קבצים מצורפים ועריכת הודעות.
#סינון הודעות נכנסות
ערוץ שאינו מסונן מהווה וקטור להזרקת הנחיות (prompt injection). כל מי שיכול להגיע לנקודת הקצה שלך יכול להציג טקסט בפני Claude. ערוץ שמאזין לפלטפורמת צ'אט או לנקודת קצה ציבורית זקוק לבדיקת שולח אמיתית לפני שהוא משדר דבר מה.
בדוק את השולח מול רשימת מורשים (allowlist) לפני קריאה ל-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. בצ'אטים קבוצתיים, אלה שונים זה מזה, וסינון לפי החדר יאפשר לכל מי שנמצא בקבוצה מורשית להזריק הודעות לתוך ההפעלה.
ערוצי Telegram ו-Discord מסננים לפי רשימת שולחים מורשים באותו אופן. הם מאתחלים את הרשימה באמצעות צימוד (pairing). ראה כל אחד מהמימושים לתהליך הצימוד המלא. ערוץ iMessage נוקט גישה שונה: הוא מזהה את הכתובות של המשתמש עצמו מתוך מסד הנתונים של הודעות בעת ההפעלה ומאפשר להן לעבור אוטומטית, בעוד ששולחים אחרים מתווספים לפי כינוי (handle).
#העברת בקשות הרשאה בממסר
כאשר Claude קורא לכלי שזקוק לאישור, תיבת הדו-שיח במסוף המקומי נפתחת וההפעלה ממתינה. ערוץ דו-כיווני יכול לבחור לקבל את אותה בקשה במקביל ולהעביר אותה אליך בממסר למכשיר אחר. שניהם נשארים פעילים: באפשרותך לענות במסוף או בטלפון שלך, ו-Claude Code מחיל את התשובה שמגיעה ראשונה וסוגר את האחרת.
ממסר מכסה אישורי שימוש בכלים כמו Bash, Write ו-Edit. תיבות דו-שיח לאמון בפרויקט (project trust) ולהסכמה לשרתי MCP אינן מועברות בממסר; אלו מופיעות רק במסוף המקומי.
Claude Code מגרסה v2.1.234 ואילך שולח בקשות הרשאה רק לשרתים שהוא רשם כערוצים עבור אותה הפעלה, לכן הממסר נמצא תחת אותם בקרות ארגוניות ובחירת הצטרפות להפעלה כמו מסירת הודעות. הממסר דורש ממך גם להכניס את השרת לפעולה עם --channels או עם דגל הפיתוח, ודורש מהשרת להצהיר על יכולת ההרשאה.
#כיצד ממסר עובד
כאשר בקשת הרשאה נפתחת, לולאת הממסר כוללת ארבעה שלבים:
Claude Codeמייצר מזהה בקשה קצר ומודיע לשרת שלך- השרת שלך מעביר את הבקשה ואת המזהה לאפליקציית הצ'אט שלך
- המשתמש המרוחק עונה ב-yes או no יחד עם אותו מזהה
- ה-handler הנכנס שלך מפענח את התשובה להכרעה (verdict), ו-
Claude Codeמחיל אותה רק אם המזהה תואם לבקשה פתוחה
תיבת הדו-שיח במסוף המקומי נשארת פתוחה לאורך כל התהליך. אם מישהו במסוף עונה לפני שההכרעה המרוחקת מגיעה, תשובה זו מוחלת במקום והבקשה המרוחקת הממתינה מתבטלת.
#שדות בקשת הרשאה
ההודעה היוצאת מ-Claude Code היא notifications/claude/channel/permission_request. בדומה ל-הודעת ערוץ, התעבורה היא תקן MCP רגיל, אך המתודה והסכמה הן הרחבות של Claude Code. האובייקט params מכיל ארבעה שדות מחרוזת שהשרת שלך מעצב לתוך הבקשה היוצאת:
| שדה | תיאור |
|---|---|
request_id | חמש אותיות קטנות מתוך a עד z ללא l, כך שלעולם לא ייקרא כמו 1 או I כאשר מקלידים בטלפון. כלול אותו בבקשה היוצאת שלך כדי שניתן יהיה להחזיר אותו בתשובה. Claude Code מקבל רק הכרעה שנושאת מזהה שהוא הנפיק. תיבת הדו-שיח במסוף המקומי אינה מציגה מזהה זה, לכן ה-handler היוצא שלך הוא הדרך היחידה לדעת אותו. |
tool_name | שם הכלי ש-Claude רוצה להשתמש בו, לדוגמה Bash או Write. |
description | תמצית קריאה לבני אדם של מה שקריאת כלי ספציפית זו עושה, לעולם לא הפקודה עצמה. עבור קריאת Bash זהו התיאור של Claude לפקודה; כאשר המודל אינו מספק תיאור, השדה מכיל את הקבוע Run shell command ואינו נושא שום פרטי פקודה. הצג את input_preview כאשר יש לך מקום. |
input_preview | הארגומנטים של הכלי כטקסט תצוגה במבנה JSON, ממופה לפי שדה ברמה העליונה. עבור Bash זוהי הפקודה; עבור Write, נתיב הקובץ והתוכן. השמט אותו מהבקשה שלך אם יש לך מקום להודעה של שורה אחת בלבד. השרת שלך מחליט מה להציג. |
לקוחות בגרסת Claude Code v2.1.211 ומעלה מחטאים (sanitize) את description ואת input_preview לפני העברתם בממסר. צפה לשלושה שינויים בטקסט שאתה מקבל:
Claude Codeמנטרל תווי שינוי כיוון (direction-override), תווים בלתי נראים, ותווים הדומים למירכאות ולסוגריים זוויתיים.Claude Codeמכווץ כל רצף של רווחים לבנים לרווח בודד.Claude Codeמעביר טקסט בשלמותו עד 3,500 נקודות קוד (code points). עבור ערך ארוך יותר, אתה מקבל את תחילתו ואת סופו סביב סמן ספור:⋯ N code points elided ⋯. סוף הפקודה הארוכה עדיין מגיע למאשר.
עבור input_preview, Claude Code מחיל את מגבלת 3,500 נקודות הקוד על כל שדה ברמה העליונה של הארגומנטים בנפרד, ושומר על המירכאות המבניות של ה-JSON עצמו. לקוחות לפני גרסה v2.1.211 מעבירים את description בצורה גולמית וחותכים את input_preview ל-200 יחידות UTF-16 עם שלוש נקודות בסוף.
לקוחות בגרסת Claude Code v2.1.234 ומעלה מעבירים את הסמן (value unserializable) במקום ערך שדה ב-input_preview שהם אינם יכולים לסרייל בבטחה, כגון מבנה מעגלי או מערך גדול במיוחד. אתה עדיין מקבל את מפתח השדה, ושאר שדות התצוגה המקדימה נשארים ללא שינוי.
לקוחות בגרסת Claude Code v2.1.234 ומעלה גם מסווים פרטי הזדהות (credentials) בתוך description ובתוך input_preview. אתה מקבל [REDACTED] במקום אסימון הזדהות מוכר של ספק, כגון מפתח API או אסימון גישה אישי. צפה לשלוש השפעות של ההסוואה בעת הצגת השדות:
Claude Codeמסווה שמות מפתחות בתוךinput_previewבנוסף לערכים שלהם. שם מפתח שאתה מציג עשוי לא להתאים לשם המפתח בקלט.Claude Codeלעולם אינו מסווה מקטע המכיל תחביר shell, תווי נתיב או תווי URL. הסוואה אינה יכולה להסתיר את הפקודה, נתיב הקובץ או היעד המאושרים.Claude Codeאינו מסווה סוד שחסרה לו קידומת מוכרת, או סוד שמתפרס על פני רווחים לבנים, כגון בלוק מפתח פרטי. שניהם מגיעים לשרת שלך ללא הסוואה.
ההסוואה אינה משנה מי מקבל את השדות. כל מה שנשאר ללא הסוואה מגיע רק לשרתים שבחרת להפעיל עם --channels או עם דגל הפיתוח. התייחס לשני השדות כבלתי מהימנים אלא אם כן אתה שולט בצי הלקוחות.
ההכרעה שהשרת שלך מחזיר היא notifications/claude/channel/permission עם שני שדות: request_id המהדהד את המזהה שלמעלה, ו-behavior שמוגדר ל-'allow' או 'deny'. ערך allow מאפשר לקריאת הכלי להמשיך; ערך deny דוחה אותה, בדומה למענה No בתיבת הדו-שיח המקומית. אף אחת מההכרעות אינה משפיעה על קריאות עתידיות.
#הוספת ממסר לגשר צ'אט
הוספת ממסר הרשאות לערוץ דו-כיווני דורשת שלושה מרכיבים:
- רשומת
'claude/channel/permission': {}תחת היכולות שלexperimentalבבנאי ה-Serverשלך כדי ש-Claude Codeיידע להעביר בקשות - פונקציית טיפול בהודעות עבור
notifications/claude/channel/permission_requestשמעצבת את הבקשה ושולחת אותה דרך ה-API של הפלטפורמה שלך - בדיקה ב-handler ההודעות הנכנסות שלך שמזהה
yes <id>אוno <id>ומשדרת הכרעתnotifications/claude/channel/permissionבמקום להעביר את הטקסט אלClaude
הצהר על היכולת רק אם הערוץ שלך מאמת את השולח, מכיוון שכל מי שיכול לענות דרך הערוץ שלך יכול לאשר או לדחות שימוש בכלים בהפעלה שלך.
להוספת מרכיבים אלה לגשר צ'אט דו-כיווני כמו זה שנבנה בסעיף חשיפת כלי מענה:
הצהרה על יכולת ההרשאה: בבנאי ה-
Serverשלך, הוסף את'claude/channel/permission': {}לצד'claude/channel'תחתexperimental:capabilities: { experimental: { 'claude/channel': {}, 'claude/channel/permission': {}, // opt in to permission relay }, tools: {}, },טיפול בבקשה הנכנסת: רשום notification handler בין בנאי ה-
Serverלביןmcp.connect().Claude Codeקורא לו עם ארבעת שדות הבקשה כאשר תיבת דו-שיח של הרשאה נפתחת. ה-handler שלך מעצב את הבקשה עבור הפלטפורמה שלך וכולל הוראות למענה עם המזהה: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() is your outbound: POST to your chat platform, or for local // testing the SSE broadcast shown in the full example below. send( `Claude wants to run ${params.tool_name}: ${params.description}\n` + // input_preview carries the actual arguments; render it when you // have room: 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 Step 3 `Reply "yes ${params.request_id}" or "no ${params.request_id}"`, ) })יירוט ההכרעה ב-handler הנכנס שלך: ה-handler הנכנס שלך הוא הלולאה או ה-callback שמקבלים הודעות מהפלטפורמה שלך: אותו מקום שבו אתה מסנן לפי שולח ומשדר
notifications/claude/channelכדי להעביר צ'אט אלClaude. הוסף בדיקה לפני הקריאה להעברת הצ'אט, אשר מזהה את פורמט ההכרעה ומשדרת את הודעת ההרשאה במקום זאת.ביטוי ה-regex מתאים לפורמט המזהה ש-
Claude Codeמייצר: חמש אותיות, ללא האותl. הדגל/iמאפשר התמודדות עם תיקון שגיאות אוטומטי של הטלפון שהופך את התשובה לאותיות גדולות; הפוך את המזהה שנלכד לאותיות קטנות לפני שליחתו בחזרה.// matches "y abcde", "yes abcde", "n abcde", "no abcde" // [a-km-z] is the ID alphabet Claude Code uses (lowercase, skips 'l') // /i tolerates phone autocorrect; lowercase the capture before sending 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 // emit the verdict notification back to Claude Code instead of chat 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) } }, }) }
תשובה מרוחקת שאינה תואמת במדויק את הפורמט הצפוי נכשלת באחת משתי דרכים, ובשתיהן תיבת הדו-שיח במסוף המקומי נשארת פתוחה:
- פורמט שונה: ביטוי ה-regex ב-handler הנכנס שלך נכשל בהתאמה, לכן טקסט כמו
approve itאוyesללא מזהה עובר כהודעה רגילה אלClaude. - פורמט נכון, מזהה שגוי: השרת שלך משדר הכרעה, אך
Claude Codeאינו מוצא בקשה פתוחה עם מזהה זה ומשמיט אותה בשקט.
#דוגמה מלאה
הקובץ webhook.ts המורכב להלן משלב את כל שלוש ההרחבות מדף זה: כלי המענה, סינון השולחים וממסר ההרשאות. אם אתה מתחיל מכאן, תזדקק גם ל-הגדרת הפרויקט ולרשומת .mcp.json מתוך ההדרכה הראשונית.
כדי לאפשר בדיקה של שני הכיוונים מתוך curl, מאזין ה-HTTP משרת שני נתיבים:
GET /events: מחזיק זרם SSE פתוח ודוחף כל הודעה יוצאת כשורה המתחילה ב-data:, כך ש-curl -Nיכול לצפות בתשובות ובבקשות ההרשאה של Claude מגיעות בשידור חי.POST /: הצד הנכנס, אותו handler כמו קודם, וכעת בדיקת פורמט ההכרעה מוכנסת לפני ענף העברת הצ'אט.
#!/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: write to any curl -N listeners on /events --------------------
// A real bridge would POST to your chat platform instead.
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)
}
// Sender allowlist. For the local walkthrough we trust the single X-Sender
// header value "dev"; a real bridge would check the platform's user ID.
const allowed = new Set(['dev'])
const mcp = new Server(
{ name: 'webhook', version: '0.0.1' },
{
capabilities: {
experimental: {
'claude/channel': {},
'claude/channel/permission': {}, // opt in to permission relay
},
tools: {},
},
instructions:
'Messages arrive as <channel source="webhook" chat_id="...">. ' +
'Reply with the reply tool, passing the chat_id from the tag.',
},
)
// --- reply tool: Claude calls this to send a message back -------------------
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'reply',
description: 'Send a message back over this channel',
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'],
},
}],
}))
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 (not Claude) calls this when a dialog opens
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 wants to run ${params.tool_name}: ${params.description}\n` +
`${params.input_preview}\n\n` +
`Reply "yes ${params.request_id}" or "no ${params.request_id}"`,
)
})
await mcp.connect(new StdioServerTransport())
// --- HTTP on :8788: GET /events streams outbound, POST routes 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, // don't close idle SSE streams
async fetch(req) {
const url = new URL(req.url)
// GET /events: SSE stream so curl -N can watch replies and prompts live
if (req.method === 'GET' && url.pathname === '/events') {
const stream = new ReadableStream({
start(ctrl) {
ctrl.enqueue(': connected\n\n') // so curl shows something immediately
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' },
})
}
// everything else is inbound: gate on sender first
const body = await req.text()
const sender = req.headers.get('X-Sender') ?? ''
if (!allowed.has(sender)) return new Response('forbidden', { status: 403 })
// check for verdict format before treating as 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')
}
// normal chat: forward to Claude as a 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')
},
})בדוק את נתיב ההכרעה בשלושה מסופים. הראשון הוא הפעלת ה-Claude Code שלך, שהופעלה עם דגל הפיתוח כך שהיא מריצה את webhook.ts:
claude --dangerously-load-development-channels server:webhookהדרכה זו בודקת את תיבת הדו-שיח של ההרשאה עצמה, לכן ברגע שההפעלה נפתחת, לחץ על Shift+Tab עד ששורת המצב תציג ⏸ manual mode on. במצב אוטומטי, המסווג יחליט על קריאת ה-reply במקומך, ושום תיבת דו-שיח לא תיפתח כדי שהצד המרוחק יענה עליה.
במסוף השני, הזרם את הצד היוצא כדי שתוכל לראות את התשובות של Claude וכל בקשת הרשאה ברגע שהן מופעלות:
curl -N localhost:8788/eventsבמסוף השלישי, שלח הודעה שתגרום ל-Claude לנסות להריץ פקודה:
curl -d "list the files in this directory" -H "X-Sender: dev" localhost:8788הצגת רשימת קבצים היא פעולה לקריאה בלבד, לכן Claude מריץ אותה ללא אישור. תיבת הדו-שיח של ההרשאה נפתחת כאשר Claude קורא לכלי reply כדי לשלוח את תשובתו בחזרה. תיבת הדו-שיח המקומית נפתחת במסוף ה-Claude Code שלך, ורגע לאחר מכן הבקשה עבור mcp__webhook__reply מופיעה בזרם /events, כולל המזהה בן חמש האותיות. אשר אותה מהצד המרוחק:
curl -d "yes <id>" -H "X-Sender: dev" localhost:8788תיבת הדו-שיח המקומית נסגרת, כלי ה-reply רץ, והתשובה של Claude נוחתת בזרם.
שלושת החלקים הייחודיים לערוץ בקובץ זה:
- יכולות בבנאי ה-
Server:claude/channelרושם את מאזין ההודעות,claude/channel/permissionמאפשר ממסר הרשאות, ו-toolsמאפשר ל-Claudeלגלות את כלי המענה. - נתיבים יוצאים: ה-handler של כלי
replyהוא מה ש-Claudeקורא לו עבור תגובות שיחה; ה-notification handler שלPermissionRequestSchemaהוא מה ש-Claude Codeקורא לו כאשר נפתחת תיבת דו-שיח של הרשאה. שניהם קוראים ל-send()כדי לשדר דרך/events, אך הם מופעלים על ידי חלקים שונים של המערכת. - HTTP handler: הנתיב
GET /eventsמחזיק זרם SSE פתוח כדי ש-curl יוכל לצפות בפלט יוצא בשידור חי;POSTמיועד לקלט נכנס, ומסונן לפי כותרתX-Sender. גוף בקשה שלyes <id>אוno <id>מועבר אלClaude Codeכהודעת הכרעה ולעולם אינו מגיע אלClaude; כל דבר אחר מועבר אלClaudeכאירוע ערוץ.
#אריזה כתוסף
כדי להפוך את הערוץ שלך לניתן להתקנה ולשיתוף, עטוף אותו ב-תוסף ופרסם אותו ב-מרקטפלייס. משתמשים מתקינים אותו באמצעות /plugin install, ולאחר מכן מפעילים אותו בכל הפעלה באמצעות --channels plugin:<name>@<marketplace>.
ערוץ שפורסם במרקטפלייס משלך עדיין זקוק ל---dangerously-load-development-channels כדי לרוץ, מכיוון שהוא אינו נמצא ב-רשימת המורשים המאושרת. ברירת המחדל של רשימת המורשים היא תוספי הערוצים ב-claude-plugins-official, ש-Anthropic אוצרת לפי שיקול דעתה. טופסי ההגשה בתוך האפליקציה מוסיפים תוספים למרקטפלייס הקהילתי, שאינו נמצא ברשימת המורשים של הערוצים.
אם אתה עובד מול איש קשר שותף ב-Anthropic, פנה אליו כדי לתאם הוספה לרשימת המרקטפלייס הרשמי. בתוכניות Team ו-Enterprise, מנהל מערכת יכול במקום זאת לכלול את התוסף שלך ברשימת allowedChannelPlugins של הארגון עצמו, אשר מחליפה את רשימת המורשים המוגדרת כברירת מחדל של Anthropic.
#ראה גם
- ערוצים כדי להתקין ולהשתמש ב-Telegram, ב-Discord, ב-iMessage, או בהדגמת fakechat, וכדי להפעיל ערוצים עבור ארגון Team או Enterprise
- מימושי ערוצים עובדים לקוד שרת מלא הכולל תהליכי צימוד, כלי מענה וקבצים מצורפים
- MCP עבור הפרוטוקול שביסוד הדברים ששרתי ערוצים מממשים
- תוספים כדי לארוז את הערוץ שלך כך שמשתמשים יוכלו להתקין אותו באמצעות
/plugin install