Panduan MCP Events: Membangun dan Menguji Server MCP Webhook untuk Langganan ChatGPT

Penjelasan MCP Events: bagaimana ChatGPT berlangganan ke server MCP Anda dengan events/subscribe, penandatanganan Webhooks Standar, batasan 256 KiB, dan pengujian di Apidog.

Medy Evrard

2 October 2026

Panduan MCP Events: Membangun dan Menguji Server MCP Webhook untuk Langganan ChatGPT

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise

MCP Events memungkinkan server MCP Anda mendorong pembaruan ke ChatGPT saat itu terjadi, sehingga tidak ada agen yang perlu melakukan polling. Sejak OpenAI DevDay pada 29 September 2026, ChatGPT mendukung spesifikasi MCP Events yang diusulkan pada versi protokol 2026-07-28 (MCP 2.0), di semua paket. Server Anda menambahkan tiga metode (events/list, events/subscribe, events/unsubscribe), mengiklankan events di server/discover, melewati tantangan verifikasi callback, dan menandatangani setiap pengiriman dengan Standard Webhooks HMAC. ChatGPT hanya menerima pengiriman webhook.

tombol

Panduan ini mencakup setiap bentuk pesan yang didokumentasikan OpenAI, aturan keamanan yang diberlakukan server Anda, dan uji coba untuk keseluruhan siklus. Baru mengenal protokol ini? Mulai dengan apa itu MCP. Anda akan mengirimkan panggilan JSON-RPC dan memalsukan callback di Apidog.

MCP Events sekilas

Item Yang diharapkan ChatGPT
Protokol MCP 2.0, versi 2026-07-28
Kapabilitas "events": {} di kapabilitas server/discover
Metode events/list, events/subscribe, events/unsubscribe, pada endpoint terautentikasi yang sama dengan alat Anda
Pengiriman Hanya Webhook: tanpa polling, streaming, notifikasi gap atau terminated
Penandatanganan Standard Webhooks HMAC-SHA256
Header webhook-id, webhook-timestamp, webhook-signature, X-MCP-Subscription-Id
Rahasia whsec_ ditambah base64 yang didekodekan menjadi 24-64 byte, disediakan oleh ChatGPT
Batas payload 256 KiB (262.144 byte), satu event per permintaan
ID Langganan Deterministik: prinsipal, URL callback, nama event, argumen
Callback HTTPS, terverifikasi tantangan, tanpa alamat pribadi, tanpa pengalihan

Sumber: Panduan MCP Events OpenAI dan draf sketsa desain MCP Events.

Mengapa event alih-alih polling

Tanpa event, agen yang memantau komentar ulasan akan memanggil alat pada pengatur waktu dan membandingkan hasilnya, menghabiskan permintaan saat tidak ada perubahan dan tertinggal saat ada perubahan. MCP Events membalik arah: server Anda tahu kapan sebuah rekaman berubah, sehingga ia mengirimkan pembaruan. Pertukarannya adalah pertukaran klasik webhooks vs polling, kini di dalam MCP.

Rekap DevDay OpenAI menggunakan papan proyek: pengguna meminta ChatGPT untuk memantau tugas baru, dan ketika ada yang muncul, ChatGPT membaca dokumen yang ditautkan dan menyusun rencana. Dokumen-dokumen tersebut menambahkan dua kasus lagi: mengubah laporan bug di saluran menjadi draf permintaan tarik (message.created, difilter berdasarkan channel_id), dan menerapkan komentar ulasan ke dokumen (comment.created, difilter berdasarkan document_id).

Spesifikasi ini berasal dari Kelompok Kerja Pemicu dan Event MCP (repo) dan diberi label eksperimental, jadi kunci kode Anda ke 2026-07-28. Untuk sisa peluncuran, lihat pusat DevDay 2026.

Iklankan event dan definisikan

Tambahkan events ke kapabilitas dalam respons server/discover Anda:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": { "tools": {}, "events": {} }
  }
}

