Stateless MCP servers: ano ang nagbago
Sa MCP revision 2026-07-28, inalis ang sessions at initialize handshake. Alamin ang epekto sa reverse proxy, health checks, timeouts, at auth.
Ano ang stateless MCP server
Ang stateless MCP server ay hindi nagtatago ng per-client state sa pagitan ng mga request. Dala ng bawat request ang protocol version, client capabilities, at credentials na kailangan ng server para masagot ito. Dahil dito, maaaring sagutin ng anumang process sa anumang machine ang anumang request. Ginawang tuntunin ito ng MCP (Model Context Protocol, ang wire format na ginagamit ng mga agent para maabot ang mga tool) sa revision 2026-07-28. Inalis nito ang initialize handshake at ang HTTP session na nakapailalim dito.
Iyan ang buong operational na punto. Ang server na walang itinatagong per-client state ay maaaring ilagay sa likod ng ordinary load balancer na walang session affinity. Maaari rin itong i-restart habang nagde-deploy nang hindi napuputol ang mga client. Maaari rin itong patakbuhin bilang apat na magkakaparehong process sa halip na isa. Hindi magagawa ng session-oriented server ang mga ito nang walang karagdagang machinery.
Ang Model Context Protocol ay isang stateless protocol: nasa request mismo ang lahat ng impormasyong kailangan para maproseso ito. Pinoproseso ng server ang bawat request nang hiwalay. Hindi dapat maghinuha ng state mula sa mga naunang request, kahit nasa parehong connection o stream ang mga ito.
Hindi ibig sabihin ng stateless na walang ini-store ang server. Nariyan pa rin ang database, queue, at cache mo. Ibig sabihin nito, walang state na dala ang protocol sa connection. Kaya hindi dapat ituring ng server ang isang connection, process, o open socket bilang kapalit ng “client na nasa kalagitnaan ng pag-uusap.”
Mga inalis sa revision 2026-07-28
Ang 2026-07-28 ang kasalukuyang revision ng specification noong August 2026. Kung ihahambing sa 2025-11-25, inalis nito ang limang bagay na dating ginagamit upang suportahan ang mga session.
- Ang
initializerequest at angnotifications/initializednotification. Wala nang handshake (SEP-2575). - Ang
Mcp-Session-Idheader, pati ang pagwawakas ng session gamit ang HTTPDELETE(SEP-2567). - Ang standalone HTTP
GETstream kung saan nagpu-push ang mga server ng notifications. Pinalitan ito ngsubscriptions/listen, isang ordinaryong POST na ang response ay long-lived stream. - Ang resumability ng SSE (server-sent events) stream. Wala na ang
Last-Event-IDheader at ang mga per-event ID. Kaya kapag naputol ang stream, mawawala ang request na kasalukuyang pinoproseso, at kailangang i-issue muli ng client bilang bagong request na may bagong request ID. ping,logging/setLevel, atnotifications/roots/list_changed. Ang log level ay field na ngayon ng bawat request,io.modelcontextprotocol/logLevelsa_meta.
May idinagdag na isang method, at kailangang i-implement ito ng bawat server. Ibinabalik ng server/discover sa isang call ang mga suportadong protocol version, capability, at identity ng server. Ito ang pinakamalapit na natitirang katumbas ng handshake, at optional itong tawagin ng mga client.
Bakit mahirap patakbuhin sa production ang session transport
Sa 2025-11-25 at mga naunang bersyon, maaaring gumawa ang server ng session ID sa initialization at ibalik ito sa Mcp-Session-Id header ng InitializeResult. Kailangang ipadala ng client ang header na iyon sa bawat kasunod na request. Nasa memory ng server ang negotiated protocol version at capabilities ng client, na naka-key sa ID na iyon. May operational cost ang bawat isa sa mga pagpipiliang ito.
- Nawawala ang session table kapag nag-restart ang server. Kinakailangan ng specification na sagutin ng server ng
404 Not Foundang anumang request na may dead session ID, at kinakailangang magsimula muli ang client gamit ang bagongInitializeRequest. Sa bawat deploy, kailangang muling kumonekta ang bawat nakakonektang client. - Hindi alam ng second replica ang mga session ng first replica. Kapag nag-scale out, kailangan ng sticky routing sa load balancer o shared session store na binabasa ng bawat replica sa bawat request.
- Ang session table ay memory na lumalaki habang dumarami ang idle client. Optional ang
DELETE, at nag-iiwan ng mga entry ang mga client na nagsara nang hindi ito ipinadala. - Maaaring mag-iba ang mga resulta ng list sa bawat connection, kaya hindi ligtas ang caching sa harap ng server.
Inaalis ng pagtanggal sa sessions ang lahat ng apat na problemang ito nang sabay-sabay. Ito ang pagbabagong dapat maunawaan bago baguhin ang anumang config.
Ano ang dala ngayon ng bawat request
Standalone ang bawat POST sa MCP endpoint. Ang protocol version at client capabilities ay ipinapadala sa request body sa ilalim ng _meta, at ang mga piling field ay inuulit sa HTTP headers upang ma-route ng intermediary ang request nang hindi nagpa-parse ng 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": {}
}
}
}Required ang io.modelcontextprotocol/protocolVersion at io.modelcontextprotocol/clientCapabilities sa bawat request. Hindi required ang clientInfo, pero dapat itong ipadala ng mga client. Malformed ang request kapag may nawawalang required field, kaya dapat itong i-reject ng server gamit ang JSON-RPC error na -32602 at HTTP 400 Bad Request.
Required ang Mcp-Method header sa bawat request. Required ang Mcp-Name sa tools/call, resources/read, at prompts/get. Dapat tumugma ang value ng header sa body. Kapag pinroseso ng server ang body, dapat nitong i-reject ang hindi pagtutugma gamit ang 400 Bad Request at error code na -32020, HeaderMismatch. Kailangan ang rule na ito dahil magkaibang source of truth ang load balancer na nagro-route batay sa header at ang server na nag-e-execute batay sa body. Kung ginagamit mo ang mga header na ito para sa routing o rate limiting, suriin muna ang MCP-Protocol-Version: hindi bine-validate ng mga naunang revision ang header laban sa body, kaya hindi mapagkakatiwalaan ang value ng header sa mga version na iyon.
Ang hindi pagtutugma ng version ay ordinaryong per-request error na ngayon sa halip na failed handshake. Kapag hindi sinusuportahan ng server ang hinihinging version, sasagot ito ng 400 Bad Request na may error na -32022, UnsupportedProtocolVersion, at ililista sa data.supported ang mga version na sinusuportahan nito. Pipili ang client ng isang version mula sa listahan at magre-retry.
Kung saan napunta ang state: tokens, cursors, subscriptions
Hindi nawala ang state. Napunta ito sa mga lugar na maaari mong makita at i-log.
Napupunta ang credentials sa bawat request. Walang session na paglalagyan ng identity, kaya kasama ang access token sa bawat HTTP call at bina-validate ito sa bawat pagkakataon. Nasa authentication section sa ibaba ang mga detalye.
Kailangang dala ng cursors ang sarili nilang posisyon. Gumagamit ang pagination sa tools/list, resources/list, prompts/list at resources/templates/list ng opaque cursor string, at hindi ito dapat i-parse o baguhin ng mga client. Sa single-process server, karaniwan nang itago sa memory ang offset at i-key ito ayon sa session. Kapag walang session, kailangang sapat ang cursor para maipagpatuloy ng alinmang replica ang listing. I-encode ang posisyon sa loob ng cursor at lagdaan ito, o itago ang posisyon sa storage na pinagsasaluhan ng lahat ng replica. Dapat magbalik ng -32602 ang invalid cursor. Lagdaan ito dahil ang opaque cursor ay input pa rin na ibinibigay ng client at dine-decode at pinagkakatiwalaan ng iyong code.
Nauugnay ang subscriptions sa request, hindi sa connection. Ang client na nangangailangan ng change notifications ay nagpapadala ng subscriptions/listen na may filter na tumutukoy sa mga type na kailangan nito: toolsListChanged, promptsListChanged, resourcesListChanged at resourceSubscriptions. Tumutugon ang server gamit ang notifications/subscriptions/acknowledged at iniiwang bukas ang response stream. Kapag naputol ang stream, walang itinatabing state ang server, at muling ipinapadala ng client ang subscriptions/listen upang maibalik ito.
Nagiging explicit handle ang application state na tumatawid sa mga call. Kapag talagang kailangang magpanatili ng server ng isang bagay sa pagitan ng mga call, ang sagot ng specification ay isang identifier na ginagawa ng server at ipinapasa pabalik bilang ordinaryong tool argument. Lumilitaw ito sa tool schema, maaaring i-log, at hindi kailanman ipinahihiwatig ng connection. Ang server na may aktuwal na per-user data sa likod nito, gaya ng isang self-hosted MCP email server, ay gumagamit ng pattern na ito sa halip na session: tool argument ang mailbox o draft identifier, kaya maaaring ipagpatuloy ng alinmang replica ang susunod na call. Maraming tool ang hindi nangangailangan ng handle: ang search tool na gumagamit ng sarili mong SearXNG instance ay tumatanggap ng query at nagbabalik ng mga resulta, nang walang kailangang ipagpatuloy sa susunod na call at walang dahilan para alamin kung aling replica ang tumugon.
Deployment: reverse proxy, timeouts, health checks
Ang MCP endpoint ay isang path na tumatanggap ng POST. Karamihan ng traffic ay maikling request at JSON response, na kayang i-handle ng anumang proxy. Ang exception ay ang streaming response, kung saan nakasasagabal ang mga default ng proxy. Ito ang bahaging nagbabago kapag lumipat ka mula sa laptop demo papunta sa MCP server na tumatakbo sa isang 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;
}Mahalaga ang proxy_buffering off dahil, bilang default, bina-buffer ng nginx ang mga response na ipinapasa sa proxy. Dahil dito, hindi nito ipinapadala ang SSE events hanggang mapuno ang buffer o matapos ang response. Hinihingi rin ng specification na magpadala ang mga server ng X-Accel-Buffering: no sa mga SSE response, at iginagalang ng nginx ang header na iyon. Kaya maayos na sinasabi ng server sa proxy ang tamang configuration. Itakda rin ang directive, dahil iyon ang bahaging kinokontrol mo.
Ang default ng proxy_read_timeout ay 60 seconds. Ang subscriptions/listen stream na walang activity nang mas mahaba rito ay isinasara ng nginx, hindi ng server mo. Dahil dito, maaaring magpakitang healthy ang process sa logs mo habang nagpapakitang naputol ang stream sa client. Itaas ang value nito sa MCP location lamang, hindi sa buong server. Hinihikayat din ang mga server na magpadala ng SSE comment line, isang linyang nagsisimula sa colon, bilang keep-alive kapag tahimik ang stream. Pinipigilan nito ang mga intermediary na i-time out ang stream.
Mas kaunti ang kailangang configuration sa Caddy. Bilang default, bahagya itong nagba-buffer para sa wire efficiency at agad itong nagfa-flush kapag dala ng response ang Content-Type: text/event-stream. Kaya gumagana ang streaming nang walang karagdagang directive.
mcp.example.com {
reverse_proxy 127.0.0.1:8080 {
health_uri /healthz
health_interval 10s
}
}Pansinin kung saan tumuturo ang health check na iyon. Huwag magpatakbo ng active check sa MCP endpoint gamit ang GET. Ang server na nag-iimplement lang ng revision na ito ay sumasagot ng 405 Method Not Allowed sa GET at DELETE, samantalang ang default health method ng Caddy ay GET. Dahil dito, mamarkahan ng proxy bilang down ang backend kahit healthy ito. Mag-serve ng plain path gaya ng /healthz para sa proxy, at hiwalay na suriin ang protocol gamit ang 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":{}}}}'Ang 200 na may supportedVersions list ay nangangahulugang gumagana ang process at nakikipag-usap ito gamit ang protocol. Ang 404 na may JSON-RPC error na -32601 ay nangangahulugang gumagana ang process ngunit hindi ito nagsi-serve ng server/discover, na kailangang i-implement ng bawat 2026-07-28 server. Ang 400 na may -32022 ay nangangahulugang humingi ang checker ng version na hindi sinusuportahan ng build na ito. Ito mismo ang kailangang mahuli pagkatapos ng dependency upgrade. Walang active health checks ang open source nginx. Kaya gumamit ng passive max_fails at fail_timeout sa upstream, at patakbuhin ang protocol check mula sa monitoring mo.
Sa rolling restart, ang mawawala na lamang ay ang mga request na kasalukuyang isinasagawa. Wala nang iba. I-drain ang traffic, hintaying matapos ang mga bukas na POST, simulan ang bagong process, at ipa-reissue sa mga client ang anumang nag-fail. Ang isang bagay na mawawala pa rin ay ang bukas na subscriptions/listen stream, dahil live connection ito sa isang partikular na process. Inalis ng statelessness ang session affinity. Hindi nito inalis ang connection affinity para sa stream na kasalukuyang bukas, at walang routing rule na makapag-aayos nito. Makikilala ito ng client: ang stream na nagtatapos sa walang lamang subscriptions/listen result ay maayos na nagsara, samantalang ang stream na nagtatapos nang wala nito ay naputol. Maaaring ituring ito ng client na dahilan para muling kumonekta.
Posible na ngayon ang caching sa unang pagkakataon. Dala na ngayon ng mga result mula sa list methods ang ttlMs at cacheScope, at sinasabi ng cacheScope: "public" sa mga shared intermediary na maaari nilang i-cache ang response. Ligtas lamang ito dahil hindi na nag-iiba ang list results batay sa connection. Direktang bunga ito ng pag-aalis ng sessions.
Bakit nagbabago ang authentication kapag walang session
Kapag may session, madaling piliing mag-authenticate nang isang beses sa initialize at ituring ang session ID bilang patunay para sa lahat ng kasunod na request. Kapag ginagamit sa ganitong paraan, ang session ID ay isang bearer credential na walang audience, expiry, o paraan ng revocation, at ginawa ng sarili mong server. Kapag inalis ang sessions, nawawala ang shortcut na iyon at mas mahigpit ang kapalit na proseso.
Ang isang protected MCP server ay kumikilos bilang OAuth 2.1 resource server. Dapat may dalang Authorization: Bearer <access token> ang bawat HTTP request mula sa client, at vina-validate ng server ang token sa bawat request. Kasama sa validation ang audience: dapat tiyakin ng server na partikular para rito inisyu ang token, alinsunod sa RFC 8707 (Resource Indicators for OAuth 2.0), at hindi nito dapat tanggapin o ipasa ang mga token na para sa ibang resource. Hinihiling ng mga client ang tamang audience sa pamamagitan ng pagpapadala ng resource parameter na naglalaman ng canonical URI ng server.
Nagsisimula ang discovery sa isang challenge. Kapag may dumating na request na walang usable token, sumasagot ang server ng 401 Unauthorized.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"Binabasa ng client ang resource_metadata, kinukuha ang document na iyon (RFC 9728, OAuth 2.0 Protected Resource Metadata, na dapat i-implement ng MCP servers), hinahanap ang authorization server, at pinapatakbo ang flow. Ang valid na token na kulang ang permissions ay tumatanggap ng 403 Forbidden kasama ang error="insufficient_scope" at ang scopes na kailangan para sa operation na iyon.
May dalawang epekto ito sa paraan ng pagpapatakbo mo sa server. Isinasagawa na ngayon ang token validation sa bawat request sa halip na isang beses bawat session. Dahil dito, makikita sa latency ang isang network round trip papunta sa introspection endpoint sa bawat call. Mas mainam ang mga token na maaaring i-verify locally gamit ang signature, audience, at expiry, o kaya ay i-cache ang validation result sa maikling window na naka-key sa token. Dahil wala nang session na nagtataglay ng identity, dapat kalkulahin ang authorization mula sa token sa bawat call. Mas tapat ito kaysa sa session model, at kaakibat ito ng mas malawak na practice na inilalayo ang credentials sa agent process, na tinalakay sa pag-iingat ng mga secret sa labas ng AI agent.
Ano ang totoo sa revision na ito, at ano ang hindi
Inilalarawan ng lahat ng nasa itaas ang revision 2026-07-28. Hindi nito inilalarawan ang MCP magpakailanman, at hindi rin nito inilalarawan ang server na dineploy mo noong nakaraang taon.
Ang mga client at server na gumagamit ng 2025-11-25 at mas naunang revision ay gumagamit pa rin ng handshake model. Tinatawag ng specification na legacy ang mga revision na iyon, at modern naman ang mga revision na gumagamit ng per-request metadata. Ang server na sumusuporta lamang sa revision na ito, kapag nakipag-ugnayan sa mas lumang client, ay dapat magbalik ng 405 Method Not Allowed para sa GET o DELETE sa MCP endpoint, huwag pansinin ang anumang Mcp-Session-Id header nang hindi gumagawa o nagbabalik ng isa, at huwag pansinin ang Last-Event-ID dahil hindi maaaring i-resume ang mga stream. Maaaring pagsilbihan ng isang dual-era server ang dalawang uri sa iisang endpoint: ang request na may modernong _meta ay pinoproseso nang stateless, habang ang initialize request ang pumipili ng mas lumang session semantics.
Kaya suriin muna ang revision string bago mo pagkatiwalaan ang alinman sa mga ito. Kung nagpapadala pa rin ang SDK mo ng initialize, aktibo pa rin ang mga session sa deployment mo at kailangan mo pa ring pamahalaan ang mga problemang nauugnay sa session na inilarawan sa itaas. Ganoon din sa client side: ang agent process sa sarili mong box, gaya ng setup sa pagpapatakbo ng coding agent sa isang VPS, ay stateless lamang sa ganitong kahulugan kung modern revision ang ginagamit na library nito. Basahin ang version na nino-negotiate ng runtime mo, pagkatapos ay basahin ang katugmang revision ng specification, at ituring ang page na ito bilang paglalarawan ng isang partikular na pinangalanang revision, hindi ng protocol sa pangkalahatan.
FAQ
Nangangahulugan ba ang stateless MCP server na wala akong maaaring i-store?
Hindi. Ang stateless ay tumutukoy sa protocol, hindi sa application mo. Gaya ng dati, gumagana ang databases, queues, at caches. Ang nagbabago ay kailangang tukuyin gamit ang explicit identifier ang state na sumasaklaw sa ilang calls. Ipinapasa ng client ang identifier sa bawat request, gaya ng handle na binuo ng server sa isang tool argument. Hindi mo maaaring tukuyin ang context batay sa connection. Ayon sa specification, hindi dapat umasa ang server sa mga naunang request sa parehong connection para itatag ang capabilities, protocol version, o client identity, dahil ibinibigay ang mga ito ng bawat request sa _meta.
Kailangan ko pa rin ba ng sticky sessions sa load balancer?
Hindi para sa mga ordinaryong request. Sa revision 2026-07-28, dala ng bawat POST ang sarili nitong protocol version, capabilities, at credentials. Kaya maaaring sumagot ang anumang replica sa anumang request, at sapat na ang round-robin. Ang natitirang long-lived na bahagi ay ang subscriptions/listen response stream. Isa itong bukas na connection sa isang process. Nagtatapos ito kapag natapos ang process, at muling ipinapadala ng client ang subscriptions/listen upang itatag itong muli. Connection lifetime ito, hindi session affinity, kaya walang routing rule na makapipigil dito.
Ano ang nangyari sa Mcp-Session-Id at sa HTTP GET stream?
Kapwa inalis ang mga ito sa revision 2026-07-28, sa ilalim ng SEP-2567 at SEP-2575. Ang server na revision na ito lamang ang ipinapatupad ay dapat sumagot ng 405 Method Not Allowed sa GET at DELETE sa MCP endpoint. Dapat din nitong balewalain ang Mcp-Session-Id header sa halip na ibalik ito. Dumadaan na ngayon ang server-initiated change notifications sa response stream ng isang subscriptions/listen request sa halip na sa hiwalay na GET stream. Ang mga server na kailangan pa ring magsilbi sa mas lumang clients ay nagpapatupad ng behavior ng naunang revision kasabay ng revision na ito.
Paano ako magsasagawa ng health check sa MCP server na walang handshake?
Gumamit ng dalawang level. Ituro ang active check ng proxy sa isang plain HTTP path na sinisilbi ng application mo. Tama lang na magbalik ang isang GET sa MCP endpoint ng 405, kaya mamarkahan nitong down ang isang healthy backend. Pagkatapos, i-check ang protocol mismo sa pamamagitan ng pag-POST ng server/discover. Dapat itong ipatupad ng bawat 2026-07-28 server. Tiyakin ding HTTP 200 ang reply at nakalista rito ang isang protocol version na ginagamit ng iyong clients. Ang 404 na may JSON-RPC error na -32601 ay nangangahulugang tumatakbo ang process ngunit hindi nito sinisilbi ang method na iyon. Ang 400 na may -32022 ay nangangahulugang hindi suportado ng build na iyon ang version na hiniling mo.