SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

เจาะลึก MCP server แบบ stateless เปลี่ยนไปอย่างไรบ้าง

อัปเดต MCP revision 2026-07-28 ยกเลิกการทำ initialize handshake และระบบ session เดิม ส่งผลโดยตรงต่อการตั้งค่า reverse proxy การทำ health checks การจัดการ timeout และระบบยืนยันตัวตน

MCP server แบบ stateless คืออะไร

MCP server แบบ stateless จะไม่เก็บสถานะของไคลเอนต์แต่ละรายไว้ระหว่างการร้องขอ ทุกคำขอจะประกอบด้วยเวอร์ชันของโปรโตคอล, ขีดความสามารถของไคลเอนต์ และข้อมูลรับรองที่เซิร์ฟเวอร์จำเป็นต้องใช้ในการตอบกลับ ดังนั้นกระบวนการใดๆ บนเครื่องใดก็ตามจึงสามารถตอบสนองต่อคำขอใดๆ ได้ MCP (Model Context Protocol ซึ่งเป็นรูปแบบการสื่อสารที่เอเจนต์ใช้เพื่อเข้าถึงเครื่องมือต่างๆ) ได้กำหนดให้สิ่งนี้เป็นกฎใน revision 2026-07-28 ซึ่งได้ยกเลิกการทำ handshake แบบ initialize และเซสชัน HTTP ที่เคยอยู่เบื้องล่างออกไป เนื้อหาทั้งหมดในที่นี้มุ่งเน้นไปที่ฝั่งเซิร์ฟเวอร์ของโปรโตคอลดังกล่าว ดังนั้นหากคุณยังใหม่กับฝั่งเอเจนต์ เส้นทางการเรียนรู้ AI agents แบบเป็นขั้นตอน จะครอบคลุมถึงลูปการตัดสินใจเรียกใช้เครื่องมือก่อนที่รายละเอียดเกี่ยวกับ HTTP เหล่านี้จะมีความสำคัญ

นั่นคือประเด็นสำคัญทั้งหมดในเชิงปฏิบัติ เซิร์ฟเวอร์ที่ไม่เก็บสถานะของไคลเอนต์แต่ละรายสามารถวางไว้หลัง load balancer ทั่วไปโดยไม่ต้องใช้ session affinity สามารถรีสตาร์ทระหว่างการ deploy ได้โดยไม่ทำให้ไคลเอนต์หลุด และสามารถรันเป็น 4 กระบวนการที่เหมือนกันแทนที่จะเป็นกระบวนการเดียวได้ ในขณะที่เซิร์ฟเวอร์แบบที่เน้นเซสชันไม่สามารถทำสิ่งเหล่านี้ได้หากไม่มีกลไกเพิ่มเติม

Model Context Protocol เป็นโปรโตคอลแบบ stateless: ข้อมูลทั้งหมดที่จำเป็นในการประมวลผลคำขอจะถูกบรรจุอยู่ในตัวคำขอนั้นเอง เซิร์ฟเวอร์จะประมวลผลแต่ละคำขออย่างเป็นอิสระต่อกัน โดยไม่ควรอนุมานสถานะใดๆ จากคำขอก่อนหน้า แม้จะเป็นคำขอที่อยู่บนการเชื่อมต่อหรือสตรีมเดียวกันก็ตาม

Stateless ไม่ได้หมายความว่าเซิร์ฟเวอร์จะไม่จัดเก็บข้อมูลใด ๆ ฐานข้อมูล, queue และ cache ของคุณยังคงอยู่ทั้งหมด ความหมายคือ protocol ไม่มี state บน connection ดังนั้นเซิร์ฟเวอร์ต้องไม่ถือว่า connection, process หรือ socket ที่เปิดอยู่เป็นตัวแทนของ “client รายนี้ที่อยู่ระหว่างการสนทนา” การแยกความหมายนี้เห็นได้ง่ายที่สุดจากแอปที่จัดการข้อมูลของตนเองอยู่แล้ว: MCP server แบบ read-only ของ openGym ตอบคำถามเกี่ยวกับประวัติการฝึกที่จัดเก็บอยู่ในฐานข้อมูลของแอปเอง และข้อมูลดังกล่าวไม่ขึ้นอยู่กับ connection ที่ request ใด request หนึ่งเข้ามาเลย

สิ่งที่ถูกนำออกในรุ่นปรับปรุง 2026-07-28

