ข้ามไปยังเนื้อหา

Backward Compatibility and Protocol Eras

revision 2026-07-28 ทำให้ MCP เป็น stateless และนั่นแบ่งประวัติของ protocol ออกเป็นสองยุค คำที่ spec ใช้เอง:

  • Modern — version ที่ส่ง protocol version, ตัวตน และ capability เป็น metadata ต่อ request: 2026-07-28 และหลังจากนั้น
  • Legacy — version ที่สร้าง session ด้วย handshake initialize: 2025-11-25 และก่อนหน้า
  • Dual-era — implementation ที่รองรับทั้งคู่

server ส่วนใหญ่ที่ deploy อยู่ทุกวันนี้ยังเป็น legacy ดังนั้น client ที่พูดได้แค่ modern จะใช้มันไม่ได้ และ legacy client ก็ใช้ server ที่เป็น modern อย่างเดียวไม่ได้ การรู้วิธีตรวจจับและเชื่อมสองยุคจึงเป็นทักษะที่ใช้จริง ไม่ใช่เกร็ดความรู้

Legacy (2025-11-25 และก่อนหน้า)Modern (2026-07-28)
request initialize แล้วตามด้วย notifications/initialized; negotiate version และ capability ครั้งเดียวversion และ capability อยู่ใน _meta ทุก request; server/discover เป็น optional
header Mcp-Session-Id ที่ server ออกให้ จบด้วย HTTP DELETEไม่มี session ระดับ protocol; ใช้ handle ที่ชัดเจนใน argument ของ tool
HTTP GET เปิด SSE stream แยกสำหรับ message ที่ server เป็นฝ่ายเริ่มไม่มี GET; POST subscriptions/listen return stream ที่เปิดค้างยาว
server ส่ง sampling/createMessage, elicitation/create, roots/list เป็น request บน SSE ได้server ฝังมันไว้ใน InputRequiredResult (MRTR)
SSE resume ได้ด้วย Last-Event-IDresume ไม่ได้; ส่ง request ใหม่
resources/subscribe / unsubscribe, ping, logging/setLevel, notifications/roots/list_changedถูกถอดออก; ใช้ filter resourceSubscriptions และ logLevel ต่อ request ใน _meta
result ไม่มี resultTypeทุก result มี resultType; client ถือว่าตัวที่ไม่มีเป็น "complete"

ก่อนหน้านั้น 2024-11-05 ใช้ transport แยกชื่อ HTTP+SSE (GET stream ที่ประกาศ POST endpoint ผ่าน event endpoint) มันถูก deprecate ตั้งแต่ 2025-03-26 และเป็น Deprecated อย่างเป็นทางการภายใต้ feature lifecycle policy — อย่านำมาใช้

ไม่มี HTTP status code ให้อิง dual-era client จึง probe ด้วย server/discover โดยใส่ version ยุคใหม่ที่ต้องการใน _meta

sequenceDiagram
  participant C as Dual-era client
  participant S as Server
  C->>S: server/discover (protocolVersion 2026-07-28)
  alt DiscoverResult
    S-->>C: supportedVersions, capabilities
    Note over C: Modern เลือก version ที่รองรับ อยู่ในโหมด modern ต่อ
  else Recognized modern error (e.g. -32022)
    S-->>C: UnsupportedProtocolVersionError พร้อมรายการ supported
    Note over C: Modern retry ด้วย version ในรายการ ห้าม fall back
  else Other error or timeout
    S-->>C: -32601 / -32602 / เงียบ
    Note over C: Legacy ส่ง initialize แล้วทำต่อในโหมด legacy
  end
probe ตรวจยุคบน stdio

การ fallback ต้องไม่ ผูกกับ code ตัวใดตัวหนึ่ง legacy server ตอบ request ที่ไม่รู้จักก่อน initialize ด้วย error แบบ implementation-defined — มักเป็น -32601 หรือ -32602 — หรือไม่ตอบเลย แนะนำให้ probe แม้กับ client ที่เป็น modern อย่างเดียว: legacy server บางตัวไม่ตรวจว่า request มาหลัง initialize หรือไม่ และจะประมวลผล tools/call ที่คลุมเครือระหว่างยุคด้วย semantics แบบ legacy การ probe เปลี่ยนเรื่องนั้นให้เป็นความล้มเหลวที่คาดเดาได้ซึ่งคุณรายงานให้ผู้ใช้ทราบได้

บน Streamable HTTP client แค่ลองส่ง request แบบ modern ก่อน legacy server ไม่รู้ว่าจะทำอะไรกับมันและมักตอบ 400 Bad Request — แต่ server แบบ modern ก็ตอบ 400 เหมือนกันสำหรับ UnsupportedProtocolVersionError, MissingRequiredClientCapabilityError และความล้มเหลวจากการ validate header ดังนั้นเมื่อได้ 400 client ควร ตรวจ body ก่อนตัดสินใจ:

  • body มี modern JSON-RPC error ที่รู้จัก → server เป็น modern retry ด้วย version ที่ประกาศไว้หรือแก้ request ห้าม fall back
  • body ว่างหรือไม่ใช่ modern error ที่รู้จัก → fall back ไป initialize แล้วทำต่อด้วย revision แบบ legacy

