З’явіліце інструменты MCP як безстандартныя службы пасля спецыфікацыі 2026-07-28.
Адыягуць сесіі з клейкімі дадзеннямі: MCP 2026-07-28 прымае запіты, якія ўсё неабходнае маюць у сабе, з маршрутывацыяй па заголовкам, перапрыбуткамі за ўмовы input_required і спяльнымі ключамі requestState між рэплікамі.
Крыніцы: MCP Just Went Stateless: Run Your Agent Tools Like Real Services
Чаму сэсіі створылі проблему ў працоўнай асяроддзі
Работа MCP за дапамою балансэра навантажэння вымагала падтрымкі афіліяцыі сэсій, пры чым кожны кліент быў прыкрэплён да адного падмашыны, і дазволялася можлівасць таго, што звычныя абнаўленні моглі перарваць працу агента ў падчас выканання задач. У версіі MCP 2026–07–28 сэсіі на рыжым канале зусім адменшаны. Адпаведальныя за развіцце апісалі гэта як найбольшыя змены з моменту запуску функцыі автарызацыі, і для команд, якія вікорыстоўваюць інструменты агента ў масштабных умовах, гэта самая значныя змена.
У наступнай інструкцыі пошагова пасвячана гэтае змены, а таксама показана простая безстановая сервера для выдачы вярненняў за дапамою офіцыйнага Python SDK v2, у якой ёсць крок затверджэння чалавекам, які застосовваецца нават пасля перэказа заданняў між рэплікамі. Прыклады булі працаваны на версіі mcp==2.2.0.
MCP пачаткова быў супрацоўнікам для перадачы даных з настольных прыстроек у локальныя інструменты праз стандарт stdio, дзе было дазволенае наявнасць сокета з довгачэйшым часам выкарыстоўвання. У Kubernetes, Cloud Run і іншых платформах з калькамі экземпляраў такі падход стаў даскладным:
- Жысткая маршрутазацыя. Працэс
initializeствараў ідэнтыфікаторMcp-Session-Id, і пасляеўныя запыты былі змушаны прайсці да таго ж працэса. - Нестабільныя запускі. Перзапуск пада стыраў усе актывацыйныя сесіі на ям.
- Незрозумелыя шлюзы. Обмежэння на частоту запытоў і маршрутазацыя ў WAF або API-шлюзе вымагалі парсавання тэла пакетаў у фармаце JSON-RPC.
- Актывацыйныя стрымы, якія застаўляліся відкрытымі. Калі інструменту пад час запыту была патрэбна адпаведзь чалавека, сервер заставляў стрым відкрытым і чакаў.
Такі фармат з пам’ятай прыстаноўкі падходзіў для локальных асістэнтав-кодавацелей. Адночаса падпрыемстваям быў патрэбны звычайны доступ да сервісаў у працэсе віробніцтва з централізаваным кераванням.
Змены спецыфікацыі, якія былі апрацаваны 2026-07-28
1. Ідэнтыфікаторы абмяну і сесіі больш не існуюць. Спароўваныя крокі initialize / initialized асобна Mcp-Session-Id заголовак больш не існуе. Кожны запіт несе версію пратакола, ідэнтыфікатор кліента і можлівасці ў params._meta. Будзь-якая копія, якая работае праз простыя механізмы рэндамайзавання навантажэння, можа прыміць будзь-які запіт.
2. Маршрутызацыя знаходзіцца ў HTTP-заголовках. Трафік HTTP, які можа быць стрымаваны, павінен мець Mcp-Method і Mcp-Name. Шлюзы можаць маршрутызаваць, кантролюваць і блакаваць запыты до інструментаў без адкрывання іх тэлу.
3. Мнагкі запыты з кілька раундоў (MRTR). Уместа таго, каб заставіць ачыты ўзаемадзею для чалавечага адказу, сервер адпавядае з resultType: "input_required", запытамі і незрозумелым блабом requestState. Кліент збірае адказы і прабуеяў зноў з inputResponses. Гэты патэрн заменяе запыты сервера тыпу elicitation/create, sampling/createMessage і roots/list пад час адправкі даных.
4. Спісы, якія можна кэшаваць. Адказы для tools/list, prompts/list, resources/list і resources/read могу паведамляць пра ttlMs і cacheScope, тады кліенты перастаюць часта запытваць эндпоінты спісаў, і кэшы запытанняў LLM застаюць стабільнымі.
5. Жорсткейя автарызацыя. Неабходны пераконтрольваннія па стандарцу RFC 9207. Дынамічная рэгістрацыя кліента заменяецца дакументамі метадаў ID кліента.
6. Расшырэннія. Задачы, якія выконваюцца дзёўга, заставляюць экспэрыментальны ядро працаваць з расшырэннем io.modelcontextprotocol/tasks. Програмы MCP і адмініструемая автарызацыя на рэвэне падаюць у кантэкст офіцыяльных расшырэнняў.
7. Застарэлыя функцыі з часам выключэння. Функцыі Roots, Sampling, Logging RPC і стары фармат HTTP+SSE застаюцца доступнымі прынеймна дванаццац месяцоў, пакуль команды перайшлі на новую версію.
Не гарантуецца, што новыя серверы будуць працаваць з старымі кліентамі. Апгрэйдавайце оба боку адночасна, калі магчыма. Рэалізацыі спецыяльных протакала сталкуюцца з значнымі вызовамі па супэрнацыю з корыстнікамі SDK.
Практычны прыклад: сервер MCP без стану на Python
Створыце маленькую службу вырачэння грошаў за дапамой двух інструментаў. get_order шукае замовленне. refund_order ўскладнены, таму першы чым грошы будуць пераказаны, ўсё павінна быць затверджана чалавекам. Тыя запітанні падчас размовы самэ пра тое, што раней выклікала патрэбу ў стабільным адносе.
З'явіце v2 SDK і створыце спакушаны секрэт для сяродзіння:
pip install "mcp[cli]>=2.2.0"
export MCP_STATE_KEY=$(python -c "import secrets; print(secrets.token_hex(32))")
Сервер
# server.py — a stateless MCP server (spec 2026-07-28, Python SDK v2)
import os
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.caching import CacheHint
from mcp.server.mcpserver import Elicit, RequestStateSecurity, Resolve
# Tell clients they may cache tools/list for 1 hour and share it across users.
mcp = MCPServer(
"refunds",
cache_hints={"tools/list": CacheHint(ttl_ms=3_600_000, scope="public")},
# Every replica must share this key, or a retry that lands on another pod fails.
request_state_security=RequestStateSecurity(keys=[os.environ["MCP_STATE_KEY"]]),
)
ORDERS = {"A-1001": 49.99, "A-1002": 1299.00} # stand-in for your real database
class Approval(BaseModel):
approve: bool
reason: str = ""
def ask_for_approval(order_id: str) -> Elicit[Approval]:
"""Resolver: ask a human to confirm the refund before the tool runs."""
amount = ORDERS[order_id]
return Elicit(f"Refund ${amount:,.2f} for order {order_id}?", Approval)
@mcp.tool()
def get_order(order_id: str) -> dict:
"""Look up an order's refundable amount."""
return {"order_id": order_id, "amount": ORDERS.get(order_id)}
@mcp.tool()
def refund_order(
order_id: str,
approval: Annotated[Approval, Resolve(ask_for_approval)],
) -> str:
"""Refund an order after a human approves it."""
if not approval.approve:
return f"Refund for {order_id} rejected: {approval.reason or 'no reason given'}"
return f"Refunded ${ORDERS[order_id]:,.2f} for {order_id}"
if __name__ == "__main__":
# No session state: any replica behind a round-robin load balancer can answer.
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000,
stateless_http=True, json_response=True)
Важлівыя тры дэталі:
Resolveпаўсараваны зElicit— гэта стыль впрыску залежнасцяў у v2. Параметрapprovalне заполняецца модэлем; SDK спачатку выканаўask_for_approval, а для кліентаў 2026-07-28 ператварае запіт у рэзультатinput_required. Старыя кліенты вяртаюцца да класычнага спосабу запітанняў падчас размовы.cache_hintsпрыўязваеttlMs/cacheScopeдаtools/list.
stateless_http=True разам з json_response=True дае адна JSON-адказка за кожны POST — без об’екта сесіі і без збераганага стрыму.Кліент
# client.py — call the server; the SDK handles the input_required retry loop
import asyncio
from mcp import Client
from mcp.types import ElicitResult
async def on_elicit(context, params):
# In a real app, render params.message in your UI and collect the answer.
print(f"[server asks] {params.message}")
return ElicitResult(action="accept", content={"approve": True})
async def main():
async with Client("http://localhost:8000/mcp", elicitation_callback=on_elicit) as client:
tools = await client.list_tools()
print("tools:", [t.name for t in tools.tools], "| ttlMs:", tools.ttl_ms)
result = await client.call_tool("refund_order", {"order_id": "A-1002"})
print(result.content[0].text)
asyncio.run(main())
Тыповая рэжым выведае ўдзейсненне на кшталт:
tools: ['get_order', 'refund_order'] | ttlMs: 3600000
[server asks] Refund $1,299.00 for order A-1002?
Refunded $1,299.00 for A-1002
Што на самай працы пераходзіць праз канэк
Першы вызов, выданы з curl, паказвае загаловкі маршрутацыі і канверт _meta, якія заменяюць стары працэс абману:
curl -s -X POST http://localhost:8000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: refund_order' \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "refund_order",
"arguments": {"order_id": "A-1002"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {"name": "curl", "version": "1.0"},
"io.modelcontextprotocol/clientCapabilities": {"elicitation": {"form": {}}}
}
}
}'
Сервер адказвае негайна, у замест на блакаванне:
{
"resultType": "input_required",
"inputRequests": {
"__main__:ask_for_approval": {
"method": "elicitation/create",
"params": {
"message": "Refund $1,299.00 for order A-1002?",
"mode": "form",
"requestedSchema": {
"type": "object",
"properties": {"approve": {"type": "boolean"}, "reason": {"type": "string"}},
"required": ["approve"]
}
}
}
},
"requestState": "v1.bOKJzdeMdGCz-ezv..."
}
Пасля таго, як чалавек адказае, кліент прабуе зноў той самы tools/call і дадае адказы плюс незрозумелы блаб стану:
"inputResponses": {
"__main__:ask_for_approval": {"action": "accept", "content": {"approve": true}}
},
"requestState": "v1.bOKJzdeMdGCz-ezv..."
Следуючыя адпаведныя запыткі вяртаюць resultType: "complete" з такім тэкстам, як Refunded $1,299.00 for A-1002. У обох запытках тыпу POST нямаўся працэс, які бы зберагаў мемарыі дыялогу локальна; поле requestState, якое не ўсвіядомляецца, неслаў усі даны, неабходныя для продажу дзеяння.
Проблема пад час рэальной експлуатацыі: ключы requestState
Ёжчы падтвердзіць гэта тверджэнне, створыце копіі на портамах 8000 і 8001, направіце першы запыткі на A, а следуючыя — на B. Калі спосабы зашыфравання секрэтных дадзеных разныя, другі роўень адхіляе блок стану:
{"code": -32602, "message": "Invalid or expired requestState",
"data": {"reason": "invalid_request_state"}}
Гэтыя неудачы ўсвядомлены. Для запечатывання выкарыстоўваецца AES-256-GCM унутрь SDK, таму корыстувачы не можаюць падрабатваць чы рэдагаваць даныя. Кожны працэс стварае прыватны ключ, калі толькі не задана іншая настройка, што значыць, што рэпліка B не можа расшыфраваць даныя, запечатаныя на A. Налаштавайце RequestStateSecurity(keys=[...]) і завантажайце прынеймна 32-байтовы секрэт з вашага менеджэра секрэтаў у кожную рэпліку. За наявнасці спакульнага ключа прабавы між рэплікамі будуць успешныя.
SDK таксама падтрымлівае замену ключоў без перыяда выключэння. Першы ключ у лісце запечатвае новы стан; кожны з ключоў у лісце можа яго адчыніць. Спачатку надсылайце ліст з двума ключамі: стары секрэт і новы, наступны раз зменіце парадак, а пасля таго, калі прыйдзе заканчэнне перыяда жыццёвасці (600 секунд, калі толькі не зменіце яго), адмахніцеся ад старага элемента. Калі хочаце вікорыстоўваць сэрвіс KMS заместа чыстых ключоў, выберайце codec=.
Чакліст для прыемкі ў працэўную
- Заўядзейце з параметрам
stateless_http=Trueі адмахніцеся правіл стыкі-сесый у балансэрах навантажэння. - Раздзяліце ключ
requestStateмежы рэплікамі і спланаваце ўзмаўчанне. - Зберагаце стойкі бізнес-стан у вашай сабе базе дадзэнняў. Длігія заданнія павінны вяртаць ідэнтыфікатор, які мадэль можа выкарыстоўваць пазней, альбо выкарыстоўваць розсірэнне Tasks — не пакладайцеся на з’язок для зберагання мэмарыі.
- Контролюйце на рубежы з параметрамі
Mcp-Method/Mcp-Nameдля лімітаў, спісакаў дазволу і мэтражавання па кожным інструменту. - Публікуйце падказкі для кэша: длігія TTL-вы значэнняя для стабільных спісакаў інструментоў, параметр
scope="private", калі рэзультаты разніцяюцца залежна ад карыстальніка. - Заставіце застарэлыя RPC-функцыі: заменіце логаванне на OpenTelemetry або stderr, і запланаваце завершэнне работы Sampling і Roots у межах дванаццаці месцаў.
- Апдэйтуйце сістэму аутанацыі: пераканайцеся ў правамох выдавачаў (RFC 9207) і перайдзіце на вікорыстанне дакументаў метададзэння Client ID.
Галоўныя выводы
- Рэвізія 2026–07–28 робіць кожны запіт самастойным; ідэнтыфікаторы рукапрыему і сесій знівечаны.
- Для запытанняў пад час каллу выкарыстоўваецца
input_requiredпаўз повторную спробу кліента, тады серверы ніколі не заканчваюць працу з адкрытым стрымам. - Python SDK v2 несе большую частку абавязакаў:
Resolve(...)у поўнай сувязі зElicit(...)дае можлівасць атрымання падтверджэнняя ад чалавека, што працуе як з старымі, так і з новымі кліентамі. - Повторныя спробы между рэплікамі выкарыстоўваюць спяльны ключ
requestStateпры расшырэнні сістэмы. - Маршрутызацыя па заголовкам і падказкі кэшу дазволяюць MCP працаваць як звычныя HTTP-сервісы.
Заключэнне
MCP большэй не ўсё толькі протакол дапаможніх праграм для настольных канцэлерыяў. Ён можа працаваць як будзь-яны іншы мікросервіс, што значыць, што зноў можна выкарыстоваць функціі автоматычнага масштабавання, развертвання у рэжыме «сіная/зелёная», API-шлюзы і WAF-ы. Калі вы вялікі час аперуюце серверы MCP, перайдзіце на v2 SDK, установіце ключ для спяльнага стану і адмовіцеся ад вікорыстоўвання «лепкіх» сэсій — адпаведальныя за працу зазначаць разліку.
Дапаможная літэратура
- Анунцыя пра спецыфікацыю: blog.modelcontextprotocol.io
- Тэкст протакола: modelcontextprotocol.io/specification/2026-07-28
- Артыкул пра галузі ў The Register пра тое, як MCP заўсёды пакідае свой дызайн з станам
- Афіцыйны рэпазітарыя Python SDK