2026-07-28 คือรุ่นปรับปรุงปัจจุบันของข้อกำหนด ณ เดือนสิงหาคม 2026 เมื่อเปรียบเทียบกับ 2025-11-25 รุ่นนี้ได้นำสิ่งที่เคยมีอยู่เพื่อรองรับเซสชันออกไป 5 รายการ ดังนี้:

  • คำขอ initialize และการแจ้งเตือน notifications/initialized โดยไม่มีการทำ handshake ใดๆ ทั้งสิ้น (SEP-2575)
  • ส่วนหัว Mcp-Session-Id และการยุติเซสชันด้วย HTTP DELETE (SEP-2567)
  • สตรีม HTTP GET แบบแยกส่วนที่เซิร์ฟเวอร์ใช้ส่งการแจ้งเตือน โดยถูกแทนที่ด้วย subscriptions/listen ซึ่งเป็น POST ปกติที่การตอบกลับจะเป็นสตรีมแบบ long-lived
  • ความสามารถในการกลับมาทำงานต่อของสตรีม SSE (server-sent events) ส่วนหัว Last-Event-ID และ ID ประจำเหตุการณ์ถูกนำออกไป ดังนั้นหากสตรีมขาดหายไป คำขอที่กำลังดำเนินการอยู่จะสูญหาย และไคลเอนต์ต้องส่งคำขอใหม่พร้อม ID คำขอใหม่
  • ping, logging/setLevel และ notifications/roots/list_changed โดยระดับของ log จะกลายเป็นฟิลด์ประจำคำขอ คือ io.modelcontextprotocol/logLevel ใน _meta

มีการเพิ่มหนึ่งเมธอดที่เซิร์ฟเวอร์ทุกตัวต้องรองรับ คือ server/discover ซึ่งจะส่งคืนเวอร์ชันโปรโตคอลที่รองรับ ความสามารถ และข้อมูลระบุตัวตนของเซิร์ฟเวอร์ในการเรียกครั้งเดียว นี่เป็นสิ่งที่ใกล้เคียงกับการทำ handshake มากที่สุดที่ยังคงเหลืออยู่ และไคลเอนต์จะเรียกใช้หรือไม่ก็ได้

เหตุผลที่ session transport ใช้งานในสภาพแวดล้อม production ได้ยาก

ใน 2025-11-25 และเวอร์ชันก่อนหน้า เซิร์ฟเวอร์สามารถสร้าง session ID ขึ้นมาในขั้นตอน initialization และส่งกลับไปใน header Mcp-Session-Id บน InitializeResult จากนั้นไคลเอนต์จะต้องส่ง header ดังกล่าวในทุกคำขอที่ตามมา เวอร์ชันของโปรโตคอลที่ตกลงกันไว้และความสามารถของไคลเอนต์จะถูกเก็บไว้ในหน่วยความจำของเซิร์ฟเวอร์โดยอ้างอิงจาก ID นั้น ซึ่งทางเลือกแต่ละอย่างล้วนมีต้นทุนในการดำเนินงาน

  • การรีสตาร์ทจะทำให้ตาราง session หายไปทั้งหมด ข้อกำหนดระบุให้เซิร์ฟเวอร์ต้องตอบกลับคำขอใดก็ตามที่ใช้ session ID ที่ไม่มีอยู่จริงด้วย 404 Not Found และกำหนดให้ไคลเอนต์ต้องเริ่มต้นใหม่ด้วย InitializeRequest ใหม่ การ deploy ทุกครั้งจึงกลายเป็นการเชื่อมต่อใหม่สำหรับไคลเอนต์ทุกรายที่เชื่อมต่ออยู่
  • replica ตัวที่สองจะไม่ทราบข้อมูล session ของ replica ตัวแรก การขยายระบบ (scaling out) จึงหมายถึงการต้องทำ sticky routing ที่ load balancer หรือต้องมีที่เก็บ session ส่วนกลางที่ทุก replica ต้องอ่านในทุกคำขอ
  • ตาราง session เป็นหน่วยความจำที่เพิ่มขึ้นตามจำนวนไคลเอนต์ที่ไม่ได้ใช้งาน DELETE เป็นสิ่งที่เลือกทำได้ (optional) และไคลเอนต์ที่ปิดการเชื่อมต่อโดยไม่ส่งข้อมูลดังกล่าวจะทิ้งรายการค้างไว้ในหน่วยความจำ
  • ผลลัพธ์ของรายการอาจแตกต่างกันไปในแต่ละการเชื่อมต่อ ทำให้การทำ caching หน้าเซิร์ฟเวอร์ไม่ปลอดภัย

การยกเลิกการใช้ session ช่วยขจัดปัญหาทั้งสี่ประการนี้ไปพร้อมกัน นี่คือการเปลี่ยนแปลงที่ควรทำความเข้าใจก่อนที่คุณจะปรับแต่งค่าคอนฟิกใดๆ

