DEV Community

Cover image for MCP Olayları Açıklandı: ChatGPT'nin Abone Olabileceği Webhook Gönderen MCP Sunucusu Kurulumu ve Testi
Tobias Hoffmann
Tobias Hoffmann

Posted on Originally published at apidog.com

MCP Olayları Açıklandı: ChatGPT'nin Abone Olabileceği Webhook Gönderen MCP Sunucusu Kurulumu ve Testi

MCP Etkinlikleri, MCP sunucunuzda bir değişiklik gerçekleştiği anda ChatGPT'ye güncelleme göndermenizi sağlar; araçların periyodik yoklama yapmasına gerek kalmaz. OpenAI DevDay'den bu yana, 29 Eylül 2026 itibarıyla ChatGPT tüm planlarda, 2026-07-28 protokol sürümündeki (MCP 2.0) önerilen MCP Etkinlikleri spesifikasyonunu destekler. Sunucunuzun üç yöntem (events/list, events/subscribe, events/unsubscribe) eklemesi, server/discover içinde events yeteneğini duyurması, geri arama doğrulamasını geçmesi ve tüm teslimatları Standart Webhook'lar HMAC ile imzalaması gerekir. ChatGPT yalnızca webhook teslimatını kabul eder. Bu kılavuzda mesaj biçimlerini, sunucunuzda uygulamanız gereken güvenlik kontrollerini ve uçtan uca test akışını ele alacağız. MCP'ye yeniyseniz, önce MCP'nin ne olduğunu öğrenin. JSON-RPC çağrılarını göndermek ve geri aramayı taklit etmek için Apidog kullanabilirsiniz.

Apidog'u bugün deneyin

MCP Etkinlikleri bir bakışta

Öğe ChatGPT'nin bekledikleri
Protokol MCP 2.0, sürüm 2026-07-28
Yetenek server/discover yeteneklerinde "events": {}
Yöntemler events/list, events/subscribe, events/unsubscribe; araçlarınızla aynı, kimliği doğrulanmış uç noktada
Teslimat Yalnızca webhook: yoklama, akış, gap veya terminated bildirimi yok
İmzalama Standart Webhook'lar HMAC-SHA256
Başlıklar webhook-id, webhook-timestamp, webhook-signature, X-MCP-Subscription-Id
Gizli anahtar ChatGPT tarafından sağlanan, whsec_ önekli ve base64 çözüldüğünde 24–64 bayt olan anahtar
Yük limiti 256 KiB (262.144 bayt), istek başına bir etkinlik
Abonelik kimliği Asıl, geri arama URL'si, etkinlik adı ve argümanlardan türetilmiş deterministik kimlik
Geri aramalar HTTPS, challenge ile doğrulanmış, özel adreslere ve yönlendirmelere kapalı

Kaynaklar: OpenAI'ın MCP Etkinlikleri kılavuzu ve MCP Etkinlikleri tasarım taslağı.

Neden yoklama yerine etkinlikler?

Yoklama kullanan bir araç, örneğin inceleme yorumlarını kontrol etmek için belirli aralıklarla API çağrısı yapar ve önceki sonuçla yeni sonucu karşılaştırır. Değişiklik yoksa gereksiz istek üretir; değişiklik varsa da bir sonraki yoklama turuna kadar gecikme yaşanır.

MCP Etkinlikleri bu akışı tersine çevirir: Değişikliği bilen sunucu, eşleşen olay gerçekleştiğinde güncellemeyi doğrudan gönderir. Bu yaklaşımın artıları ve eksileri, klasik webhook ve yoklama karşılaştırmasıyla aynıdır.

OpenAI'ın DevDay özetindeki örnekte kullanıcı, ChatGPT'den yeni proje görevlerini izlemesini ister. Yeni görev oluştuğunda ChatGPT bağlantılı belgeleri okuyabilir ve plan taslağı hazırlayabilir. Belgelerdeki diğer örnekler:

  • Bir kanaldaki hata raporlarını taslak çekme isteğine dönüştürmek: message.created, channel_id filtresiyle.
  • Bir belgeye inceleme yorumları uygulamak: comment.created, document_id filtresiyle.

Spesifikasyon, MCP Tetikleyiciler ve Etkinlikler Çalışma Grubu tarafından hazırlanmıştır ve hâlâ deneysel olarak etiketlenir. Bu nedenle uygulamanızı 2026-07-28 sürümüne sabitleyin. DevDay lansmanının geri kalanı için DevDay 2026 merkezine bakın.

Etkinlikleri duyurun ve tanımlayın

Önce server/discover yanıtındaki yeteneklere events ekleyin:

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

Ardından events/list yanıtında kullanılabilir etkinlikleri döndürün. Her etkinlik tanımı şunları içermelidir:

  • name
  • description
  • delivery: ["webhook"]
  • Abonelik filtreleri için inputSchema
  • Her teslimattaki data nesnesi için payloadSchema

