מדריך קלוד קוד בעברית

תיעוד 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 יוכל לשלוח הודעות בחזרה. ערוץ עם נתיב שולח מהימן יכול גם לבחור להעביר בקשות הרשאה בממסר, כך שתוכל לאשר או לדחות שימוש בכלים מרחוק.

דף זה מכסה:

כדי להשתמש בערוץ קיים במקום לבנות אחד, ראה ערוצים. 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.

תרשים ארכיטקטורה המציג מערכות חיצוניות שמתחברות לשרת הערוץ המקומי שלך, אשר מתקשר עם Claude Code דרך stdio

#מה שאתה צריך

הדרישה הקשיחה היחידה היא החבילה @modelcontextprotocol/sdk וסביבת ריצה תואמת Node.js. Bun, Node ו-Deno כולן עובדות. התוספים המובנים מראש ב-research preview משתמשים ב-Bun, אבל הערוץ שלך לא חייב לעשות זאת.

השרת שלך צריך:

  1. להצהיר על יכולת claude/channel כדי ש-Claude Code ירשום מאזין הודעות (notification listener)
  2. לשדר אירועי notifications/claude/channel כאשר משהו קורה
  3. להתחבר דרך תעבורת 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.

  1. יצירת הפרויקט: דוגמאות ממסר ההרשאות בהמשך דף זה מייבאות את zod ישירות, לכן היא מותקנת לצד ה-SDK של MCP. צור ספרייה חדשה והתקן את שתיהן:

    mkdir webhook-channel && cd webhook-channel
    bun add @modelcontextprotocol/sdk zod
  2. כתיבת שרת הערוץ: צור קובץ בשם 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, ולכן הוא רץ באותו תהליך. ניתן לפצל אותו למודולים נפרדים בפרויקט גדול יותר.
  3. רישום השרת שלך מול Claude Code: הוסף את השרת להגדרות ה-MCP שלך כדי ש-Claude Code יידע כיצד להפעיל אותו. עבור .mcp.json ברמת הפרויקט באותה ספרייה, השתמש בנתיב יחסי. עבור הגדרות ברמת המשתמש ב-~/.claude.json, השתמש בנתיב מוחלט מלא כדי שניתן יהיה למצוא את השרת מכל פרויקט:

    {
      "mcpServers": {
        "webhook": { "command": "bun", "args": ["./webhook.ts"] }
      }
    }

    Claude Code קורא את הגדרות ה-MCP שלך בעת ההפעלה ומריץ כל שרת כתהליך משנה.

  4. בדיקה: במהלך ה-research preview, ערוצים מותאמים אישית אינם נמצאים ברשימת המורשים, לכן הפעל את Claude Code עם דגל הפיתוח:

    claude --dangerously-load-development-channels server:webhook

    Claude 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.toolsobjectדו-כיווני בלבד. תמיד {}. יכולת כלים סטנדרטית של MCP. ראה חשיפת כלי מענה.
instructionsstringמומלץ. 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 עם שני פרמטרים:

שדהטיפוסתיאור
contentstringגוף האירוע. נמסר כגוף התגית <channel>.
metaRecord<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 יוכל לקרוא לו כדי לשלוח הודעות בחזרה. שום פרט ברישום הכלי אינו ייחודי לערוץ. לכלי מענה יש שלושה מרכיבים:

  1. רשומת tools: {} ביכולות של בנאי ה-Server שלך כדי ש-Claude Code יגלה את הכלי
  2. פונקציות טיפול (handlers) בכלי שמגדירות את הסכמה שלו ומממשות את לוגיקת השליחה
  3. מחרוזת instructions בבנאי ה-Server שלך שמסבירה ל-Claude מתי וכיצד לקרוא לכלי

להוספת מרכיבים אלה ל-מקבל ה-webhook שלמעלה:

  1. אפשור גילוי כלים: בבנאי ה-Server שלך ב-webhook.ts, הוסף את tools: {} ליכולות כדי ש-Claude Code יידע שהשרת שלך מציע כלים:

    capabilities: {
      experimental: { 'claude/channel': {} },
      tools: {},  // enables tool discovery
    },
  2. רישום כלי המענה: הוסף את הקוד הבא ל-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}`)
    })
  3. עדכון ההנחיות: עדכן את מחרוזת ה-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 או עם דגל הפיתוח, ודורש מהשרת להצהיר על יכולת ההרשאה.

#כיצד ממסר עובד

כאשר בקשת הרשאה נפתחת, לולאת הממסר כוללת ארבעה שלבים:

  1. Claude Code מייצר מזהה בקשה קצר ומודיע לשרת שלך
  2. השרת שלך מעביר את הבקשה ואת המזהה לאפליקציית הצ'אט שלך
  3. המשתמש המרוחק עונה ב-yes או no יחד עם אותו מזהה
  4. ה-handler הנכנס שלך מפענח את התשובה להכרעה (verdict), ו-Claude Code מחיל אותה רק אם המזהה תואם לבקשה פתוחה

תיבת הדו-שיח במסוף המקומי נשארת פתוחה לאורך כל התהליך. אם מישהו במסוף עונה לפני שההכרעה המרוחקת מגיעה, תשובה זו מוחלת במקום והבקשה המרוחקת הממתינה מתבטלת.

תרשים רצף: Claude Code שולח הודעת permission_request לשרת הערוץ, השרת מעצב ושולח את הבקשה לאפליקציית הצ'אט, האדם עונה עם הכרעה, והשרת מפענח את התשובה להודעת permission בחזרה אל 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 בתיבת הדו-שיח המקומית. אף אחת מההכרעות אינה משפיעה על קריאות עתידיות.

#הוספת ממסר לגשר צ'אט

הוספת ממסר הרשאות לערוץ דו-כיווני דורשת שלושה מרכיבים:

  1. רשומת 'claude/channel/permission': {} תחת היכולות של experimental בבנאי ה-Server שלך כדי ש-Claude Code יידע להעביר בקשות
  2. פונקציית טיפול בהודעות עבור notifications/claude/channel/permission_request שמעצבת את הבקשה ושולחת אותה דרך ה-API של הפלטפורמה שלך
  3. בדיקה ב-handler ההודעות הנכנסות שלך שמזהה yes <id> או no <id> ומשדרת הכרעת notifications/claude/channel/permission במקום להעביר את הטקסט אל Claude

הצהר על היכולת רק אם הערוץ שלך מאמת את השולח, מכיוון שכל מי שיכול לענות דרך הערוץ שלך יכול לאשר או לדחות שימוש בכלים בהפעלה שלך.

להוספת מרכיבים אלה לגשר צ'אט דו-כיווני כמו זה שנבנה בסעיף חשיפת כלי מענה:

  1. הצהרה על יכולת ההרשאה: בבנאי ה-Server שלך, הוסף את 'claude/channel/permission': {} לצד 'claude/channel' תחת experimental:

    capabilities: {
      experimental: {
        'claude/channel': {},
        'claude/channel/permission': {},  // opt in to permission relay
      },
      tools: {},
    },
  2. טיפול בבקשה הנכנסת: רשום 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}"`,
      )
    })
  3. יירוט ההכרעה ב-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