สิ่งที่ทุกคำขอต้องมีในปัจจุบัน

การส่ง POST ไปยัง MCP endpoint แต่ละครั้งจะเป็นอิสระต่อกัน เวอร์ชันของโปรโตคอลและความสามารถของไคลเอนต์จะถูกส่งไปในส่วนเนื้อหาของคำขอภายใต้ _meta และฟิลด์ที่เลือกจะถูกคัดลอกไปยัง HTTP headers เพื่อให้ตัวกลางสามารถใช้ข้อมูลดังกล่าวในการกำหนดเส้นทาง (route) ได้โดยไม่ต้องแยกวิเคราะห์ JSON

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
Authorization: Bearer <access token>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {"location": "Seattle, WA"},
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {"name": "ExampleClient", "version": "1.0.0"},
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

io.modelcontextprotocol/protocolVersion และ io.modelcontextprotocol/clientCapabilities เป็นสิ่งที่จำเป็นต้องมีในทุกคำขอ ส่วน clientInfo ไม่จำเป็นต้องมี แต่ไคลเอนต์ควรส่งมาด้วย คำขอที่ขาดฟิลด์บังคับถือว่ามีรูปแบบไม่ถูกต้อง ดังนั้นเซิร์ฟเวอร์ต้องปฏิเสธคำขอดังกล่าวด้วย JSON-RPC error -32602 และ HTTP 400 Bad Request

Header Mcp-Method เป็นสิ่งที่จำเป็นต้องมีในทุกคำขอ และ Mcp-Name จำเป็นต้องมีใน tools/call, resources/read และ prompts/get ค่าใน header ต้องตรงกับข้อมูลในส่วนเนื้อหา และเซิร์ฟเวอร์ที่ประมวลผลเนื้อหาต้องปฏิเสธหากข้อมูลไม่ตรงกันด้วย 400 Bad Request และรหัสข้อผิดพลาด -32020, HeaderMismatch กฎนี้มีไว้เนื่องจาก load balancer ที่กำหนดเส้นทางโดยใช้ header และเซิร์ฟเวอร์ที่ดำเนินการตามเนื้อหาเป็นแหล่งข้อมูลที่แตกต่างกัน หากคุณทำการกำหนดเส้นทางหรือจำกัดอัตรา (rate-limit) โดยใช้ header เหล่านี้ ให้ตรวจสอบ MCP-Protocol-Version ก่อน เนื่องจากเวอร์ชันก่อนหน้านี้ไม่เคยตรวจสอบความถูกต้องของ header เทียบกับเนื้อหา ดังนั้นในเวอร์ชันเหล่านั้นค่าใน header จึงไม่น่าเชื่อถือ

ความไม่สอดคล้องกันของเวอร์ชันในปัจจุบันถือเป็นข้อผิดพลาดปกติในระดับคำขอ แทนที่จะเป็นการล้มเหลวของการทำ handshake เซิร์ฟเวอร์ที่ไม่รองรับเวอร์ชันที่ร้องขอจะตอบกลับด้วย 400 Bad Request พร้อมข้อผิดพลาด -32022, UnsupportedProtocolVersion และระบุรายการเวอร์ชันที่รองรับไว้ใน data.supported จากนั้นไคลเอนต์จะเลือกหนึ่งเวอร์ชันจากรายการดังกล่าวแล้วลองส่งคำใหม่อีกครั้ง

สถานะหายไปไหน: โทเค็น, เคอร์เซอร์ และการสมัครรับข้อมูล

สถานะไม่ได้หายไปไหน แต่ย้ายไปอยู่ในจุดที่คุณสามารถมองเห็นและบันทึก log ได้

ข้อมูลประจำตัวจะถูกส่งไปพร้อมกับทุกคำขอ เนื่องจากไม่มีเซสชันสำหรับผูกตัวตนเข้ากับคำขอ ดังนั้น access token จึงต้องติดไปกับทุกการเรียก HTTP และจะถูกตรวจสอบทุกครั้ง รายละเอียดอยู่ในส่วนการยืนยันตัวตนด้านล่าง

