MCP 2026-07-28: co się zmieniło i jak zmigrować serwer
Specyfikacja MCP 2026-07-28 czyni protokół bezstanowym: znika handshake initialize i nagłówek Mcp-Session-Id, każde żądanie niesie wersję protokołu i możliwości klienta w _meta, a serwer musi implementować server/discover. Własny serwer migruje się przez przejście na SDK v2, zastąpienie stanu sesji i żądań inicjowanych przez serwer oraz obsługę obu er protokołu naraz.
Wewnętrzny serwer MCP twojego zespołu opakowuje API wdrożeń. Trzyma cache per sesja oparty na Mcp-Session-Id, prosi użytkownika o potwierdzenie rollbacku przez elicitation/create i loguje przez logging/setLevel. W 2026-07-28 wszystkie trzy mechanizmy zostały usunięte albo oznaczone jako przestarzałe. Claude Code domyślnie negocjuje już nową rewizję z serwerami HTTP, a Codex 0.157.1 jeszcze nie, więc ten sam serwer musi odpowiadać obu. Ta strona jest dla programisty, który utrzymuje taki serwer, i dla tech leada, który zatwierdza migrację.
Sprawdzone 2026-09-26 na podstawie changelogu 2026-07-28, wpisu opiekunów o wydaniu (2026-07-28), dokumentacji TypeScript SDK 2.1.0 i Python SDK 2.2.0, Claude Code 2.1.283 oraz codex-cli 0.157.1. Obsługi protokołu MCP w Cursorze nie zweryfikowaliśmy 2026-09-26; sprawdź ją w aplikacji (zakładka Cursor). Ta strona zakłada, że masz już działający serwer zbudowany jak w Tworzenie własnego serwera MCP.
Co daje migracja serwera do MCP 2026-07-28
Dział zatytułowany „Co daje migracja serwera do MCP 2026-07-28”- Tabelę wszystkich zmian łamiących zgodność w 2026-07-28: co psują w twoim serwerze i czym je zastąpić.
- Informację, które z narzędzi Claude Code, Codex i Cursor mówią już nową rewizją i dlaczego serwer musi dalej obsługiwać starą.
- Migrację serwera w TypeScripcie krok po kroku oraz odpowiadające jej zmiany w serwerze w Pythonie.
- Test dymny, który łączy się raz na każdą erę protokołu i zatrzymuje CI, gdy którakolwiek się zepsuje.
- Cztery prompty do wklejenia: audyt serwera, migracja, wywołanie z dowolnego agenta i test obu er.
- Checklistę akceptacji, którą tech lead wkleja do pull requesta z migracją.
Co się zmieniło w MCP 2026-07-28?
Dział zatytułowany „Co się zmieniło w MCP 2026-07-28?”Opiekunowie podsumowują wydanie jako „a stateless protocol core, Multi Round-Trip Requests, header-based routing, cacheable list results, authorization hardening, a formal extensions framework, and updated Tier 1 SDKs” (blog MCP, 2026-07-28). Release candidate zamrożono 21 maja 2026. Dla autora serwera changelog sprowadza się do poniższych wierszy.
| Zmiana w 2026-07-28 | Co psuje w twoim serwerze | Czym ją zastąpić |
|---|---|---|
Usunięte initialize / notifications/initialized | Kod, który raz, przy połączeniu, odczytuje możliwości lub dane klienta | Klucze _meta w każdym żądaniu: io.modelcontextprotocol/protocolVersion, clientCapabilities i opcjonalnie clientInfo |
Dodane server/discover, które serwer musi implementować | Ręcznie pisany serwer bez obsługi tej metody | SDK v2 odpowiadają na nią za ciebie |
Usunięte Mcp-Session-Id i sesje protokołu | Stan oparty na identyfikatorze sesji: cache, kontekst autoryzacji, przepływy wieloetapowe | Jawne uchwyty zwracane przez jedno narzędzie i przekazywane jako argumenty do następnego albo podpisany requestState |
Żądania serwera sampling/createMessage, elicitation/create, roots/list zastąpione przez Multi Round-Trip Requests | Narzędzie, które w trakcie wywołania zatrzymuje się, by o coś zapytać użytkownika | Zwróć resultType: "input_required" z inputRequests; klient ponawia wywołanie z inputResponses |
Każdy wynik ma wymagane pole resultType | Ręcznie budowane wyniki JSON-RPC | "complete" dla zwykłych wyników (SDK ustawiają je same) |
Strumień HTTP GET i resources/subscribe zastąpione przez subscriptions/listen | Powiadomienia o zmianach wysyłane samodzielnym strumieniem | Jeden strumień na klienta, na który klient się zapisuje, oznaczony identyfikatorem subskrypcji |
Usunięte ping, logging/setLevel i notifications/roots/list_changed | Health checki oparte na ping, poziom logowania dla całej sesji | io.modelcontextprotocol/logLevel w _meta każdego żądania; bez tego pola serwer nie wysyła logów |
Usunięte wznawianie strumieni SSE (Last-Event-ID) | Długie wywołania, które liczyły na wznowienie zerwanego strumienia | Klient wysyła żądanie ponownie, więc wywołania narzędzi muszą być bezpieczne do powtórzenia |
Nagłówki Mcp-Method i Mcp-Name wymagane w żądaniach POST Streamable HTTP | Bramki i reguły WAF, które wycinają nieznane nagłówki | Przepuść nagłówki; routuj i limituj ruch na ich podstawie |
Wyniki list mają ttlMs i cacheScope; tools/list powinno mieć deterministyczną kolejność | Listy narzędzi budowane z nieuporządkowanej mapy | Stałe sortowanie, dzięki któremu klienci i cache promptów widzą tę samą listę |
Tasks przeniesione do rozszerzenia io.modelcontextprotocol/tasks | Kod na eksperymentalnym API tasks w rdzeniu | Odpytywanie przez tasks/get, przekazywanie danych przez tasks/update; tasks/list zniknęło |
Kod błędu „resource not found” zmieniony z -32002 na -32602 | Klienci i testy, które sprawdzają stary kod | Kod JSON-RPC Invalid Params |
Cztery funkcje są przestarzałe, ale nadal działają przez co najmniej dwanaście miesięcy zgodnie z nową polityką wycofywania: Roots, Sampling, Logging i transport HTTP+SSE. Dynamic Client Registration jest przestarzałe na rzecz Client ID Metadata Documents, a klient musi teraz weryfikować parametr iss, jeśli odpowiedź autoryzacyjna OAuth go zawiera. Nowy kod nie powinien sięgać po żadną z przestarzałych funkcji.
Framework rozszerzeń formalizuje też MCP Apps: interaktywny interfejs, który serwer zwraca do wyrenderowania w izolowanym iframe. SDK tego rozszerzenia to @modelcontextprotocol/ext-apps (npm 2.0.0, tag latest na 2026-09-26, opublikowane 2026-09-08), a jego README wymienia Claude, ChatGPT i VS Code jako obsługiwanych klientów. Klient ogłasza obsługę pod identyfikatorem rozszerzenia io.modelcontextprotocol/ui; rozszerzenie Tasks używa io.modelcontextprotocol/tasks. Rozszerzenia są wyłączone, dopóki obie strony ich nie zadeklarują, więc serwer musi wrócić do zachowania z rdzenia protokołu, gdy klient ich nie obsługuje. W codex-cli 0.157.1 funkcja enable_mcp_apps ma status under development i jest wyłączona, więc narzędzie, które zwraca App, nadal musi zwracać sensowny wynik tekstowy.
Którzy klienci mówią już MCP 2026-07-28?
Dział zatytułowany „Którzy klienci mówią już MCP 2026-07-28?”O protokole nie decyduje serwer, tylko klient. Klient obsługujący obie ery najpierw sonduje serwer (przez server/discover albo, w HTTP, pierwszym żądaniem w nowej erze) i wraca do initialize, gdy serwer nie odpowie w nowej erze. Klient znający tylko starą erę wysyła initialize i nic więcej. Klienci się różnią, więc zaplanuj obsługę obu er na tym samym endpoincie.
| Klient | Mówi 2026-07-28? | Jak wymusić erę na potrzeby testu |
|---|---|---|
| Claude Code 2.1.283 | Tak z serwerami HTTP, domyślnie w kliencie v2 (na wszystkich typach instalacji od v2.1.274). Serwery stdio zostają przy starym handshake’u, chyba że to włączysz | MCP_PROTOCOL_NEGOTIATION=auto sonduje także serwery stdio; legacy wyłącza sondowanie dla wszystkich serwerów |
| Codex 0.157.1 | Nie. mcp_2026_07_28 ma status under development i jest wyłączone (codex features list) | codex --enable mcp_2026_07_28 włącza niedokończoną funkcję na jedno uruchomienie. Traktuj to jak podgląd, nie jak bramkę |
| Cursor | Niezweryfikowane 2026-09-26 | Testuj w aplikacji po każdej aktualizacji Cursora |
Stan SDK na 2026-09-26: pakiety TypeScript v2 @modelcontextprotocol/server i @modelcontextprotocol/client są w wersji 2.1.0 (npm), pakiet v1 @modelcontextprotocol/sdk jest w wersji 1.30.1 i dostaje poprawki przez co najmniej sześć miesięcy od wydania v2, a mcp na PyPI jest w wersji 2.2.0. Opiekunowie podają w tym samym wpisie o wydaniu „close to half-a-billion downloads a month” dla SDK z Tier 1 (TypeScript, Python, Go i C#); to liczba podana przez samych opiekunów.
Czy twój serwer MCP musi się zmienić?
Dział zatytułowany „Czy twój serwer MCP musi się zmienić?”SDK nadal obsługują klientów z ery 2025, więc migracja dotyczy głównie założeń twojego własnego kodu. Znajdź wiersz, który pasuje do twojego serwera.
| Twój serwer dziś | Potrzebna praca |
|---|---|
| Bezstanowe narzędzia na TypeScript SDK v1, stdio | Przejście na v2 i zmiana na serveStdio. Niewiele: większość zrobi codemod |
Bezstanowe narzędzia na TypeScript SDK v1, Streamable HTTP z sessionIdGenerator: undefined | Przejście na v2; domyślny createMcpHandler wprost odpowiada temu ustawieniu |
| Cokolwiek oparte na identyfikatorze sesji | Przeprojektowanie tego stanu na jawne uchwyty lub requestState. To jest prawdziwy koszt |
Narzędzia wywołujące elicitInput, createMessage lub roots/list | Przepisanie ich tak, by zwracały input_required (TypeScript) albo użycie parametrów Resolve(...) (Python) |
| Transport HTTP+SSE | Przejście na Streamable HTTP. Serwer v2 nie obsługuje SSE |
Python na mcp 1.x | Dziś przypnij mcp<2 (tak radzi samo SDK; 1.29.1 to ostatnie 1.x na 2026-09-26), potem przenieś kod na MCPServer (niżej) |
| Ręcznie pisany JSON-RPC bez SDK | Samodzielna implementacja server/discover, parsowania _meta, resultType i nowych nagłówków albo przejście na SDK |
Zmigruj serwer MCP w TypeScripcie do 2026-07-28
Dział zatytułowany „Zmigruj serwer MCP w TypeScripcie do 2026-07-28”TypeScript SDK dzieli migrację na dwie części. Codemod przenosi kod z pakietu v1 na pakiety v2. Obsługa 2026-07-28 jest potem osobnym, jawnym krokiem, bo serwer v2 zbudowany ręcznie i podłączony wprost do transportu nadal mówi protokołem 2025, dla którego go napisano.
-
Spisz, co serwer zakłada. Zanim cokolwiek zmienisz, uruchom prompt audytowy poniżej. Potrzebujesz listy wszystkich odczytów identyfikatora sesji, wszystkich żądań inicjowanych przez serwer, wszystkich wywołań logowania i wszystkich narzędzi, których wynik zależy od wcześniejszego wywołania.
-
Uruchom codemod w katalogu głównym pakietu, żeby przepisał też importy w testach i skryptach:
Okno terminala npx @modelcontextprotocol/codemod@latest v1-to-v2 .Codemod wykonuje mechaniczne zmiany nazw z
@modelcontextprotocol/sdkna@modelcontextprotocol/serveri@modelcontextprotocol/client. Nie wprowadza za ciebie obsługi 2026-07-28. -
Przejdź na punkt wejścia per żądanie. Buduj serwer w funkcji fabrykującej, bo ścieżka 2026 tworzy nową instancję na każde żądanie (HTTP) lub na każde połączenie (stdio).
// v1: new McpServer(...) + await server.connect(new StdioServerTransport())import { McpServer } from '@modelcontextprotocol/server';import { serveStdio } from '@modelcontextprotocol/server/stdio';import * as z from 'zod/v4';function buildServer() {const server = new McpServer({ name: 'deploy-tools', version: '2.0.0' });server.registerTool('get_release',{ description: 'Read one release by id', inputSchema: z.object({ releaseId: z.string() }) },async ({ releaseId }) => ({ content: [{ type: 'text', text: await readRelease(releaseId) }] }),);return server;}serveStdio(buildServer); // obsługuje 2026-07-28 i domyślnie także połączenia z ery 2025Dla HTTP użyj
createMcpHandler(buildServer)z@modelcontextprotocol/server. We frameworkach Node opakuj go przeztoNodeHandlerz@modelcontextprotocol/node, na przykładapp.all('/mcp', toNodeHandler(handler)). Domyślnelegacy: 'stateless'obsługuje też klientów z ery 2025, osobno dla każdego żądania. -
Zastąp stan sesji. Cache albo kontekst autoryzacji oparty na identyfikatorze sesji nie ma już klucza. Niech narzędzie, które tworzy stan, zwraca jawny uchwyt (na przykład
releaseId), a kolejne narzędzie przyjmuje go jako argument; model widzi uchwyt i sam go przekazuje dalej. Dla przepływu wieloetapowego w ramach jednego wywołania użyjrequestStatei podpisz go przezcreateRequestStateCodec({ key }), bo klient odsyła go z powrotem i jest to niezaufane wejście. Klucz musi mieć co najmniej 32 bajty i pochodzić z magazynu sekretów, nigdy z kodu źródłowego. -
Zastąp żądania inicjowane przez serwer. Tam, gdzie narzędzie wywoływało
elicitInputlubcreateMessage, teraz zwracainputRequired(...)i przy ponowieniu czyta odpowiedź zctx.mcpReq.inputResponses. Ten sam handler nadal działa dla klientów z ery 2025 dzięki warstwie zgodności (legacy shim) w SDK. -
Wyprowadź logowanie poza protokół. Loguj na
stderr(nigdy nastdoutw stdio) albo do OpenTelemetry.ctx.mcpReq.log()nic nie wysyła w żądaniu z ery 2026, jeśli klient nie ustawiłlogLevelw_meta, a klient z SDK domyślnie go nie ustawia. -
Posortuj
tools/list. Rejestruj narzędzia w stałej kolejności, żeby lista była identyczna przy każdym żądaniu, a cache po stronie klienta pozostawał ważny. -
Jeśli musisz, utrzymaj stare wdrożenie z sesjami. Postaw restrykcyjny handler (
createMcpHandler(buildServer, { legacy: 'reject' })) za rozgałęzieniem naisLegacyRequest(request)i kieruj ruch z ery 2025 do istniejącej konfiguracji v1, dopóki jej klienci nie znikną.
Zmigruj serwer MCP w Pythonie do 2026-07-28
Dział zatytułowany „Zmigruj serwer MCP w Pythonie do 2026-07-28”mcp 2.x z PyPI implementuje 2026-07-28 i obsługuje obie ery z tego samego streamable_http_app() lub serwera stdio, bez żadnej flagi. Zmiany dotyczą twojego kodu:
pip install mcpinstaluje teraz 2.x. W każdym projekcie, który nie jest jeszcze zmigrowany, przypnijmcp<2, bo inaczej build bez przypięcia sam zrobi aktualizację.FastMCPnazywa się terazMCPServer:from mcp.server import MCPServer. Opcje transportu, takie jakhost,portistateless_http, przechodzą z konstruktora dorun()i metod budujących aplikację.ctx.elicit()ictx.session.create_message()rzucająNoBackChannelErrorw połączeniu z ery 2026. Przenieś pytanie do parametru oznaczonegoResolve(...), który zada je mechanizmem dostępnym w danym połączeniu.- Klucz do pieczętowania
request_statejest domyślnie generowany osobno w każdym procesie. Za load balancerem przekażRequestStateSecurity(keys=[...]), żeby każda replika mogła zweryfikować ponowienie. - Przewodnik migracji v2 podaje, że eksperymentalna obsługa Tasks została usunięta. Dopóki nie potwierdzisz obsługi rozszerzenia Tasks w przypiętej wersji SDK, trzymaj długie operacje za własnym identyfikatorem zadania i narzędziem do sprawdzania statusu.
Udowodnij, że serwer działa w obu erach protokołu
Dział zatytułowany „Udowodnij, że serwer działa w obu erach protokołu”Zielona kropka w jednym kliencie dowodzi działania jednej ery. Bramka, która ma znaczenie, łączy się dwa razy: raz przypięta do 2026-07-28, raz przez handshake z 2025. Zatrzymuje build, gdy którekolwiek połączenie nie zwraca narzędzi, znane wywołanie się psuje albo ery udostępniają różne nazwy narzędzi. Tryb przypięcia (pin) nigdy nie wraca do starej ery, więc serwer, który po cichu obsługuje tylko stary protokół, oblewa pierwsze połączenie zamiast przejść.
// scripts/mcp-era-smoke.mjs (uruchom: MCP_URL=http://localhost:3000/mcp node scripts/mcp-era-smoke.mjs)import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const url = new URL(process.env.MCP_URL ?? 'http://localhost:3000/mcp');const modes = [{ pin: '2026-07-28' }, 'legacy'];
const names = [];
for (const mode of modes) { const client = new Client({ name: 'era-smoke', version: '1.0.0' }, { versionNegotiation: { mode } }); await client.connect(new StreamableHTTPClientTransport(url)); const { tools } = await client.listTools(); console.log(`${client.getProtocolEra()}: ${tools.length} tools`); if (tools.length === 0) process.exitCode = 1; names.push(JSON.stringify(tools.map((t) => t.name).sort())); try { // znane wywołanie z identyfikatorem z fixture musi działać w obu erach const result = await client.callTool({ name: 'get_release', arguments: { releaseId: process.env.FIXTURE_ID ?? 'r-102' }, }); if (result.isError) process.exitCode = 1; } catch (err) { console.error(`${client.getProtocolEra()}: get_release failed`, err); process.exitCode = 1; } await client.close();}
// obie ery muszą udostępniać te same nazwy narzędziif (names[0] !== names[1]) { console.error('listy narzędzi różnią się między erami'); process.exitCode = 1;}Oczekiwany wynik to jedna linia modern i jedna linia legacy z tą samą liczbą narzędzi oraz kod wyjścia 0; nieudane wywołanie get_release albo różne nazwy narzędzi ustawiają kod wyjścia 1. Błąd ERA_NEGOTIATION_FAILED w pierwszej linii oznacza, że serwer nigdy nie zaoferował 2026-07-28. W testach bez gniazda sieciowego przekaż do transportu metodę fetch handlera (fetch: (u, init) => handler.fetch(new Request(u, init))), która obsługuje żądanie w tym samym procesie. W Pythonie Client(mcp) podłączony do obiektu serwera domyślnie negocjuje 2026-07-28; drugiego klienta testowego przypnij do mode="legacy", żeby pokryć starą ścieżkę.
Żeby ręcznie sprawdzić działający serwer bez pisania kodu, użyj CLI MCP Inspectora (@modelcontextprotocol/inspector 2.8.0 na 2026-09-26). Bez dodatkowej flagi łączy się w erze legacy, więc za każdym razem podawaj --protocol-era jawnie:
npx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp --transport http --protocol-era modern --method tools/listnpx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp --transport http --protocol-era legacy --method tools/listOba polecenia powinny wypisać te same nazwy narzędzi. Dla serwera stdio zamień URL i --transport http na polecenie uruchamiające, na przykład node build/index.js.
Następnie zarejestruj serwer w każdym obsługiwanym agencie i wywołaj jedno narzędzie tylko do odczytu. Polecenia różnią się między narzędziami:
Zarejestruj lokalny serwer, a potem porównaj obie ery. Claude Code domyślnie sonduje serwery HTTP pod kątem 2026-07-28:
claude mcp add --transport http deploy-tools http://localhost:3000/mcpclaude mcp list # domyślnie: serwery HTTP w 2026-07-28, jeśli je oferująMCP_PROTOCOL_NEGOTIATION=legacy claude mcp list # wymuś starszy handshakeMCP_PROTOCOL_NEGOTIATION=auto claude # sonduj też serwery stdio, potem wywołaj narzędzieJeśli serwer działa tylko z legacy, błąd leży w jego ścieżce 2026. Serwer stdio, który jest też kanałem, nie zostaje zarejestrowany jako kanał, gdy wynegocjuje 2026-07-28, bo ta rewizja nie przenosi wiadomości kanałów.
Codex 0.157.1 łączy się starym protokołem, więc zwykłe uruchomienie testuje twoją ścieżkę legacy:
codex mcp add deploy-tools --url http://localhost:3000/mcpcodex mcp listcodex exec "Call the get_release tool with releaseId r-102 and print the raw result."Żeby na jedno uruchomienie wypróbować niedokończonego klienta 2026-07-28, dodaj --enable mcp_2026_07_28. Funkcja ma status under development, więc błąd w tym trybie nie musi jeszcze oznaczać błędu w twoim serwerze.
Dodaj serwer do .cursor/mcp.json (ten format pochodzi z README dostawców i nie był testowany w Cursorze):
{ "mcpServers": { "deploy-tools": { "url": "http://localhost:3000/mcp" } }}Nie zweryfikowaliśmy 2026-09-26, którą rewizję protokołu negocjuje Cursor. Po każdej aktualizacji Cursora otwórz ustawienia MCP, sprawdź, czy serwer pokazuje swoje narzędzia, i poproś agenta o wywołanie jednego narzędzia tylko do odczytu. Bramką zostaje test dymny obu er, bo nie zależy od wersji Cursora.
W każdym z trzech agentów ten prompt dowodzi, że narzędzie działa od początku do końca, i pokazuje, co dostał model:
Zobaczysz dokładnie jedno wywołanie get_release i rekord wydania. Prośba o potwierdzenie albo runda input_required przy narzędziu tylko do odczytu oznacza, że narzędzie żąda danych, których nie powinno potrzebować.
Zatwierdź migrację serwera MCP
Dział zatytułowany „Zatwierdź migrację serwera MCP”Migracja jest skończona wtedy, gdy poniższe dowody są dołączone do pull requesta, a nie wtedy, gdy kod się kompiluje. Dostarcza je właściciel serwera; zatwierdza tech lead odpowiedzialny za konfigurację MCP zespołu.
- SDK jest w wersji v2:
@modelcontextprotocol/server2.x albomcp2.x z PyPI z jawnym zakresem wersji. - Żaden kod nie czyta identyfikatora sesji; stan między wywołaniami przechodzi jako jawne uchwyty lub podpisany
requestState. - Żadne narzędzie nie wysyła żądania inicjowanego przez serwer na ścieżce 2026; każde pytanie zwraca
input_required. - Test dymny obu er przechodzi w CI, z tymi samymi nazwami narzędzi w obu erach.
-
tools/listzwraca tę samą kolejność przy każdym wywołaniu. - Logi trafiają na
stderrlub do OpenTelemetry; w stdio nic nie pisze nastdout. - Bramki, proxy i reguły WAF przepuszczają
Mcp-Method,Mcp-NameiMCP-Protocol-Version. - Każdy obsługiwany agent został sprawdzony: Claude Code w obu erach, Codex w ustawieniu domyślnym, Cursor ręcznie.
- Rollback jest opisany: tag poprzedniego wydania, a dla użytkowników Claude Code
MCP_PROTOCOL_NEGOTIATION=legacy(lubMCP_SDK_GENERATION=v1) jako furtka po stronie klienta.
Co 2026-07-28 zmienia w koszcie kontekstu
Dział zatytułowany „Co 2026-07-28 zmienia w koszcie kontekstu”Rewizja nie zmniejsza definicji narzędzi, więc liczba tokenów, którą każde narzędzie zajmuje w kontekście agenta, zostaje taka sama. Zmienia się stabilność. tools/list w deterministycznej kolejności, z podpowiedzią świeżości ttlMs, pozwala klientowi zapisać katalog w cache i utrzymuje stabilny cache promptów u dostawcy modelu między kolejnymi połączeniami. Serwer, który przy każdym żądaniu tasuje narzędzia, psuje jedno i drugie. Zmierz stan przed zmianą i po niej przez /context w Claude Code, a narzędzia przytnij wzorcami z ograniczania kosztu tokenów MCP.
Co psuje się po migracji do MCP 2026-07-28?
Dział zatytułowany „Co psuje się po migracji do MCP 2026-07-28?”ERA_NEGOTIATION_FAILED: ... did not offer pinned protocol version 2026-07-28. Serwer nadal odpowiada tylko na initialize. Sprawdź, czy punktem wejścia jest serveStdio lub createMcpHandler, a nie bezpośrednie server.connect(...), i czy żadne proxy przed serwerem nie odpowiada na server/discover stroną błędu.
Logi przestały się pojawiać. W żądaniu z ery 2026 serwer wysyła notifications/message tylko wtedy, gdy klient umieścił logLevel w _meta. Loguj na stderr lub do OpenTelemetry, zamiast polegać na logowaniu w protokole.
NoBackChannelError (Python) albo prośba o potwierdzenie, która nigdy się nie pokazuje. Narzędzie nadal wysyła elicitation inicjowane przez serwer. W TypeScripcie zwróć input_required, w Pythonie przenieś pytanie do parametru Resolve(...).
Ponowienia kończą się błędem invalid params za load balancerem. Każda replika podpisuje requestState własnym kluczem z procesu, więc ponowienie, które trafi na inną replikę, nie przechodzi weryfikacji. Skonfiguruj jeden wspólny zestaw kluczy (createRequestStateCodec({ key }) w TypeScripcie, RequestStateSecurity(keys=[...]) w Pythonie) i rotuj go jak każdy inny sekret.
Żądania kończą się HeaderMismatch (-32020) albo w ogóle nie docierają do serwera. Bramka przepisała lub usunęła Mcp-Method albo Mcp-Name. Przepuszczaj oba nagłówki i pozwól bramce routować na ich podstawie. Jeśli serwer wywołują klienci działający w przeglądarce, dodaj Mcp-Method, Mcp-Name i MCP-Protocol-Version do Access-Control-Allow-Headers, bo przeglądarka blokuje niestandardowe nagłówki, na które nie zezwala preflight CORS.
Serwer działa w Codeksie, a w Claude Code nie. Codex mówi protokołem 2025, Claude Code z serwerami HTTP mówi 2026. Potwierdź to przez MCP_PROTOCOL_NEGOTIATION=legacy claude, co jest też obejściem dla użytkowników, a ścieżkę 2026 napraw z pomocą testu dymnego opisanego wyżej.
Serwer stdio w Ruście kończy działanie, gdy tylko klient się połączy. Serwery na Rust SDK (rmcp) kończą pracę na każde żądanie przed initialize, także na sondę server/discover. Dlatego klient TypeScript sonduje w osobnym, jednorazowym procesie. Jeśli piszesz własnego klienta, sonduj w ten sam sposób albo przypnij dla tego serwera mode: 'legacy'.
Klient nadal sprawdza stary kod błędu zasobu. „Resource not found” to teraz -32602, a nie -32002. Zaktualizuj testy, które sprawdzają starą liczbę.