ถ้าจะรองรับ transport HTTP+SSE ที่ deprecate แล้วด้วย client ที่ได้ 400, 404 หรือ 405 โดยไม่มี modern error body จะส่ง GET ไปที่ URL; ถ้า SSE stream เปิดขึ้นและ event แรกคือ endpoint แสดงว่าเป็น server รุ่น 2024-11-05

ยุคเป็นคุณสมบัติของ server ไม่ใช่ของ request cache ผลไว้ตลอดอายุของ process (stdio) หรือ origin (HTTP) จะเก็บข้าม restart ของ configuration เดิมก็ได้ และ probe ใหม่ถ้าสมมติฐานที่ cache ไว้ล้มเหลวในภายหลัง

ClientServerผลลัพธ์
ModernModernใช้ได้ server/discover เป็น optional; version ไม่ตรงกันโผล่เป็น -32022 แล้ว client retry
ModernLegacyล้มเหลว server อาจปฏิเสธ, เงียบ หรือประมวลผล method ที่คลุมเครือด้วย semantics แบบ legacy probe บน stdio เพื่อให้ล้มเหลวแบบคาดเดาได้
Dual-eraModernใช้ได้ probe ได้ DiscoverResult หรือ modern error; client อยู่ในโหมด modern
Dual-eraLegacyใช้ได้ probe ได้ error ที่ไม่ใช่ modern หรือ timeout; client fall back ไป initialize (และอาจไปถึง HTTP+SSE)
LegacyModernล้มเหลว stdio: initialize เป็น method ที่ไม่รู้จักและไม่มี _meta ถูกปฏิเสธด้วย error แบบ implementation-defined HTTP: ไม่มี header บังคับ → 400 legacy client ไม่มีทาง fall forward
LegacyDual-eraใช้ได้ server ตอบ initialize และให้บริการตาม revision legacy ที่ negotiate ได้
LegacyLegacyใช้ได้ตาม revision แบบ legacy

อ่านมันเป็นกฎการออกแบบ: เพื่อเข้าถึง peer ได้มากที่สุด client ควรเป็น dual-era และ server ควรเป็น dual-era ตราบเท่าที่ legacy client ยังสำคัญกับคุณ

ถ้าคุณเลือกไม่รองรับ legacy client ก็ควรปฏิเสธอย่างสุภาพ:

  • HTTP GET หรือ DELETE มาที่ MCP endpoint → 405 Method Not Allowed
  • header Mcp-Session-Id → เพิกเฉย; ห้ามออกหรือส่ง session ID กลับ
  • header Last-Event-ID → เพิกเฉย; stream resume ไม่ได้
  • request initialize บน transport ใดก็ตาม → ปฏิเสธ และ ระบุ protocol version ที่คุณรองรับ ใน error message legacy client ไม่มีกลไก fall forward ดังนั้น message นั้นอาจเป็นข้อมูล diagnostic เดียวที่ผู้ใช้จะได้เห็น

dual-era server เลือกพฤติกรรมจากวิธีที่ client เปิด: request ที่พก _meta แบบ modern จะได้รับบริการแบบ stateless; request initialize เลือก semantics แบบ legacy ที่ผูกกับ process ของ stdio หรือ session ของ HTTP ทั้งสองยุครันพร้อมกันบน endpoint เดียวได้

ใน TypeScript SDK เรื่องนี้เป็น option เดียวบน handler:

import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
const buildServer = () => new McpServer({ name: 'notes', version: '1.0.0' });
// default: also serve legacy clients, statelessly
const handler = createMcpHandler(buildServer);
// modern only: reject legacy traffic
const strict = createMcpHandler(buildServer, { legacy: 'reject' });

factory ตัวเดียวกันรองรับทั้งสองยุค คุณจึงลงทะเบียน tool ครั้งเดียว MCP Inspector ก็ negotiate ทั้งสองยุคได้ ทำให้มันเป็นวิธีที่เร็วที่สุดในการยืนยันว่า server รันอยู่ในโหมดไหนจริง ๆ — ดู testing and debugging

พฤติกรรมคู่ไหนที่บ่งบอกว่า server เป็น legacy (2025-11-25 และก่อนหน้า)?
บน HTTP request แบบ modern ได้ 400 อะไรเป็นตัวตัดสินว่า client จะ fall back ไป initialize หรือไม่?
legacy client คุยกับ server ที่เป็น modern อย่างเดียว จะเกิดอะไรขึ้น?
ทำไม probe บน stdio ถึงไม่ควรผูกกับ error code ตัวเดียว?