เคอร์เซอร์ต้องระบุตำแหน่งของตัวเอง การแบ่งหน้า (pagination) บน tools/list, resources/list, prompts/list และ resources/templates/list จะใช้สตริงเคอร์เซอร์แบบ opaque ซึ่งไคลเอนต์ห้ามทำการแยกวิเคราะห์หรือแก้ไขโดยเด็ดขาด ในเซิร์ฟเวอร์แบบกระบวนการเดียว (single-process) การเก็บค่า offset ไว้ในหน่วยความจำโดยอ้างอิงจากเซสชันเป็นเรื่องปกติ แต่เมื่อไม่มีเซสชัน เคอร์เซอร์จะต้องมีข้อมูลเพียงพอให้ replica ใดก็ตามสามารถดำเนินการรายการต่อได้ ดังนั้นให้เข้ารหัสตำแหน่งไว้ภายในเคอร์เซอร์แล้วทำการลงลายเซ็น (sign) หรือเก็บไว้ในที่จัดเก็บข้อมูลที่ทุก replica เข้าถึงร่วมกัน หากเคอร์เซอร์ไม่ถูกต้องควรส่งคืน -32602 การลงลายเซ็นเป็นสิ่งจำเป็นเพราะเคอร์เซอร์แบบ opaque ยังคงเป็นอินพุตที่ไคลเอนต์ส่งมา ซึ่งโค้ดของคุณต้องถอดรหัสและเชื่อถือ

การสมัครรับข้อมูล (subscriptions) ขึ้นอยู่กับคำขอ ไม่ใช่การเชื่อมต่อ ไคลเอนต์ที่ต้องการรับการแจ้งเตือนการเปลี่ยนแปลงจะส่ง subscriptions/listen พร้อมตัวกรองที่ระบุประเภทที่ต้องการ ได้แก่ toolsListChanged, promptsListChanged, resourcesListChanged และ resourceSubscriptions เซิร์ฟเวอร์จะตอบกลับด้วย notifications/subscriptions/acknowledged และเปิด stream การตอบกลับนั้นค้างไว้ หาก stream หลุด เซิร์ฟเวอร์จะไม่เก็บข้อมูลใดๆ ไว้ และไคลเอนต์จะต้องส่ง subscriptions/listen ใหม่อีกครั้งเพื่อเรียกข้อมูลคืน

สถานะของแอปพลิเคชันที่ข้ามการเรียก (cross-call) จะกลายเป็น handle ที่ชัดเจน เมื่อเซิร์ฟเวอร์จำเป็นต้องจดจำข้อมูลระหว่างการเรียกจริงๆ คำตอบตามข้อกำหนดคือการใช้ตัวระบุที่เซิร์ฟเวอร์สร้างขึ้นแล้วส่งกลับไปเป็นอาร์กิวเมนต์ปกติของเครื่องมือ ข้อมูลนี้จะปรากฏใน schema ของเครื่องมือ สามารถบันทึก log ได้ และไม่ได้ถูกอนุมานจากการเชื่อมต่อ เซิร์ฟเวอร์ที่มีข้อมูลเฉพาะของผู้ใช้เบื้องหลัง เช่น เซิร์ฟเวอร์อีเมล MCP ที่โฮสต์เอง จะใช้รูปแบบนี้แทนการใช้เซสชัน โดยตัวระบุกล่องจดหมายหรือฉบับร่างจะเป็นอาร์กิวเมนต์ของเครื่องมือ ทำให้ replica ใดก็ตามสามารถรับช่วงการเรียกถัดไปได้ เครื่องมือจำนวนมากไม่จำเป็นต้องใช้ handle เลย เช่น เครื่องมือค้นหาที่ทำงานบน SearXNG instance ของคุณเอง ซึ่งรับคำค้นหาและส่งผลลัพธ์กลับ โดยไม่มีข้อมูลใดที่ต้องใช้ในการเรียกครั้งถัดไปและไม่จำเป็นต้องสนใจว่า replica ใดเป็นผู้ตอบคำถามนั้น

การปรับใช้: reverse proxy, การตั้งค่า timeout และ health check

MCP endpoint เป็น path หนึ่งที่รับคำขอแบบ POST โดยทั่วไปทราฟฟิกส่วนใหญ่จะเป็นคำขอสั้นๆ และการตอบกลับเป็น JSON ซึ่ง proxy ทั่วไปสามารถจัดการได้ แต่ข้อยกเว้นคือการตอบกลับแบบ streaming ซึ่งค่าเริ่มต้นของ proxy มักจะขัดขวางการทำงาน นี่คือส่วนที่ต้องปรับเปลี่ยนเมื่อย้ายจากการทดสอบบนแล็ปท็อปไปสู่ การรัน MCP server บน VPS

location /mcp {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;
}