Kemudian kembalikan definisi dari events/list. Setiap definisi memiliki name, description, delivery: ["webhook"], inputSchema untuk argumen langganan (filter Anda, seperti document_id), dan payloadSchema untuk objek data di setiap pengiriman. Gunakan nama yang stabil dan deskripsi yang spesifik, terapkan filter di server Anda, dan cantumkan hanya event yang dapat dilihat oleh akun yang terhubung.

events/subscribe: permintaan dan respons

Ketika pengguna meminta ChatGPT untuk memantau sesuatu, ChatGPT memanggil events/subscribe:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "events/subscribe",
  "params": {
    "name": "comment.created",
    "arguments": { "document_id": "doc_123" },
    "delivery": {
      "mode": "webhook",
      "url": "https://receiver.example.com/mcp-events/callback_123",
      "secret": "whsec_<base64-encoded-signing-key>"
    },
    "cursor": null
  }
}

Sebelum menerima, otorisasi pengguna untuk event dan argumen tersebut, validasi keduanya terhadap definisi Anda, perlukan rahasia whsec_ yang base64-nya didekodekan menjadi 24-64 byte, verifikasi callback (bagian selanjutnya), dan simpan langganan dengan pemiliknya, filter, URL, rahasia, dan masa berlakunya. Kemudian balas:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "id": "sub_123",
    "refreshBefore": "2026-10-02T12:00:00Z",
    "cursor": null,
    "truncated": false
  }
}

Tiga aturan menjaga langganan tetap benar:

Verifikasi callback sebelum data apa pun

Sebelum mengirim data aplikasi, POST badan verifikasi yang ditandatangani dengan tantangan baru, sekali pakai, berumur pendek:

{ "type": "verification", "challenge": "a-single-use-random-value" }

Berikan webhook-id unik (misalnya msg_verification_123) dan header penandatanganan yang sama seperti pengiriman. ChatGPT akan mengulang {"challenge": "a-single-use-random-value"} dengan 2xx. Bandingkan dalam waktu konstan, lalu aktifkan pengiriman. Jika gagal, kembalikan error JSON-RPC -32015 (CallbackEndpointError) dengan data.reason seperti challenge_failed atau timeout. Cache setiap lulus per prinsipal dan URL untuk periode terbatas agar refresh tidak menantang ulang.

Tantangan ini ada karena pelanggan menyediakan rahasia: tanpanya, siapa pun dapat mengarahkan server Anda ke URL korban dan membanjirinya. Terapkan pengerasan yang sama untuk setiap permintaan keluar: hanya HTTPS, selesaikan dan validasi alamat pada waktu koneksi, blokir alamat pribadi, lokal, dan alamat non-publik lainnya, dan jangan pernah mengikuti pengalihan.

Kirim dan tandatangani event

Ketika event yang cocok terjadi, POST satu objek event ke URL callback:

{
  "eventId": "evt_456",
  "name": "comment.created",
  "timestamp": "2026-10-01T12:05:00Z",
  "data": {
    "document_id": "doc_123",
    "comment_id": "comment_456",
    "text": "Can we add the rollout dates to this section?",
    "url": "https://docs.example.com/doc_123#comment_456"
  },
  "cursor": null
}

Kirim Content-Type: application/json, webhook-id (sama dengan eventId), webhook-timestamp (detik Unix saat penandatanganan), webhook-signature, dan X-MCP-Subscription-Id. Serialisasikan badan sekali dan kirim byte yang persis sama, karena tanda tangan mencakupnya.

Tetap di bawah 256 KiB; untuk rekaman besar, kirim ringkasan dan ekspos alat baca. Perlakukan teks yang dibuat pengguna sebagai data dan jangan pernah memasukkan instruksi model ke dalam payload. Coba lagi kegagalan sementara dengan backoff eksponensial terbatas, jaga ID event dan tandatangani setiap percobaan yang baru. Jangan coba lagi 410 atau 413. Event dapat tiba tidak berurutan, jadi buat alat tulis menjadi idempoten. Panduan desain webhook yang andal mencakup jadwal percobaan ulang.