Örneğin document_id gibi filtreleri inputSchema içinde tanımlayın. Etkinlik adlarını kararlı, açıklamalarını ise belirli tutun. Filtrelemeyi istemcide değil sunucuda uygulayın ve yalnızca bağlı hesabın görme yetkisi olan olayları listeleyin.

events/subscribe: istek ve yanıt

Kullanıcı ChatGPT'den bir kaynağı izlemesini istediğinde ChatGPT aşağıdaki gibi bir events/subscribe çağrısı yapar:

{
  "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
  }
}
Enter fullscreen mode Exit fullscreen mode

Aboneliği kabul etmeden önce şu kontrolleri uygulayın:

  1. Kullanıcının istenen etkinlik ve argümanlar için yetkisini doğrulayın.
  2. Etkinlik adını ve argümanları etkinlik tanımınızdaki şemaya göre doğrulayın.
  3. whsec_ önekli gizli anahtarın base64 çözüldüğünde 24–64 bayt olduğunu kontrol edin.
  4. Geri arama URL'sini doğrulayın.
  5. Aboneliği sahip, filtreler, URL, gizli anahtar ve son kullanma bilgisiyle saklayın.

Başarılı bir abonelik için aşağıdaki biçimde yanıt verin:

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

Abonelik yaşam döngüsünde üç temel kural vardır:

  • Deterministik kimlikler: id değerini kimliği doğrulanmış asıl, geri arama URL'si, etkinlik adı ve argümanlardan türetin. Tasarım taslağı, bu anahtarın kırpılmış SHA-256 özetini önerir.
  • İdempotent upsert işlemleri: Aynı kimlikle gelen tekrar abonelik isteği mevcut kaydı güncellemelidir. Yinelenmeleri önlemek için argümanları kanonik JSON biçiminde karşılaştırın.
  • refreshBefore öncesinde yenileme: ChatGPT, bu zamandan önce aynı kimlik ve son kaydedilen imleçle events/subscribe çağrısını yeniler. Yeni son kullanma zamanını döndürün. Yenilemede yeni gizli anahtar geldiyse anahtarı değiştirin ve kısa bir geçiş süresinde her iki anahtarla imzalayın. Olayları yeniden oynatamıyorsanız cursor: null döndürün.

Herhangi bir veriden önce geri aramayı doğrulayın

Uygulama verisi göndermeden önce yeni, tek kullanımlık ve kısa ömürlü bir challenge içeren imzalı POST isteği gönderin:

{ "type": "verification", "challenge": "a-single-use-random-value" }
Enter fullscreen mode Exit fullscreen mode

Bu istekte benzersiz bir webhook-id kullanın; örneğin msg_verification_123. Normal etkinlik teslimatlarıyla aynı imzalama başlıklarını ekleyin.

ChatGPT aşağıdaki gövdeyi 2xx durum koduyla döndürmelidir:

{ "challenge": "a-single-use-random-value" }
Enter fullscreen mode Exit fullscreen mode

Challenge değerini sabit zamanda karşılaştırın ve yalnızca eşleşirse teslimatı etkinleştirin. Doğrulama başarısız olursa -32015 kodlu CallbackEndpointError döndürün:

{
  "code": -32015,
  "message": "CallbackEndpointError",
  "data": {
    "reason": "challenge_failed"
  }
}
Enter fullscreen mode Exit fullscreen mode

timeout gibi uygun hata nedenlerini de data.reason içinde belirtin.

Doğrulama sonucunu asıl ve URL başına sınırlı süreyle önbelleğe alın. Böylece yenileme işlemlerinde tekrar challenge göndermeniz gerekmez.

Bu doğrulama adımı önemlidir: Abone gizli anahtarı sağladığı için, saldırganların sunucunuzu rastgele bir kurban URL'sine yönlendirip istek yağmuruna tutması engellenir.

Tüm giden webhook isteklerinde aşağıdaki SSRF korumalarını uygulayın:

  • Yalnızca HTTPS URL'lerine izin verin.
  • Hedef adresi bağlantı zamanında çözümleyip doğrulayın.
  • Özel, yerel ve diğer genel olmayan IP aralıklarını engelleyin.
  • HTTP yönlendirmelerini asla takip etmeyin.

Etkinlikleri teslim edin ve imzalayın

Eşleşen olay oluştuğunda geri arama URL'sine olay nesnesi olarak POST isteği gönderin:

{
  "eventId": "evt_456",
  "name": "comment.created",
  "timestamp": "2026-10-01T12:05:00Z",
  "data": {
    "document_id": "doc_123",
    "comment_id": "comment_456",
    "text": "Bu bölüme dağıtım tarihlerini ekleyebilir miyiz?",
    "url": "https://docs.example.com/doc_123#comment_456"
  },
  "cursor": null
}
Enter fullscreen mode Exit fullscreen mode