proxy_buffering off มีความสำคัญเนื่องจาก nginx จะทำ buffering การตอบกลับที่ผ่าน proxy โดยค่าเริ่มต้น ซึ่งจะกักเก็บ SSE events ไว้จนกว่า buffer จะเต็มหรือการตอบกลับสิ้นสุดลง ข้อกำหนดระบุให้เซิร์ฟเวอร์ส่ง X-Accel-Buffering: no ในการตอบกลับแบบ SSE ซึ่ง nginx จะปฏิบัติตาม header นั้น ดังนั้นเซิร์ฟเวอร์ที่ถูกต้องจะแจ้งให้ proxy ทราบถึงสิ่งที่ควรทำด้วยตัวเอง อย่างไรก็ตาม ควรตั้งค่า directive นี้ไว้ด้วยเพราะเป็นส่วนที่คุณควบคุมได้

proxy_read_timeout มีค่าเริ่มต้นที่ 60 วินาที ดังนั้น subscriptions/listen stream ที่ไม่มีการเคลื่อนไหวเกินกว่านั้นจะถูก nginx ปิดการเชื่อมต่อ ไม่ใช่โดยเซิร์ฟเวอร์ของคุณ ส่งผลให้ log แสดงว่า process ยังทำงานปกติ แต่ฝั่ง client กลับพบว่า stream ถูกตัด ให้เพิ่มค่านี้เฉพาะใน location ของ MCP เท่านั้น ไม่ใช่ทั้งเซิร์ฟเวอร์ นอกจากนี้ เซิร์ฟเวอร์ควรส่ง SSE comment line (บรรทัดที่ขึ้นต้นด้วยเครื่องหมาย colon) เพื่อเป็น keep-alive ในช่วงที่ไม่มีทราฟฟิก ซึ่งจะช่วยป้องกันไม่ให้ตัวกลางตัดการเชื่อมต่อ stream

Caddy ต้องการการตั้งค่าน้อยกว่า โดยจะทำ buffering เพียงบางส่วนเพื่อประสิทธิภาพในการส่งข้อมูลและจะ flush ทันทีเมื่อการตอบกลับมี Content-Type: text/event-stream ดังนั้น streaming จึงทำงานได้โดยไม่ต้องเพิ่ม directive พิเศษ

mcp.example.com {
	reverse_proxy 127.0.0.1:8080 {
		health_uri /healthz
		health_interval 10s
	}
}

โปรดสังเกตว่า health check ชี้ไปที่ใด อย่าชี้การตรวจสอบแบบ active ไปที่ MCP endpoint ด้วย GET เนื่องจากเซิร์ฟเวอร์ที่ใช้ revision นี้จะตอบกลับ 405 Method Not Allowed ต่อ GET และ DELETE ในขณะที่วิธีตรวจสอบเริ่มต้นของ Caddy คือ GET ซึ่งจะทำให้ proxy ระบุว่า backend ที่ทำงานปกติกลายเป็นสถานะ down ให้สร้าง path ธรรมดาเช่น /healthz สำหรับ proxy และตรวจสอบโปรโตคอลแยกต่างหากด้วย POST

curl -sS https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":"health-1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

200 ที่มีรายการ supportedVersions หมายความว่า process กำลังทำงานและสื่อสารด้วยโปรโตคอลได้ ส่วน 404 ที่มี JSON-RPC error -32601 หมายความว่า process ทำงานอยู่แต่ไม่รองรับ server/discover ซึ่งเป็นสิ่งที่ 2026-07-28 server ทุกตัวต้องมี และ 400 ที่มี -32022 หมายความว่าตัวตรวจสอบของคุณร้องขอเวอร์ชันที่ build นี้ไม่รองรับ ซึ่งเป็นสิ่งที่คุณต้องการตรวจพบหลังจากอัปเกรด dependency สำหรับ nginx เวอร์ชัน open source ไม่มีการตรวจสอบ health check แบบ active ดังนั้นให้ใช้ max_fails และ fail_timeout แบบ passive บน upstream และรันการตรวจสอบโปรโตคอลจากระบบ monitoring ของคุณแทน

การทำ rolling restart ในตอนนี้จะส่งผลกระทบเพียงแค่คำขอที่กำลังประมวลผลอยู่เท่านั้น ให้ทำการ drain, ปล่อยให้ POST ที่ค้างอยู่ทำงานจนเสร็จ, เริ่ม process ใหม่ แล้ว client จะส่งคำขอที่ล้มเหลวใหม่อีกครั้ง สิ่งเดียวที่ยังคงถูกตัดคือ subscriptions/listen stream ที่เปิดอยู่ เพราะ stream นั้นเป็นการเชื่อมต่อโดยตรงกับ process หนึ่งๆ การทำให้ระบบเป็น stateless ช่วยลดความจำเป็นในการทำ session affinity แต่ไม่ได้ลด connection affinity สำหรับ stream ที่เปิดอยู่ และไม่มีกฎการ routing ใดแก้ไขปัญหานี้ได้ Client สามารถแยกแยะได้: stream ที่จบด้วยผลลัพธ์ subscriptions/listen ว่างเปล่าถือว่าปิดอย่างสมบูรณ์ ส่วน stream ที่จบลงโดยไม่มีผลลัพธ์ถือว่าถูกตัด ซึ่ง client อาจใช้เป็นเหตุผลในการเชื่อมต่อใหม่