Verifikasi tanda tangan Webhooks Standar

Sesuai spesifikasi Webhooks Standar, konten yang ditandatangani adalah ${webhook-id}.${webhook-timestamp}.${body}, kunci HMAC-SHA256 adalah rahasia yang didekodekan base64 setelah menghapus whsec_, dan header menyimpan satu atau lebih tanda tangan v1,<base64> yang dipisahkan spasi. Draf MCP memberitahu penerima untuk menolak stempel waktu yang lebih lama dari 5 menit dan melakukan deduplikasi berdasarkan webhook-id.

ChatGPT memverifikasi dari sisinya, tetapi penerima ketat Anda sendiri merupakan oracle uji lokal yang baik. Yang satu ini hanya menggunakan bawaan Node:

// receiver.mjs: strict Standard Webhooks receiver for local tests (Node 18+)
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.WEBHOOK_SECRET; // whsec_...
const MAX_BYTES = 256 * 1024;
const TOLERANCE_S = 5 * 60;
const seen = new Set();

export function verify(raw, h, secret, now = Math.floor(Date.now() / 1000)) {
  const id = h["webhook-id"], ts = h["webhook-timestamp"], sigs = h["webhook-signature"];
  if (!id || !ts || !sigs) return false;
  const t = Number(ts);
  if (!Number.isInteger(t) || Math.abs(now - t) > TOLERANCE_S) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = createHmac("sha256", key).update(`${id}.${ts}.`).update(raw).digest();
  return sigs.split(" ").some((s) => {
    const [version, b64] = s.split(",");
    const got = Buffer.from(b64 ?? "", "base64");
    return version === "v1" && got.length === expected.length && timingSafeEqual(got, expected);
  });
}

if (SECRET) createServer((req, res) => {
  const chunks = [];
  let size = 0;
  req.on("data", (c) => { size += c.length; if (size <= MAX_BYTES) chunks.push(c); });
  req.on("end", () => {
    if (size > MAX_BYTES) return res.writeHead(413).end();
    const raw = Buffer.concat(chunks);
    if (!verify(raw, req.headers, SECRET)) return res.writeHead(401).end();
    let body;
    try { body = JSON.parse(raw); } catch { return res.writeHead(400).end(); }
    if (body.type === "verification") {
      res.writeHead(200, { "Content-Type": "application/json" });
      return res.end(JSON.stringify({ challenge: body.challenge }));
    }
    const id = req.headers["webhook-id"];
    if (!seen.has(id)) { seen.add(id); console.log(req.headers["x-mcp-subscription-id"], body.name, id); }
    res.writeHead(200).end();
  });
}).listen(8787);

Jalankan dengan WEBHOOK_SECRET=whsec_... node receiver.mjs. Ia mengembalikan 413 di atas 256 KiB dan 401 untuk tanda tangan yang buruk atau basi, mengulang tantangan, dan mencatat setiap webhook-id sekali. Kami memeriksa verify() terhadap vektor uji penandatanganan di pustaka JavaScript Standard Webhooks. Untuk teorinya, baca verifikasi tanda tangan webhook.

Server Anda memblokir alamat pribadi, jadi secara default tidak akan dikirimkan ke localhost. Draf memungkinkan target non-publik hanya jika dikonfigurasi secara eksplisit: tambahkan entri daftar izin khusus dev, atau ekspos penerima melalui terowongan.

Uji sebelum ChatGPT melakukannya