İstekle birlikte şu başlıkları gönderin:

Content-Type: application/json
webhook-id: evt_456
webhook-timestamp: <unix-saniye>
webhook-signature: v1,<signature>
X-MCP-Subscription-Id: sub_123
Enter fullscreen mode Exit fullscreen mode

Dikkat edilmesi gereken uygulama ayrıntıları:

  • Gövdeyi yalnızca bir kez serileştirin; imza bu tam baytları kapsadığı için gönderilen baytlar imzalanan baytlarla aynı olmalıdır.
  • Yükü 256 KiB altında tutun.
  • Büyük kayıtlar için tam içeriği göndermek yerine özet gönderin ve ayrıntılar için bir okuma aracı sunun.
  • Kullanıcı tarafından yazılmış metni yalnızca veri olarak işleyin; yük içine model talimatları yerleştirmeyin.
  • Geçici hatalarda kademeli üstel geri çekilme kullanın.
  • Yeniden denemelerde aynı olay kimliğini koruyun, ancak her denemeyi yeniden imzalayın.
  • 410 ve 413 yanıtlarında yeniden deneme yapmayın.
  • Olaylar sırasız ulaşabileceği için yazma araçlarınızı idempotent tasarlayın.

Yeniden deneme stratejileri için güvenilir webhook tasarım kılavuzuna bakın.

Standart Webhook imzasını doğrulayın

Standart Webhook'lar spesifikasyonuna göre imzalanan içerik şu biçimdedir:

${webhook-id}.${webhook-timestamp}.${body}
Enter fullscreen mode Exit fullscreen mode

HMAC-SHA256 anahtarı, whsec_ öneki kaldırıldıktan sonra base64 ile çözülen gizli anahtardır. webhook-signature başlığı, boşlukla ayrılmış bir veya daha fazla v1, imzası içerebilir.

MCP taslağı, alıcıların aşağıdaki kontrolleri yapmasını önerir:

  • 5 dakikadan eski zaman damgalarını reddetmek.
  • webhook-id değerine göre yinelenen teslimatları kaldırmak.

ChatGPT imzayı kendi tarafında doğrular. Ancak yerel bir alıcı yazmak, entegrasyon testi için iyi bir oracle sağlar. Aşağıdaki örnek yalnızca Node.js yerleşik modüllerini kullanır:

// receiver.mjs: yerel testler için sıkı Standart Webhook alıcısı (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);
}
Enter fullscreen mode Exit fullscreen mode

Alıcıyı çalıştırın:

WEBHOOK_SECRET=whsec_... node receiver.mjs
Enter fullscreen mode Exit fullscreen mode

Bu alıcı:

  • 256 KiB üzerindeki isteklerde 413 döndürür.
  • Geçersiz veya eski imzalarda 401 döndürür.
  • Doğrulama challenge'ını yankılar.
  • Her webhook-id değerini yalnızca bir kez günlüğe yazar.

verify() işlevini Standart Webhook JavaScript kütüphanesinin imzalama test vektörleriyle doğrulayın. Daha fazla ayrıntı için webhook imza doğrulaması kılavuzunu inceleyin.

Sunucunuz özel adresleri engellediği için varsayılan olarak localhost hedeflerine teslimat yapmamalıdır. Taslak, genel olmayan hedeflere yalnızca açıkça yapılandırıldığında izin verir. Geliştirme için bir izin listesi kullanın veya alıcıyı güvenli bir tünel aracılığıyla dışarı açın.

ChatGPT'ye bağlamadan önce test edin

OpenAI'ın belirttiği hata modlarını doğrulamak için bir Apidog test akışı oluşturun.

Önce şu ortam değişkenlerini tanımlayın:

MCP_URL
MCP_TOKEN
CALLBACK_URL
WEBHOOK_SECRET
Enter fullscreen mode Exit fullscreen mode

Her JSON-RPC çağrısını {{MCP_URL}} adresine POST olarak gönderin. Yetkilendirme başlığı:

Authorization: Bearer {{MCP_TOKEN}}
Enter fullscreen mode Exit fullscreen mode

Akışlanabilir HTTP bağlamasının gerektirdiği başlıkları ekleyin:

Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: <çağrılan-yöntem>
Enter fullscreen mode Exit fullscreen mode

Her gövdenin params._meta alanında ayrıca aşağıdaki değerler bulunmalıdır:

  • io.modelcontextprotocol/protocolVersion
  • io.modelcontextprotocol/clientCapabilities