การทำ caching สามารถทำได้เป็นครั้งแรก ผลลัพธ์จาก method ประเภท list จะมี ttlMs และ cacheScope และ cacheScope: "public" จะแจ้งให้ตัวกลางที่ใช้ร่วมกันทราบว่าสามารถ cache การตอบกลับได้ ซึ่งปลอดภัยเพราะผลลัพธ์ของ list ไม่เปลี่ยนแปลงตามการเชื่อมต่ออีกต่อไป อันเป็นผลโดยตรงจากการยกเลิกการใช้ session

เหตุใดการยืนยันตัวตนจึงเปลี่ยนไปเมื่อไม่มี session

เมื่อมี session เรามักจะเผลอใช้วิธีการยืนยันตัวตนเพียงครั้งเดียวที่ initialize แล้วใช้ session ID เป็นหลักฐานยืนยันสำหรับทุกอย่างหลังจากนั้น session ID ที่ถูกนำมาใช้ในลักษณะนี้เปรียบเสมือน bearer credential ที่ไม่มีการระบุกลุ่มผู้ใช้งาน (audience) ไม่มีวันหมดอายุ และไม่มีช่องทางในการเพิกถอนสิทธิ์ ซึ่งเซิร์ฟเวอร์ของคุณเป็นผู้สร้างขึ้นเอง การยกเลิกการใช้ session จะช่วยตัดทางลัดดังกล่าวออกไป และแทนที่ด้วยกระบวนการที่เข้มงวดกว่า

เซิร์ฟเวอร์ MCP ที่มีการป้องกันจะทำหน้าที่เป็น OAuth 2.1 resource server โดยทุกคำขอ HTTP จากไคลเอนต์จะต้องแนบ Authorization: Bearer <access token> มาด้วย และเซิร์ฟเวอร์จะตรวจสอบ token ในทุกคำขอ การตรวจสอบนี้รวมถึงการระบุกลุ่มผู้ใช้งาน (audience) ด้วย กล่าวคือ เซิร์ฟเวอร์ต้องยืนยันว่า token นั้นถูกออกให้สำหรับเซิร์ฟเวอร์นั้นโดยเฉพาะ ตามมาตรฐาน RFC 8707 (Resource Indicators for OAuth 2.0) และต้องไม่ยอมรับหรือส่งต่อ token ที่มีไว้สำหรับบริการอื่น ไคลเอนต์จะร้องขอ audience ที่ถูกต้องโดยการส่งพารามิเตอร์ resource พร้อมกับ canonical URI ของเซิร์ฟเวอร์

กระบวนการค้นหา (discovery) จะเริ่มจาก challenge เมื่อมีคำขอเข้ามาโดยไม่มี token ที่ใช้งานได้ เซิร์ฟเวอร์จะตอบกลับด้วย 401 Unauthorized

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

ไคลเอนต์จะอ่าน resource_metadata จากนั้นดึงเอกสารดังกล่าว (RFC 9728, OAuth 2.0 Protected Resource Metadata ซึ่งเซิร์ฟเวอร์ MCP ต้องรองรับ) เพื่อค้นหา authorization server และดำเนินการตาม flow หาก token ที่ถูกต้องมีสิทธิ์ไม่เพียงพอ เซิร์ฟเวอร์จะตอบกลับด้วย 403 Forbidden พร้อมกับ error="insufficient_scope" และขอบเขต (scopes) ที่จำเป็นสำหรับการดำเนินการนั้น

