> ## Documentation Index
> Fetch the complete documentation index at: https://cli.fuxicode.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Referensi Channels

> Bangun server MCP yang mendorong webhooks, alerts, dan pesan chat ke dalam sesi Fuxi Code. Referensi untuk kontrak channel: deklarasi kemampuan, event notifikasi, tools balasan, gating pengirim, dan relay izin.

<Note>
  Channels berada dalam [research preview](/id/channels#research-preview). Organisasi Team dan Enterprise harus [secara eksplisit mengaktifkannya](/id/channels#enterprise-controls).
</Note>

Sebuah channel adalah server MCP yang mendorong event ke dalam sesi Fuxi Code sehingga Fuxi 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 Fuxi bertindak. Channel dua arah seperti chat bridges juga [mengekspos tool balasan](#expose-a-reply-tool) sehingga Fuxi dapat mengirim pesan kembali. Sebuah channel dengan jalur pengirim terpercaya juga dapat memilih untuk [relay permission prompts](#relay-permission-prompts) sehingga Anda dapat menyetujui atau menolak penggunaan tool dari jarak jauh.

Halaman ini mencakup:

* [Overview](#overview): bagaimana channels bekerja
* [Yang Anda butuhkan](#what-you-need): persyaratan dan langkah-langkah umum
* [Contoh: bangun penerima webhook](#example-build-a-webhook-receiver): panduan satu arah minimal
* [Server options](#server-options): field konstruktor
* [Notification format](#notification-format): payload event dan perilaku pengiriman
* [Expose a reply tool](#expose-a-reply-tool): biarkan Fuxi mengirim pesan kembali
* [Gate inbound messages](#gate-inbound-messages): pemeriksaan pengirim untuk mencegah prompt injection
* [Relay permission prompts](#relay-permission-prompts): teruskan permission prompts ke channels jarak jauh

Untuk menggunakan channel yang sudah ada daripada membangun satu, lihat [Channels](/id/channels). Telegram, Discord, iMessage, dan fakechat disertakan dalam research preview.

<h2 id="overview">
  Overview
</h2>

Sebuah channel adalah server [MCP](https://modelcontextprotocol.io) yang berjalan di mesin yang sama dengan Fuxi Code. Fuxi Code menjalankannya sebagai subprocess dan berkomunikasi melalui stdio. Server channel Anda adalah jembatan antara sistem eksternal dan sesi Fuxi 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 Fuxi. 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 Fuxi.

<img src="https://mintcdn.com/fuxi-code/9FG0ZKj9uKYiHmbi/images/channel-architecture.svg?fit=max&auto=format&n=9FG0ZKj9uKYiHmbi&q=85&s=9a037b7da80184ae49015c0256b21a1f" alt="Diagram arsitektur menunjukkan sistem eksternal terhubung ke server channel lokal Anda, yang berkomunikasi dengan Fuxi Code melalui stdio" width="600" height="220" data-path="images/channel-architecture.svg" />

<h2 id="what-you-need">
  Yang Anda butuhkan
</h2>

Satu-satunya persyaratan keras adalah paket [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) dan runtime yang kompatibel dengan Node.js. [Bun](https://bun.sh), [Node](https://nodejs.org), dan [Deno](https://deno.com) semuanya bekerja. Plugin pra-bangun dalam research preview menggunakan Bun, tetapi channel Anda tidak harus.

Server Anda perlu:

1. Mendeklarasikan kemampuan `fuxi/channel` sehingga Fuxi Code mendaftarkan pendengar notifikasi
2. Memancarkan event `notifications/fuxi/channel` ketika sesuatu terjadi
3. Terhubung melalui [stdio transport](https://modelcontextprotocol.io/docs/concepts/transports#standard-io) (Fuxi Code menjalankan server Anda sebagai subprocess)

Bagian [Server options](#server-options) dan [Notification format](#notification-format) mencakup masing-masing secara detail. Lihat [Contoh: bangun penerima webhook](#example-build-a-webhook-receiver) untuk panduan lengkap.

Selama research preview, custom channels tidak ada di [approved allowlist](/id/channels#supported-channels). Gunakan `--dangerously-load-development-channels` untuk menguji secara lokal. Lihat [Test during the research preview](#test-during-the-research-preview) untuk detail.

<h2 id="example-build-a-webhook-receiver">
  Contoh: bangun penerima webhook
</h2>

Panduan ini membangun server file tunggal yang mendengarkan permintaan HTTP dan meneruskannya ke sesi Fuxi Code Anda. Pada akhirnya, apa pun yang dapat mengirim HTTP POST, seperti pipeline CI, alert monitoring, atau perintah `curl`, dapat mendorong event ke Fuxi.

Contoh ini menggunakan [Bun](https://bun.sh) sebagai runtime untuk server HTTP bawaan dan dukungan TypeScript. Anda dapat menggunakan [Node](https://nodejs.org) atau [Deno](https://deno.com) sebagai gantinya; satu-satunya persyaratan adalah [MCP SDK](https://www.npmjs.com/package/@modelcontextprotocol/sdk).

<Steps>
  <Step title="Buat proyek">
    Buat direktori baru dan instal MCP SDK:

    ```bash theme={null}
    mkdir webhook-channel && cd webhook-channel
    bun add @modelcontextprotocol/sdk
    ```
  </Step>

  <Step title="Tulis server channel">
    Buat file bernama `webhook.ts`. Ini adalah seluruh server channel Anda: terhubung ke Fuxi Code melalui stdio, dan mendengarkan POST HTTP pada port 8788. Ketika permintaan tiba, mendorong body ke Fuxi sebagai channel event.

    ```ts title="webhook.ts" theme={null}
    #!/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 — Fuxi Code mendaftarkan pendengar untuknya
        capabilities: { experimental: { 'fuxi/channel': {} } },
        // ditambahkan ke system prompt Fuxi 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 Fuxi Code melalui stdio (Fuxi Code menjalankan proses ini)
    await mcp.connect(new StdioServerTransport())

    // Mulai server HTTP yang meneruskan setiap POST ke Fuxi
    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/fuxi/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 `fuxi/channel` dalam capabilities, yang memberi tahu Fuxi Code ini adalah channel. String [`instructions`](#server-options) masuk ke system prompt Fuxi: beri tahu Fuxi event apa yang diharapkan, apakah akan membalas, dan bagaimana cara merutekan balasan jika harus.
    * **Stdio connection**: terhubung ke Fuxi Code melalui stdin/stdout. Ini standar untuk server [MCP](https://modelcontextprotocol.io/docs/concepts/transports#standard-io) apa pun: Fuxi Code menjalankannya sebagai subprocess.
    * **HTTP listener**: memulai server web lokal pada port 8788. Setiap body POST diteruskan ke Fuxi sebagai channel event melalui `mcp.notification()`. `content` menjadi body event, dan setiap entry `meta` menjadi atribut pada tag `<channel>`. Pendengar memerlukan akses ke instance `mcp`, jadi berjalan dalam proses yang sama. Anda dapat membaginya menjadi modul terpisah untuk proyek yang lebih besar.
  </Step>

  <Step title="Daftarkan server Anda dengan Fuxi Code">
    Tambahkan server ke konfigurasi MCP Anda sehingga Fuxi Code tahu cara memulainya. Untuk `.mcp.json` tingkat proyek di direktori yang sama, gunakan jalur relatif. Untuk konfigurasi tingkat pengguna di `~/.fuxi.json`, gunakan jalur absolut lengkap sehingga server dapat ditemukan dari proyek apa pun:

    ```json title=".mcp.json" theme={null}
    {
      "mcpServers": {
        "webhook": { "command": "bun", "args": ["./webhook.ts"] }
      }
    }
    ```

    Fuxi Code membaca konfigurasi MCP Anda saat startup dan menjalankan setiap server sebagai subprocess.
  </Step>

  <Step title="Uji">
    Selama research preview, custom channels tidak ada di allowlist, jadi mulai Fuxi Code dengan flag pengembangan:

    ```bash theme={null}
    fuxi --dangerously-load-development-channels server:webhook
    ```

    Kali pertama Anda memulai sesi dalam proyek ini, Fuxi 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 Fuxi 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](/id/channels#enterprise-controls) 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):

    ```bash theme={null}
    curl -X POST localhost:8788 -d "build failed on main: https://ci.example.com/run/1234"
    ```

    Payload tiba di sesi Fuxi Code Anda sebagai tag `<channel>`:

    ```text theme={null}
    <channel source="webhook" path="/" method="POST">build failed on main: https://ci.example.com/run/1234</channel>
    ```

    Di terminal Fuxi Code Anda, Anda akan melihat Fuxi menerima pesan dan mulai merespons: membaca file, menjalankan perintah, atau apa pun yang diminta pesan. Ini adalah channel satu arah, jadi Fuxi bertindak dalam sesi Anda tetapi tidak mengirim apa pun kembali melalui webhook. Untuk menambahkan balasan, lihat [Expose a reply tool](#expose-a-reply-tool).

    Jika event tidak tiba, diagnosis tergantung pada apa yang dikembalikan `curl`:

    * **`curl` berhasil tetapi tidak ada yang mencapai Fuxi**: jalankan `/mcp` dalam sesi Anda untuk memeriksa status server. "Failed to connect" biasanya berarti kesalahan dependensi atau impor dalam file server Anda; periksa debug log di `~/.fuxi/debug/<session-id>.txt` untuk jejak stderr.
    * **`curl` gagal dengan "connection refused"**: port tidak terikat lagi atau proses basi dari run sebelumnya memegangnya. `lsof -i :<port>` menunjukkan apa yang mendengarkan; `kill` proses basi sebelum memulai ulang sesi Anda.
  </Step>
</Steps>

[Server fakechat](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins/fakechat) memperluas pola ini dengan UI web, lampiran file, dan tool balasan untuk chat dua arah.

<h2 id="test-during-the-research-preview">
  Uji selama research preview
</h2>

Selama research preview, setiap channel harus ada di [approved allowlist](/id/channels#research-preview) untuk mendaftar. Flag pengembangan melewati allowlist untuk entri spesifik setelah prompt konfirmasi. Contoh ini menunjukkan kedua tipe entri:

```bash theme={null}
# Menguji plugin yang sedang Anda kembangkan
fuxi --dangerously-load-development-channels plugin:yourplugin@yourmarketplace

# Menguji server .mcp.json telanjang (belum ada wrapper plugin)
fuxi --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.

<Note>
  Flag ini melewati allowlist saja. Kebijakan organisasi `channelsEnabled` masih berlaku. Jangan gunakan untuk menjalankan channels dari sumber yang tidak terpercaya.
</Note>

<h2 id="server-options">
  Server options
</h2>

Sebuah channel menetapkan opsi ini dalam konstruktor [`Server`](https://modelcontextprotocol.io/docs/concepts/servers). Field `instructions` dan `capabilities.tools` adalah [MCP standar](https://modelcontextprotocol.io/docs/concepts/servers); `capabilities.experimental['fuxi/channel']` dan `capabilities.experimental['fuxi/channel/permission']` adalah penambahan spesifik channel:

| Field                                                    | Type     | Description                                                                                                                                                                                                                                                                                                          |
| :------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `capabilities.experimental['fuxi/channel']`            | `object` | Diperlukan. Selalu `{}`. Kehadiran mendaftarkan pendengar notifikasi.                                                                                                                                                                                                                                                |
| `capabilities.experimental['fuxi/channel/permission']` | `object` | Opsional. Selalu `{}`. Mendeklarasikan bahwa channel ini dapat menerima permission relay requests. Ketika dideklarasikan, Fuxi Code meneruskan permission prompts ke channel Anda sehingga Anda dapat menyetujui atau menolak mereka dari jarak jauh. Lihat [Relay permission prompts](#relay-permission-prompts). |
| `capabilities.tools`                                     | `object` | Dua arah saja. Selalu `{}`. Kemampuan tool MCP standar. Lihat [Expose a reply tool](#expose-a-reply-tool).                                                                                                                                                                                                           |
| `instructions`                                           | `string` | Direkomendasikan. Ditambahkan ke system prompt Fuxi. Beri tahu Fuxi 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:

```ts theme={null}
import { Server } from '@modelcontextprotocol/sdk/server/index.js'

const mcp = new Server(
  { name: 'your-channel', version: '0.0.1' },
  {
    capabilities: {
      experimental: { 'fuxi/channel': {} },  // mendaftarkan pendengar channel
      tools: {},  // hilangkan untuk channels satu arah
    },
    // ditambahkan ke system prompt Fuxi 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/fuxi/channel`. Params ada di bagian berikutnya.

<h2 id="notification-format">
  Notification format
</h2>

Server Anda memancarkan `notifications/fuxi/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:

```ts theme={null}
await mcp.notification({
  method: 'notifications/fuxi/channel',
  params: {
    content: 'build failed on main: https://ci.example.com/run/1234',
    meta: { severity: 'high', run_id: '1234' },
  },
})
```

Event tiba dalam konteks Fuxi dibungkus dalam tag `<channel>`. Atribut `source` ditetapkan secara otomatis dari nama server yang dikonfigurasi:

```text theme={null}
<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 Fuxi 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](#expose-a-reply-tool) yang dapat dipanggil Fuxi untuk melaporkan status kembali.

Event antri ke dalam sesi dan diproses secara berurutan. Jika beberapa notifikasi tiba saat Fuxi sibuk, mereka dikirimkan bersama pada giliran berikutnya dan Fuxi menangani mereka sebagai grup. Untuk memproses aliran event independen secara bersamaan, jalankan sesi terpisah.

<h2 id="expose-a-reply-tool">
  Expose a reply tool
</h2>

Jika channel Anda dua arah, seperti chat bridge daripada alert forwarder, ekspos [tool MCP](https://modelcontextprotocol.io/docs/concepts/tools) standar yang dapat dipanggil Fuxi untuk mengirim pesan kembali. Tidak ada yang spesifik channel tentang registrasi tool. Tool balasan memiliki tiga komponen:

1. Entry `tools: {}` dalam capabilities konstruktor `Server` Anda sehingga Fuxi Code menemukan tool
2. Tool handlers yang mendefinisikan schema tool dan mengimplementasikan logika pengiriman
3. String `instructions` dalam konstruktor `Server` Anda yang memberi tahu Fuxi kapan dan bagaimana memanggil tool

Untuk menambahkan ini ke [penerima webhook di atas](#example-build-a-webhook-receiver):

<Steps>
  <Step title="Aktifkan penemuan tool">
    Dalam konstruktor `Server` Anda di `webhook.ts`, tambahkan `tools: {}` ke capabilities sehingga Fuxi Code tahu server Anda menawarkan tools:

    ```ts theme={null}
    capabilities: {
      experimental: { 'fuxi/channel': {} },
      tools: {},  // mengaktifkan penemuan tool
    },
    ```
  </Step>

  <Step title="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 Fuxi dengan `chat_id` dan `text`:

    ```ts theme={null}
    // Tambahkan import ini di bagian atas webhook.ts
    import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'

    // Fuxi 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 Fuxi 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'],
        },
      }],
    }))

    // Fuxi 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}`)
    })
    ```
  </Step>

  <Step title="Perbarui instructions">
    Perbarui string `instructions` dalam konstruktor `Server` Anda sehingga Fuxi tahu merutekan balasan kembali melalui tool. Contoh ini memberi tahu Fuxi untuk melewatkan `chat_id` dari tag inbound:

    ```ts theme={null}
    instructions: 'Messages tiba sebagai <channel source="webhook" chat_id="...">. Balas dengan tool balasan, melewatkan chat_id dari tag.'
    ```
  </Step>
</Steps>

Berikut adalah `webhook.ts` lengkap dengan dukungan dua arah. Balasan outbound mengalir melalui `GET /events` menggunakan [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE), jadi `curl -N localhost:8788/events` dapat menontonnya secara langsung; chat inbound tiba di `POST /`:

```ts title="Full webhook.ts with reply tool' expandable theme={null}
#!/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: { 'fuxi/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 Fuxi 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 Fuxi sebagai channel event
    const body = await req.text()
    const chat_id = String(nextId++)
    await mcp.notification({
      method: 'notifications/fuxi/channel',
      params: {
        content: body,
        meta: { chat_id, path: url.pathname, method: req.method },
      },
    })
    return new Response('ok')
  },
})
```

[Server fakechat](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins/fakechat) menunjukkan contoh yang lebih lengkap dengan lampiran file dan pengeditan pesan.

<h2 id="gate-inbound-messages">
  Gate inbound messages
</h2>

Channel tanpa gate adalah vektor prompt injection. Siapa pun yang dapat menjangkau endpoint Anda dapat menempatkan teks di depan Fuxi. 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:

```ts theme={null}
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](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins/telegram) dan [Discord](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins/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 Fuxi Code mereka, dan ID platform mereka ditambahkan. Lihat implementasi mana pun untuk alur pairing lengkap. Channel [iMessage](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins/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.

<h2 id="relay-permission-prompts">
  Relay permission prompts
</h2>

Ketika Fuxi 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 Fuxi 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.

<h3 id="how-relay-works">
  Bagaimana relay bekerja
</h3>

Ketika permission prompt terbuka, loop relay memiliki empat langkah:

1. Fuxi Code menghasilkan ID permintaan pendek dan memberi tahu server Anda
2. Server Anda meneruskan prompt dan ID ke aplikasi chat Anda
3. Pengguna jarak jauh membalas dengan ya atau tidak dan ID itu
4. Handler inbound Anda menguraikan balasan menjadi verdict, dan Fuxi 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.

<img src="https://mintcdn.com/fuxi-code/9FG0ZKj9uKYiHmbi/images/channel-permission-relay.svg?fit=max&auto=format&n=9FG0ZKj9uKYiHmbi&q=85&s=97d57f128f0da55f105ab1e3a7e10240" alt="Diagram urutan: Fuxi Code mengirim notifikasi permission_request ke server channel, server memformat dan mengirim prompt ke aplikasi chat, manusia membalas dengan verdict, dan server menguraikan balasan itu menjadi notifikasi permission kembali ke Fuxi Code" width="600" height="230" data-path="images/channel-permission-relay.svg" />

<h3 id="permission-request-fields">
  Permission request fields
</h3>

Notifikasi outbound dari Fuxi Code adalah `notifications/fuxi/channel/permission_request`. Seperti [channel notification](#notification-format), transport adalah MCP standar tetapi method dan schema adalah ekstensi Fuxi 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. Fuxi 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 Fuxi, 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 Fuxi 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/fuxi/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.

<h3 id="add-relay-to-a-chat-bridge">
  Tambahkan relay ke chat bridge
</h3>

Menambahkan permission relay ke channel dua arah memerlukan tiga komponen:

1. Entry `fuxi/channel/permission: {}` di bawah capabilities `experimental` dalam konstruktor `Server` Anda sehingga Fuxi Code tahu untuk meneruskan prompts
2. Notification handler untuk `notifications/fuxi/channel/permission_request` yang memformat prompt dan mengirimnya melalui API platform Anda
3. Pemeriksaan dalam handler pesan inbound Anda yang mengenali `yes <id>` atau `no <id>` dan memancarkan notifikasi `notifications/fuxi/channel/permission` verdict sebagai gantinya dari meneruskan teks ke Fuxi

Hanya deklarasikan kemampuan jika channel Anda [mengautentikasi pengirim](#gate-inbound-messages), 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](#expose-a-reply-tool):

<Steps>
  <Step title="Deklarasikan kemampuan permission">
    Dalam konstruktor `Server` Anda, tambahkan `fuxi/channel/permission: {}` bersama `fuxi/channel` di bawah `experimental`:

    ```ts theme={null}
    capabilities: {
      experimental: {
        'fuxi/channel': {},
        'fuxi/channel/permission': {},  // opt in ke permission relay
      },
      tools: {},
    },
    ```
  </Step>

  <Step title="Tangani permintaan masuk">
    Daftarkan notification handler antara konstruktor `Server` Anda dan `mcp.connect()`. Fuxi Code memanggilnya dengan [empat request fields](#permission-request-fields) ketika dialog permission terbuka. Handler Anda memformat prompt untuk platform Anda dan menyertakan instruksi untuk membalas dengan ID:

    ```ts theme={null}
    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/fuxi/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(
        `Fuxi 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}"`,
      )
    })
    ```
  </Step>

  <Step title="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](#gate-inbound-messages) dan memancarkan `notifications/fuxi/channel` untuk meneruskan chat ke Fuxi. Tambahkan pemeriksaan sebelum panggilan chat-forwarding yang mengenali format verdict dan memancarkan notifikasi permission sebagai gantinya.

    Regex cocok dengan format ID yang dihasilkan Fuxi Code: lima huruf, tidak pernah `l`. Flag `/i` mentoleransi autocorrect ponsel memanfaatkan balasan; hurufkan ID yang ditangkap sebelum mengirimnya kembali.

    ```ts theme={null}
    // cocok dengan "y abcde", "yes abcde", "n abcde", "no abcde"
    // [a-km-z] adalah alfabet ID yang digunakan Fuxi 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 Fuxi Code daripada chat
        await mcp.notification({
          method: 'notifications/fuxi/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/fuxi/channel',
        params: { content: message.text, meta: { chat_id: String(message.chat.id) } },
      })
    }
    ```
  </Step>
</Steps>

Fuxi 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 it` atau `yes` tanpa ID jatuh melalui sebagai pesan normal ke Fuxi.
* **Format benar, ID salah**: server Anda memancarkan verdict, tetapi Fuxi Code tidak menemukan permintaan terbuka dengan ID itu dan menjatuhkannya diam-diam.

<h3 id="full-example">
  Contoh lengkap
</h3>

`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`](#example-build-a-webhook-receiver) 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 baris `data:`, jadi `curl -N` dapat menonton balasan Fuxi dan permission prompts tiba secara langsung.
* **`POST /`**: sisi inbound, handler yang sama seperti sebelumnya, sekarang dengan pemeriksaan format verdict disisipkan sebelum cabang chat-forward.

```ts title="Full webhook.ts with permission relay' expandable theme={null}
#!/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: {
        'fuxi/channel': {},
        'fuxi/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: Fuxi 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: Fuxi Code (bukan Fuxi) memanggil ini ketika dialog terbuka
const PermissionRequestSchema = z.object({
  method: z.literal('notifications/fuxi/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(
    `Fuxi 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/fuxi/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 Fuxi sebagai channel event
    const chat_id = String(nextId++)
    await mcp.notification({
      method: 'notifications/fuxi/channel',
      params: { content: body, meta: { chat_id, path: url.pathname } },
    })
    return new Response('ok')
  },
})
```

Uji jalur verdict dalam tiga terminal. Yang pertama adalah sesi Fuxi Code Anda, dimulai dengan [development flag](#test-during-the-research-preview) sehingga menjalankan `webhook.ts`:

```bash theme={null}
fuxi --dangerously-load-development-channels server:webhook
```

Di yang kedua, alirkan sisi outbound sehingga Anda dapat melihat balasan Fuxi dan permission prompts apa pun saat mereka menyala:

```bash theme={null}
curl -N localhost:8788/events
```

Di yang ketiga, kirim pesan yang akan membuat Fuxi mencoba menjalankan perintah:

```bash theme={null}
curl -d "list the files in this directory" -H "X-Sender: dev" localhost:8788
```

Listing file adalah read-only, jadi Fuxi menjalankannya tanpa persetujuan. Dialog permission terbuka ketika Fuxi memanggil tool `reply` untuk mengirim jawabannya kembali. Dialog lokal terbuka di terminal Fuxi Code Anda, dan sebentar kemudian prompt untuk `mcp__webhook__reply` muncul dalam aliran `/events`, termasuk ID lima huruf. Setujui dari sisi jarak jauh:

```bash theme={null}
curl -d "yes <id>" -H "X-Sender: dev" localhost:8788
```

Dialog lokal menutup, tool `reply` berjalan, dan balasan Fuxi mendarat dalam aliran.

Tiga bagian spesifik channel dalam file ini:

* **Capabilities** dalam konstruktor `Server`: `fuxi/channel` mendaftarkan pendengar notifikasi, `fuxi/channel/permission` opt in ke permission relay, `tools` membiarkan Fuxi menemukan tool balasan.
* **Outbound paths**: handler tool `reply` adalah apa yang dipanggil Fuxi untuk respons percakapan; handler notifikasi `PermissionRequestSchema` adalah apa yang dipanggil Fuxi Code ketika dialog permission terbuka. Keduanya memanggil `send()` untuk broadcast melalui `/events`, tetapi dipicu oleh bagian berbeda dari sistem.
* **HTTP handler**: `GET /events` memegang aliran SSE terbuka sehingga curl dapat menonton outbound secara langsung; `POST` adalah inbound, gated pada header `X-Sender`. Body `yes <id>` atau `no <id>` masuk ke Fuxi Code sebagai notifikasi verdict dan tidak pernah mencapai Fuxi; apa pun lainnya diteruskan ke Fuxi sebagai channel event.

<h2 id="package-as-a-plugin">
  Paket sebagai plugin
</h2>

Untuk membuat channel Anda dapat diinstal dan dibagikan, bungkus dalam [plugin](/id/plugins) dan publikasikan ke [marketplace](/id/plugin-marketplaces). 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](/id/channels#supported-channels). Allowlist default adalah plugin channel di `fuxi-plugins-official`, yang dikurasi Anthropic sesuai kebijaksanaannya. [Formulir pengajuan in-app](/id/plugins#submit-your-plugin-to-the-community-marketplace) 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`](/id/channels#restrict-which-channel-plugins-can-run) organisasi, yang menggantikan allowlist Anthropic default.

<h2 id="see-also">
  Lihat juga
</h2>

* [Channels](/id/channels) untuk menginstal dan menggunakan Telegram, Discord, iMessage, atau demo fakechat, dan untuk mengaktifkan channels untuk Team atau Enterprise org
* [Working channel implementations](https://github.com/anthropics/fuxi-plugins-official/tree/main/external_plugins) untuk kode server lengkap dengan alur pairing, tools balasan, dan lampiran file
* [MCP](/id/mcp) untuk protokol dasar yang diimplementasikan server channel
* [Plugins](/id/plugins) untuk mengemas channel Anda sehingga pengguna dapat menginstalnya dengan `/plugin install`
