Sự kiện MCP cho phép máy chủ MCP của bạn đẩy các bản cập nhật lên ChatGPT ngay khi chúng xảy ra, vì vậy không có tác nhân nào phải thăm dò thông tin. Kể từ OpenAI DevDay vào ngày 29 tháng 9 năm 2026, ChatGPT hỗ trợ đặc tả Sự kiện MCP được đề xuất tại phiên bản giao thức 2026-07-28 (MCP 2.0), trên tất cả các gói. Máy chủ của bạn bổ sung ba phương thức (events/list, events/subscribe, events/unsubscribe), quảng cáo events trong server/discover, vượt qua thử thách xác minh callback và ký mọi lần gửi với Standard Webhooks HMAC. ChatGPT chỉ chấp nhận gửi webhook.
Hướng dẫn này bao gồm từng định dạng thông báo mà OpenAI tài liệu hóa, các quy tắc bảo mật mà máy chủ của bạn thực thi và một lượt kiểm tra toàn bộ vòng lặp. Bạn mới làm quen với giao thức? Bắt đầu với MCP là gì. Bạn sẽ gửi các cuộc gọi JSON-RPC và mô phỏng callback trong Apidog.
Tổng quan về Sự kiện MCP
| Mục | Những gì ChatGPT mong đợi |
|---|---|
| Giao thức | MCP 2.0, phiên bản 2026-07-28 |
| Khả năng | "events": {} trong các khả năng của server/discover |
| Các phương thức | events/list, events/subscribe, events/unsubscribe, trên cùng một điểm cuối đã xác thực như công cụ của bạn |
| Gửi | Chỉ webhook: không thăm dò, truyền trực tuyến, thông báo gap hoặc terminated |
| Ký | Standard Webhooks HMAC-SHA256 |
| Tiêu đề | webhook-id, webhook-timestamp, webhook-signature, X-MCP-Subscription-Id |
| Khóa bí mật | whsec_ cộng với base64 giải mã ra 24-64 byte, được cung cấp bởi ChatGPT |
| Giới hạn tải trọng | 256 KiB (262.144 byte), một sự kiện mỗi yêu cầu |
| ID Đăng ký | Xác định: principal, URL callback, tên sự kiện, đối số |
| Callbacks | HTTPS, đã xác minh thử thách, không địa chỉ riêng tư, không chuyển hướng |
Nguồn: Hướng dẫn Sự kiện MCP của OpenAI và bản phác thảo thiết kế Sự kiện MCP.
Tại sao lại dùng sự kiện thay vì thăm dò
Nếu không có sự kiện, một tác nhân theo dõi bình luận đánh giá sẽ gọi một công cụ theo thời gian và so sánh kết quả, gây lãng phí yêu cầu khi không có gì thay đổi và chậm trễ khi có điều gì đó xảy ra. Sự kiện MCP đảo ngược hướng: máy chủ của bạn biết khi nào một bản ghi thay đổi, vì vậy nó gửi bản cập nhật. Các đánh đổi là những vấn đề kinh điển của webhooks vs thăm dò, giờ đây nằm trong MCP.
Tổng kết DevDay của OpenAI sử dụng một bảng dự án: người dùng yêu cầu ChatGPT theo dõi các tác vụ mới, và khi một tác vụ xuất hiện, ChatGPT đọc các tài liệu được liên kết và soạn thảo một kế hoạch. Các tài liệu bổ sung thêm hai trường hợp: chuyển báo cáo lỗi trong một kênh thành yêu cầu kéo bản nháp (message.created, được lọc bởi channel_id), và áp dụng các bình luận đánh giá vào một tài liệu (comment.created, được lọc bởi document_id).
Đặc tả đến từ Nhóm công tác Kích hoạt và Sự kiện của MCP (repo) và được gắn nhãn là thử nghiệm, vì vậy hãy khóa mã của bạn vào 2026-07-28. Để biết phần còn lại của bản phát hành, hãy xem trung tâm DevDay 2026.
Quảng cáo sự kiện và định nghĩa chúng
Thêm events vào các khả năng trong phản hồi server/discover của bạn:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": { "tools": {}, "events": {} }
}
}
Sau đó trả về các định nghĩa từ events/list. Mỗi định nghĩa bao gồm một name, một description, delivery: ["webhook"], một inputSchema cho các đối số đăng ký (các bộ lọc của bạn, chẳng hạn như document_id), và một payloadSchema cho đối tượng data trong mỗi lần gửi. Sử dụng các tên ổn định và mô tả cụ thể, áp dụng các bộ lọc trên máy chủ của bạn và chỉ liệt kê các sự kiện mà tài khoản được kết nối có thể thấy.
events/subscribe: yêu cầu và phản hồi
Khi người dùng yêu cầu ChatGPT theo dõi điều gì đó, ChatGPT gọi 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
}
}
Trước khi chấp nhận, ủy quyền cho người dùng đối với sự kiện và các đối số đó, xác thực cả hai theo định nghĩa của bạn, yêu cầu một khóa bí mật whsec_ có base64 giải mã ra 24-64 byte, xác minh callback (phần tiếp theo), và lưu trữ đăng ký với chủ sở hữu, bộ lọc, URL, khóa bí mật và thời hạn hết hạn của nó. Sau đó trả lời:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"id": "sub_123",
"refreshBefore": "2026-10-02T12:00:00Z",
"cursor": null,
"truncated": false
}
}
Ba quy tắc giữ cho các đăng ký chính xác:
- ID xác định. Suy ra
idtừ principal đã xác thực, URL callback, tên sự kiện và các đối số. Bản phác thảo thiết kế gợi ý một SHA-256 rút gọn của khóa đó. - Cập nhật bảo lưu. Một lần đăng ký lặp lại với cùng một danh tính sẽ cập nhật bản ghi hiện có. So sánh các đối số dưới dạng JSON chuẩn hóa để thứ tự khóa không thể tạo bản sao.
- Làm mới trước
refreshBefore. ChatGPT sẽ gọi lạievents/subscribetrước thời gian đó với cùng danh tính và con trỏ đã lưu cuối cùng của nó; bạn trả về một thời hạn hết hạn mới. Nếu một lần làm mới mang theo một khóa bí mật mới, hãy thay thế nó và ký bằng cả hai khóa trong một khoảng thời gian ngắn. Trả vềcursor: nullcho các sự kiện bạn không thể phát lại.
Xác minh callback trước bất kỳ dữ liệu nào
Trước khi gửi dữ liệu ứng dụng, hãy POST một nội dung xác minh đã ký với một thử thách mới, dùng một lần, có thời gian sống ngắn:
{ "type": "verification", "challenge": "a-single-use-random-value" }
Cung cấp cho nó một webhook-id duy nhất (ví dụ msg_verification_123) và các tiêu đề ký tương tự như một lần gửi. ChatGPT sẽ phản hồi {"challenge": "a-single-use-random-value"} với mã trạng thái 2xx. So sánh trong thời gian không đổi, sau đó kích hoạt gửi dữ liệu. Nếu thất bại, trả về lỗi JSON-RPC -32015 (CallbackEndpointError) với data.reason như challenge_failed hoặc timeout. Lưu trữ một lần vượt qua cho mỗi principal và URL trong một khoảng thời gian giới hạn để các lần làm mới không phải thử thách lại.
Thử thách tồn tại vì người đăng ký cung cấp khóa bí mật: nếu không có nó, bất kỳ ai cũng có thể hướng máy chủ của bạn đến URL của nạn nhân và làm ngập nó. Áp dụng cùng một biện pháp bảo mật cho mọi yêu cầu gửi đi: chỉ HTTPS, giải quyết và xác thực địa chỉ tại thời điểm kết nối, chặn các địa chỉ riêng tư, cục bộ và các địa chỉ không công khai khác, và không bao giờ theo dõi chuyển hướng.
Gửi và ký các sự kiện
Khi một sự kiện phù hợp xảy ra, POST một đối tượng sự kiện đến 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
}
Gửi Content-Type: application/json, webhook-id (bằng với eventId), webhook-timestamp (giây Unix tại thời điểm ký), webhook-signature, và X-MCP-Subscription-Id. Mã hóa nội dung một lần và gửi chính xác các byte đó, vì chữ ký bao gồm chúng.
Giữ dưới 256 KiB; đối với các bản ghi lớn, hãy gửi bản tóm tắt và hiển thị công cụ đọc. Xử lý văn bản do người dùng tạo dưới dạng dữ liệu và không bao giờ đặt hướng dẫn mô hình vào một tải trọng. Thử lại các lỗi tạm thời với thuật toán backoff lũy thừa giới hạn, giữ nguyên ID sự kiện và làm mới mỗi lần ký. Không thử lại 410 hoặc 413. Các sự kiện có thể đến không theo thứ tự, vì vậy hãy tạo các công cụ ghi có tính chất bảo lưu. Hướng dẫn thiết kế webhook đáng tin cậy bao gồm lịch trình thử lại.
Xác minh chữ ký Standard Webhooks
Theo đặc tả Standard Webhooks, nội dung đã ký là ${webhook-id}.${webhook-timestamp}.${body}, khóa HMAC-SHA256 là khóa bí mật đã giải mã base64 sau khi loại bỏ whsec_, và tiêu đề chứa một hoặc nhiều chữ ký v1,<base64> được phân tách bằng dấu cách. Bản nháp MCP yêu cầu các máy thu từ chối các dấu thời gian cũ hơn 5 phút và loại bỏ trùng lặp dựa trên webhook-id.
ChatGPT xác minh ở phía của nó, nhưng một bộ nhận nghiêm ngặt của riêng bạn sẽ là một oracle kiểm tra cục bộ tốt. Đoạn mã này chỉ sử dụng các hàm tích hợp của Node:
// receiver.mjs: bộ nhận Standard Webhooks nghiêm ngặt cho các thử nghiệm cục bộ (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);
Chạy nó với WEBHOOK_SECRET=whsec_... node receiver.mjs. Nó trả về 413 nếu lớn hơn 256 KiB và 401 cho chữ ký xấu hoặc đã hết hạn, phản hồi thử thách và ghi nhật ký mỗi webhook-id một lần. Chúng tôi đã kiểm tra verify() so với vectơ kiểm tra ký trong thư viện Standard Webhooks JavaScript. Để hiểu lý thuyết, hãy đọc xác minh chữ ký webhook.
Máy chủ của bạn chặn các địa chỉ riêng tư, vì vậy nó sẽ không gửi đến localhost theo mặc định. Bản nháp chỉ cho phép các mục tiêu không công khai khi được cấu hình rõ ràng: thêm một mục cho phép chỉ dành cho nhà phát triển, hoặc hiển thị bộ nhận thông qua một kênh.
Kiểm tra nó trước khi ChatGPT thực hiện
Đây là một lượt kiểm tra của Apidog cho các chế độ lỗi mà OpenAI liệt kê. Tạo một môi trường với MCP_URL, MCP_TOKEN, CALLBACK_URL và WEBHOOK_SECRET, và gửi mỗi cuộc gọi JSON-RPC dưới dạng POST tới {{MCP_URL}} với Authorization: Bearer {{MCP_TOKEN}} cộng với các tiêu đề mà liên kết HTTP có thể truyền trực tuyến yêu cầu: Accept: application/json, text/event-stream, MCP-Protocol-Version: 2026-07-28, và Mcp-Method được đặt thành phương thức. Mỗi phần thân cũng cần params._meta với io.modelcontextprotocol/protocolVersion và io.modelcontextprotocol/clientCapabilities. Các ví dụ của OpenAI bỏ qua nó, và một _meta bị thiếu cũng trả về -32602, vì vậy bước 3 có thể thành công vì lý do sai.
- Khám phá. Gửi
server/discovervà khẳng định$.result.capabilities.eventstồn tại và$.result.supportedVersionschứa2026-07-28. Gửievents/listvà khẳng địnhdeliverycủa mỗi sự kiện bao gồmwebhook. - Đăng ký bảo lưu. Gửi nội dung đăng ký ở trên bằng cách sử dụng
{{CALLBACK_URL}}và{{WEBHOOK_SECRET}}, với một bộ xử lý hậu kỳ Extract Variable lưu$.result.iddưới dạngSUB_ID. Gửi lại nó không thay đổi, sau đó với các khóaargumentsđược sắp xếp lại, và khẳng định$.result.idbằng{{SUB_ID}}cả hai lần. - Xác thực. Gửi một khóa bí mật giải mã ra dưới 24 byte, sau đó là một callback
http://. Bản nháp ánh xạ cả hai đến-32602(InvalidParams); khẳng định trên$.error.code. Một callback IP riêng tư cũng phải thất bại. - Thử thách xác minh. Tạo một điểm cuối giả của Apidog trả lời
{"challenge": "wrong"}và đăng ký với URL giả mạo đám mây của nó làm callback. Khẳng định$.error.codelà-32015và$.error.data.reasonlàchallenge_failed. Sau đó trỏCALLBACK_URLvào bộ nhận của bạn và xác nhận đăng ký thành công. - Tải trọng quá lớn. Kích hoạt một sự kiện có nội dung vượt quá 262.144 byte. Bên gửi của bạn phải từ chối nó; nếu một sự kiện lọt qua, bộ nhận sẽ trả lời
413, và nhật ký máy chủ của bạn sẽ chỉ hiển thị một lần thử, không phải thử lại. - Phát lại và giả mạo. Sao chép các tiêu đề và nội dung của một lần gửi đã ký từ nhật ký gửi đi của máy chủ của bạn vào một yêu cầu Apidog mới. Gửi lại ngay lập tức:
200, và không có dòng nhật ký thứ hai. Thay đổi một byte nội dung:401. Gửi lại sau 5 phút:401.
Lưu các bước dưới dạng kịch bản và chạy nó trong CI với Apidog CLI. Hướng dẫn kiểm tra máy chủ MCP bao gồm các cuộc gọi công cụ, và cách kiểm tra webhooks đi sâu hơn về các bộ nhận. Sau đó kết nối máy chủ với ChatGPT thông qua một plugin và chạy danh sách kiểm tra vòng đời của OpenAI.
Câu hỏi thường gặp
Sự kiện MCP là gì? Một tiện ích mở rộng MCP thử nghiệm cho phép máy chủ đẩy thông báo sự kiện đến một máy khách thay vì máy khách thăm dò. ChatGPT hỗ trợ chế độ webhook của nó tại 2026-07-28.
ChatGPT có hỗ trợ thăm dò hoặc truyền trực tuyến cho Sự kiện MCP không? Không. ChatGPT chỉ hỗ trợ gửi webhook và xác minh callback. Thăm dò, truyền trực tuyến, và các thông báo gap và terminated không được hỗ trợ.
Các gói ChatGPT nào nhận được Sự kiện MCP? Tổng kết DevDay của OpenAI cho biết tính năng này có sẵn cho tất cả các gói.
Ai tạo ra khóa bí mật ký? Người đăng ký. ChatGPT gửi một khóa bí mật whsec_ trong delivery.secret; máy chủ của bạn xác thực, lưu trữ và ký với nó, và không bao giờ tự tạo khóa của riêng mình.
Điều này khác với API Agents như thế nào? Sự kiện MCP đẩy dữ liệu từ máy chủ của bạn vào ChatGPT. API Agents của OpenAI chạy các tác nhân bạn xây dựng và báo cáo tiến độ của chúng bằng cách truyền trực tuyến hoặc webhooks.
Bước tiếp theo
Thêm "events": {} vào một máy chủ, hiển thị một sự kiện với một bộ lọc, và chạy sáu kiểm tra đối với một bộ nhận cục bộ trước khi kết nối ChatGPT. Tải xuống Apidog để giữ các kiểm tra đó dưới dạng một kịch bản chạy trên mỗi lần commit.