ผลลัพธ์ที่ตามมาสำหรับการจัดการระบบมีสองประการ ประการแรก การตรวจสอบ token จะเกิดขึ้นในทุกคำขอแทนที่จะเป็นเพียงครั้งเดียวต่อ session ดังนั้นการทำ network round trip ไปยัง introspection endpoint ในทุกการเรียกใช้งานจะส่งผลต่อค่า latency ของคุณ จึงควรเลือกใช้ token ที่สามารถตรวจสอบได้ในเครื่องผ่านลายเซ็น (signature), audience และวันหมดอายุ หรือใช้วิธีแคชผลการตรวจสอบไว้ในช่วงเวลาสั้นๆ โดยใช้ token เป็นคีย์ ประการที่สอง เนื่องจากไม่มี session ที่คอยเก็บข้อมูลระบุตัวตน การอนุญาต (authorization) จึงต้องถูกคำนวณจาก token ในทุกการเรียกใช้งาน วิธีนี้มีความโปร่งใสมากกว่ารูปแบบ session เดิม และสอดคล้องกับแนวทางปฏิบัติทั่วไปในการเก็บ credential ไว้นอกกระบวนการทำงานของ agent ซึ่งครอบคลุมอยู่ใน การเก็บความลับไว้นอก AI agent ขอบเขต (scopes) จะทำหน้าที่จำกัดสิ่งที่ token สามารถทำได้เมื่อคำขอมาถึงคุณเท่านั้น ส่วนบนเครื่องที่ agent ทำงานอยู่ การใช้ปลั๊กอินควบคุมที่เพิ่มกฎการอนุญาตเครื่องมือและจำกัดงบประมาณ จะเป็นตัวตัดสินว่าการเรียกใช้งานใดบ้างที่สามารถเกิดขึ้นได้จริง

ข้อเท็จจริงและสิ่งที่ไม่ใช่สำหรับรุ่นนี้

เนื้อหาทั้งหมดข้างต้นอธิบายถึงรุ่น 2026-07-28 เท่านั้น ไม่ได้อธิบายถึง MCP ในภาพรวม และไม่ได้อธิบายถึงเซิร์ฟเวอร์ที่คุณติดตั้งใช้งานเมื่อปีที่แล้ว

ไคลเอนต์และเซิร์ฟเวอร์ที่ใช้รุ่น 2025-11-25 หรือเก่ากว่านั้นยังคงใช้รูปแบบการทำ handshake แบบเดิม ข้อกำหนดเรียกการแก้ไขเหล่านี้ว่ารุ่น legacy และเรียกการแก้ไขแบบ per-request-metadata ว่ารุ่น modern เซิร์ฟเวอร์ที่รองรับเฉพาะรุ่นนี้เมื่อพบกับไคลเอนต์รุ่นเก่า ควรตอบกลับด้วย 405 Method Not Allowed ไปยัง GET หรือ DELETE ที่ MCP endpoint, เพิกเฉยต่อ header Mcp-Session-Id ใดๆ โดยไม่ต้องสร้างหรือส่งค่ากลับ และเพิกเฉยต่อ Last-Event-ID เนื่องจากสตรีมไม่สามารถกลับมาทำงานต่อได้ (resumable) เซิร์ฟเวอร์ที่รองรับทั้งสองยุคอาจให้บริการทั้งสองแบบใน endpoint เดียวกัน โดยคำขอที่มี _meta แบบ modern จะถูกประมวลผลแบบ stateless ส่วนคำขอแบบ initialize จะเลือกใช้รูปแบบ session แบบเดิม

ดังนั้น โปรดตรวจสอบสตริงของรุ่นก่อนที่จะเชื่อถือข้อมูลเหล่านี้ หาก SDK ของคุณยังคงส่ง initialize แสดงว่า session ยังคงมีผลจริงสำหรับการติดตั้งใช้งานของคุณ และปัญหาที่เกี่ยวข้องกับ session ตามที่กล่าวข้างต้นยังคงเป็นสิ่งที่คุณต้องจัดการ เช่นเดียวกันกับฝั่งไคลเอนต์ กระบวนการ agent บนเครื่องของคุณเอง เช่น การตั้งค่าใน การรัน coding agent บน VPS จะเป็นแบบ stateless ในความหมายนี้ก็ต่อเมื่อไลบรารีที่ใช้รองรับรุ่น modern เท่านั้น โปรดอ่านเวอร์ชันที่ runtime ของคุณเจรจาตกลงกัน จากนั้นอ่านรุ่นของข้อกำหนดที่ตรงกัน และถือว่าหน้านี้อธิบายถึงรุ่นที่ระบุไว้รุ่นหนึ่งเท่านั้น ไม่ใช่โปรโตคอลโดยรวม

FAQ

การที่ MCP server เป็นแบบ stateless หมายความว่าฉันไม่สามารถจัดเก็บข้อมูลใดๆ ได้ใช่หรือไม่

