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.
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:
- ID Deterministik. Dapatkan
iddari prinsipal terautentikasi, URL callback, nama event, dan argumen. Sketsa desain menyarankan SHA-256 terpotong dari kunci tersebut. - Upsert Idempoten. Langganan berulang dengan identitas yang sama memperbarui rekaman yang sudah ada. Bandingkan argumen sebagai JSON kanonik sehingga urutan kunci tidak dapat membuat duplikat.
- Refresh sebelum
refreshBefore. ChatGPT memanggil ulangevents/subscribesebelum waktu tersebut dengan identitas yang sama dan kursor terakhir yang disimpannya; Anda mengembalikan masa berlaku baru. Jika refresh membawa rahasia baru, ganti dan tandatangani dengan kedua kunci untuk jangka waktu singkat. Kembalikancursor: nulluntuk event yang tidak dapat Anda putar ulang.
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.
- Penemuan. Kirim
server/discoverdan pastikan$.result.capabilities.eventsada dan$.result.supportedVersionsberisi2026-07-28. Kirimevents/listdan pastikandeliverysetiap event menyertakanwebhook. - Langganan idempoten. Kirim badan langganan di atas menggunakan
{{CALLBACK_URL}}dan{{WEBHOOK_SECRET}}, dengan post-processor Ekstrak Variabel yang menyimpan$.result.idsebagaiSUB_ID. Kirim ulang tanpa perubahan, lalu dengan kunciargumentsdiurutkan ulang, dan pastikan$.result.idsama dengan{{SUB_ID}}kedua kalinya. - 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. - Tantangan verifikasi. Buat endpoint mock Apidog yang menjawab
{"challenge": "wrong"}dan berlangganan dengan URL mock cloud-nya sebagai callback. Pastikan$.error.codeadalah-32015dan$.error.data.reasonadalahchallenge_failed. Kemudian arahkanCALLBACK_URLke penerima Anda dan konfirmasi langganan berhasil. - 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. - 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.