Berikut adalah uji coba Apidog untuk mode kegagalan yang dicantumkan OpenAI. Buat lingkungan dengan MCP_URL, MCP_TOKEN, CALLBACK_URL, dan WEBHOOK_SECRET, dan kirim setiap panggilan JSON-RPC sebagai POST ke {{MCP_URL}} dengan Authorization: Bearer {{MCP_TOKEN}} ditambah header yang disyaratkan oleh binding HTTP yang dapat di-stream: Accept: application/json, text/event-stream, MCP-Protocol-Version: 2026-07-28, dan Mcp-Method yang diatur ke metode. Setiap badan juga memerlukan params._meta dengan io.modelcontextprotocol/protocolVersion dan io.modelcontextprotocol/clientCapabilities. Sampel OpenAI mengabaikannya, dan _meta yang hilang juga mengembalikan -32602, sehingga langkah 3 akan berhasil karena alasan yang salah.

  1. Penemuan. Kirim server/discover dan pastikan $.result.capabilities.events ada dan $.result.supportedVersions berisi 2026-07-28. Kirim events/list dan pastikan delivery setiap event menyertakan webhook.
  2. Langganan idempoten. Kirim badan langganan di atas menggunakan {{CALLBACK_URL}} dan {{WEBHOOK_SECRET}}, dengan post-processor Ekstrak Variabel yang menyimpan $.result.id sebagai SUB_ID. Kirim ulang tanpa perubahan, lalu dengan kunci arguments diurutkan ulang, dan pastikan $.result.id sama dengan {{SUB_ID}} kedua kalinya.
  3. Validasi. Kirim rahasia yang didekodekan menjadi kurang dari 24 byte, lalu callback http://. Draf memetakan keduanya ke -32602 (InvalidParams); pastikan pada $.error.code. Callback IP pribadi juga harus gagal.
  4. Tantangan verifikasi. Buat endpoint mock Apidog yang menjawab {"challenge": "wrong"} dan berlangganan dengan URL mock cloud-nya sebagai callback. Pastikan $.error.code adalah -32015 dan $.error.data.reason adalah challenge_failed. Kemudian arahkan CALLBACK_URL ke penerima Anda dan konfirmasi langganan berhasil.
  5. Payload terlalu besar. Picu event yang badannya melebihi 262.144 byte. Pengirim Anda harus menolaknya; jika ada yang lolos, penerima menjawab 413, dan log server Anda seharusnya menunjukkan satu percobaan, bukan percobaan ulang.
  6. Replay dan perusakan. Salin header dan badan pengiriman yang ditandatangani dari log keluar server Anda ke permintaan Apidog baru. Kirim ulang segera: 200, dan tidak ada baris log kedua. Ubah satu byte badan: 401. Kirim ulang setelah 5 menit: 401.

Simpan langkah-langkah ini sebagai skenario dan jalankan di CI dengan Apidog CLI. Playbook pengujian server MCP mencakup panggilan alat, dan cara menguji webhook membahas lebih dalam tentang penerima. Kemudian hubungkan server ke ChatGPT melalui plugin dan jalankan daftar periksa siklus hidup OpenAI.

Pertanyaan Umum

Apa itu MCP Events? Ekstensi MCP eksperimental yang memungkinkan server mendorong notifikasi event ke klien alih-alih klien melakukan polling. ChatGPT mendukung mode webhook-nya pada 2026-07-28.

Apakah ChatGPT mendukung polling atau streaming untuk MCP Events? Tidak. ChatGPT hanya mendukung pengiriman webhook dan verifikasi callback. Polling, streaming, serta notifikasi gap dan terminated tidak didukung.

Paket ChatGPT mana yang mendapatkan MCP Events? Rekap DevDay OpenAI mengatakan fitur ini tersedia untuk semua paket.

Siapa yang membuat rahasia penandatanganan? Pelanggan. ChatGPT mengirimkan rahasia whsec_ di delivery.secret; server Anda memvalidasi, menyimpan, dan menandatangani dengannya, dan tidak pernah menghasilkan sendiri.

Bagaimana ini berbeda dari Agents API? MCP Events mendorong data dari server Anda ke ChatGPT. OpenAI Agents API menjalankan agen yang Anda bangun dan melaporkan kemajuan mereka melalui streaming atau webhook.

Langkah selanjutnya

Tambahkan "events": {} ke satu server, ekspos satu event dengan satu filter, dan jalankan enam pemeriksaan terhadap penerima lokal sebelum menghubungkan ChatGPT. Unduh Apidog untuk menyimpan pemeriksaan tersebut sebagai skenario yang berjalan pada setiap commit.

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.