ไม่จริง คำว่า stateless อธิบายถึงตัวโปรโตคอล ไม่ใช่แอปพลิเคชันของคุณ ฐานข้อมูล คิว และแคชยังคงทำงานได้ตามปกติเหมือนเดิม สิ่งที่เปลี่ยนไปคือสถานะที่ครอบคลุมการเรียกใช้งานหลายครั้งจะต้องถูกอ้างอิงด้วยตัวระบุที่ชัดเจนซึ่งไคลเอนต์ส่งมาในทุกคำขอ เช่น handle ที่เซิร์ฟเวอร์สร้างขึ้นในอาร์กิวเมนต์ของเครื่องมือ สิ่งที่คุณไม่สามารถทำได้คือการอนุมานบริบทจากการเชื่อมต่อ: ข้อกำหนดระบุว่าเซิร์ฟเวอร์ต้องไม่พึ่งพาคำขอก่อนหน้าผ่านการเชื่อมต่อเดียวกันเพื่อกำหนดขีดความสามารถ เวอร์ชันของโปรโตคอล หรือตัวตนของไคลเอนต์ เนื่องจากทุกคำขอจะระบุข้อมูลเหล่านี้ไว้ใน _meta แล้ว

ฉันยังจำเป็นต้องใช้ sticky sessions บน load balancer ของฉันอยู่หรือไม่

ไม่จำเป็นสำหรับคำขอทั่วไป ภายใต้การแก้ไขฉบับ 2026-07-28 คำขอ POST แต่ละรายการจะระบุเวอร์ชันของโปรโตคอล ขีดความสามารถ และข้อมูลประจำตัวของตัวเอง ดังนั้น replica ใดๆ ก็สามารถตอบสนองคำขอใดๆ ได้ และการทำ round-robin ก็เพียงพอแล้ว สิ่งเดียวที่ยังคงมีอายุการใช้งานยาวนานคือสตรีมการตอบกลับ subscriptions/listen ซึ่งเป็นการเชื่อมต่อแบบเปิดเดียวไปยังกระบวนการเดียว มันจะสิ้นสุดลงเมื่อกระบวนการนั้นสิ้นสุด และไคลเอนต์จะส่ง subscriptions/listen อีกครั้งเพื่อสร้างการเชื่อมต่อใหม่ นั่นเป็นเรื่องของอายุการเชื่อมต่อมากกว่าความสัมพันธ์ของเซสชัน และไม่มีกฎการกำหนดเส้นทางใดที่ป้องกันเรื่องนี้ได้

เกิดอะไรขึ้นกับ Mcp-Session-Id และสตรีม HTTP GET

ทั้งสองรายการถูกลบออกในฉบับแก้ไข 2026-07-28 ภายใต้ SEP-2567 และ SEP-2575 เซิร์ฟเวอร์ที่ใช้เฉพาะฉบับแก้ไขนี้ควรตอบกลับ 405 Method Not Allowed ต่อ GET และ DELETE ที่ MCP endpoint และควรเพิกเฉยต่อ header Mcp-Session-Id แทนที่จะส่งกลับไป การแจ้งเตือนการเปลี่ยนแปลงที่เริ่มต้นจากเซิร์ฟเวอร์จะถูกส่งผ่านสตรีมการตอบกลับของคำขอ subscriptions/listen แทนที่จะเป็นสตรีม GET แบบแยกต่างหาก เซิร์ฟเวอร์ที่ยังคงต้องให้บริการไคลเอนต์รุ่นเก่าจะต้องใช้พฤติกรรมของฉบับแก้ไขก่อนหน้านี้ควบคู่ไปกับฉบับนี้

ฉันจะตรวจสอบสุขภาพ (health check) ของ MCP server ที่ไม่มีการทำ handshake ได้อย่างไร

ให้ใช้การตรวจสอบสองระดับ ชี้การตรวจสอบแบบ active ของพร็อกซีไปยัง path HTTP ปกติที่แอปพลิเคชันของคุณให้บริการ เนื่องจาก GET ไปยัง MCP endpoint จะส่งกลับ 405 อย่างถูกต้องและจะทำให้ backend ที่มีสุขภาพดีถูกทำเครื่องหมายว่าใช้งานไม่ได้ จากนั้นให้ตรวจสอบตัวโปรโตคอลเองโดยการส่ง POST ไปยัง server/discover ซึ่งเซิร์ฟเวอร์ 2026-07-28 ทุกตัวต้องรองรับ และยืนยันว่าการตอบกลับเป็น HTTP 200 และแสดงรายการเวอร์ชันของโปรโตคอลที่ไคลเอนต์ของคุณใช้งานอยู่ หากได้รับ 404 พร้อมข้อผิดพลาด JSON-RPC -32601 หมายความว่ากระบวนการกำลังทำงานอยู่แต่ไม่ได้ให้บริการเมธอดนั้น และหากได้รับ 400 พร้อม -32022 หมายความว่าเวอร์ชันที่คุณร้องขอไม่ได้รับการสนับสนุนโดย build นั้น