Bu alanların tanımı için MCP params._meta spesifikasyonuna bakın. OpenAI örnekleri bunu atlayabilir; ancak eksik _meta, -32602 hatası döndürebilir ve testinizin yanlış nedenle geçmesine yol açabilir.

  1. Keşif

    server/discover gönderin. $.result.capabilities.events alanının bulunduğunu ve $.result.supportedVersions listesinin 2026-07-28 içerdiğini doğrulayın. Ardından events/list gönderin ve her olayın delivery alanında webhook bulunduğunu kontrol edin.

  2. İdempotent abonelik

    Yukarıdaki abonelik gövdesini {{CALLBACK_URL}} ve {{WEBHOOK_SECRET}} ile gönderin. $.result.id değerini SUB_ID olarak kaydetmek için bir değişken çıkarma son işlemcisi ekleyin. İsteği değiştirmeden yeniden gönderin. Sonra arguments anahtarlarının sırasını değiştirerek tekrar gönderin. Her iki durumda da $.result.id değerinin {{SUB_ID}} ile aynı olduğunu doğrulayın.

  3. Geri arama girdisi doğrulaması

    Base64 çözüldüğünde 24 bayttan kısa bir gizli anahtar gönderin. Ardından http:// ile başlayan bir geri arama URL'si deneyin. Taslak, iki durumu da -32602 (InvalidParams) ile eşler. $.error.code alanını doğrulayın. Özel IP adresine işaret eden geri arama URL'si de başarısız olmalıdır.

  4. Challenge doğrulaması

    {"challenge": "wrong"} yanıtını dönen bir Apidog sahte uç noktası oluşturun. Bu bulut sahte URL'sini geri arama olarak kullanarak abone olun. $.error.code değerinin -32015, $.error.data.reason değerinin ise challenge_failed olduğunu doğrulayın. Ardından CALLBACK_URL değerini yerel alıcınıza yönlendirin ve aboneliğin başarıyla tamamlandığını kontrol edin.

  5. Büyük yük

    Gövdesi 262.144 baytı aşan bir olay tetikleyin. Göndericiniz bu olayı reddetmelidir. Eğer istek alıcıya ulaşırsa alıcı 413 döndürmeli; sunucu günlüklerinde tek deneme görünmeli ve yeniden deneme yapılmamalıdır.

  6. Yeniden oynatma ve kurcalama

    İmzalı teslimatın başlıklarını ve gövdesini sunucunuzun giden günlüklerinden yeni bir Apidog isteğine kopyalayın. İsteği hemen yeniden gönderin: 200 dönmeli, ancak ikinci günlük satırı oluşmamalıdır. Gövdedeki bir baytı değiştirin: 401 dönmelidir. Aynı isteği 5 dakika sonra tekrar gönderin: yine 401 dönmelidir.

Bu adımları bir senaryo olarak kaydedin ve Apidog CLI ile CI ortamında çalıştırın. Araç çağrıları için MCP sunucu test playbook'unu, alıcı testleri için ise webhook'ları nasıl test edeceğiniz rehberini kullanın. Ardından sunucunuzu bir eklenti aracılığıyla ChatGPT'ye bağlayın ve OpenAI'ın yaşam döngüsü kontrol listesini uygulayın.

SSS

MCP Etkinlikleri nelerdir?

Bir sunucunun, istemcinin yoklama yapması yerine olay bildirimlerini istemciye göndermesini sağlayan deneysel bir MCP uzantısıdır. ChatGPT, 2026-07-28 sürümünde webhook modunu destekler.

ChatGPT, MCP Etkinlikleri için yoklamayı veya akışı destekliyor mu?

Hayır. ChatGPT yalnızca webhook teslimatını ve geri arama doğrulamasını destekler. Yoklama, akış, gap ve terminated bildirimleri desteklenmez.

Hangi ChatGPT planları MCP Etkinliklerini kullanabilir?

OpenAI'ın DevDay özeti, özelliğin tüm planlarda kullanılabildiğini belirtir.

İmzalama gizli anahtarını kim oluşturur?

Abone. ChatGPT, delivery.secret içinde whsec_ önekli gizli anahtarı gönderir. Sunucunuz bu değeri doğrular, saklar ve webhook'ları bununla imzalar; anahtarı kendisi üretmez.

Bu yaklaşım Agents API'den nasıl farklıdır?

MCP Etkinlikleri, sunucunuzdan ChatGPT'ye veri gönderir. OpenAI Agents API ise oluşturduğunuz ajanları çalıştırır ve ilerlemelerini akış veya webhook aracılığıyla bildirir.

Sonraki adım

Sunucunuza "events": {} ekleyin, filtrelenmiş tek bir etkinlik tanımlayın ve ChatGPT'ye bağlanmadan önce yerel alıcınıza karşı altı kontrolü çalıştırın. Bu kontrolleri her taahhütte çalışan bir senaryo olarak saklamak için Apidog'u indirin.

Top comments (0)