Dwa poprzednie artykuły opisywały role i prymitywy MCP w kategoriach architektury. Ten artykuł schodzi poziom niżej — do bajtów. Do tego co faktycznie leci między procesami gdy agent wywołuje narzędzie.
Rozumienie tej warstwy ma jeden konkretny cel praktyczny: możesz debugować serwer MCP bez specjalistycznych narzędzi, wyłącznie przez curl i czytanie JSON. Gdy coś nie działa — wiesz gdzie szukać.
JSON-RPC 2.0 — specyfikacja w pięciu minutach
JSON-RPC jest protokołem prostym do bólu. Specyfikacja 2.0 ma kilka stron i opisuje trzy typy wiadomości.
Żądanie (Request)
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "get_term",
"arguments": { "slug": "agent-readiness" }
}
}
Cztery pola: jsonrpc zawsze "2.0" — tak rozpoznajesz wersję protokołu. id — unikalny numer żądania w ramach sesji, dowolna liczba lub string. method — co chcesz zrobić. params — parametry metody, opcjonalne.
Pole id jest kluczowe: odpowiedź będzie miała ten sam id — tak klient wie że ta odpowiedź dotyczy jego konkretnego żądania, a nie innego które wysłał wcześniej.
Odpowiedź sukces (Response)
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{
"type": "text",
"text": "Agent-readiness — gotowość agentowa..."
}
]
}
}
Odpowiedź zawiera ten sam id co żądanie i albo result (sukces) albo error (błąd) — nigdy oba naraz.
Odpowiedź błąd (Error Response)
{
"jsonrpc": "2.0",
"id": 42,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"detail": "Brakuje wymaganego parametru 'slug'"
}
}
}
Kody błędów JSON-RPC:
-32700— Parse error (nieprawidłowy JSON)-32600— Invalid Request (poprawny JSON, złe żądanie)-32601— Method not found-32602— Invalid params-32603— Internal error-32000do-32099— zarezerwowane dla błędów serwera (MCP używa tego zakresu)
Powiadomienie (Notification)
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Powiadomienie nie ma id. Odbiorca nie odpowiada. W MCP używane do informowania o zdarzeniach: klient powiadamia serwer że inicjalizacja zakończona, serwer powiadamia klienta że resource się zmienił.
Pełna sesja MCP — sekwencja wiadomości od startu do zamknięcia
To jest wiedza której nie ma w większości tutoriali. Pełna sesja MCP ma ściśle określoną kolejność wiadomości. Pominięcie któregokolwiek kroku daje błąd.
Krok 1: Initialize
Klient wysyła pierwsze żądanie — zawsze initialize:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.5.0"
}
}
}
Klient mówi serwerowi: kim jestem, jaką wersję protokołu obsługuję, jakie mam capabilities.
Krok 2: Odpowiedź serwera na initialize
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
},
"serverInfo": {
"name": "webflux-slownik",
"version": "2.2.0"
}
}
}
Serwer odpowiada: jaką wersję protokołu obsługuje, jakie ma capabilities, kim jest. To jest capability negotiation o którym pisałem w artykule 1.
Ważne: jeśli klient i serwer mają różne protocolVersion — klient powinien użyć niższej wersji (backward compatibility). Jeśli wersje są kompletnie niekompatybilne — połączenie kończy się błędem.
Krok 3: Powiadomienie initialized
Po otrzymaniu odpowiedzi na initialize klient wysyła powiadomienie — bez id, serwer nie odpowiada:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Dopiero po tym powiadomieniu sesja jest aktywna i można wysyłać właściwe żądania. Serwery które nie respektują tego kroku często dają tajemnicze błędy.
Krok 4: Właściwa praca
Teraz klient może wysyłać tools/list, tools/call, resources/list, resources/read etc. Kolejność żądań roboczych jest dowolna.
// Pobierz listę tools
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
// Wywołaj tool
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_term","arguments":{"query":"MCP"}}}
// Odczytaj resource
{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"memo://notes/projekt"}}
Krok 5: Zamknięcie sesji
Nie ma dedykowanej metody disconnect. Klient po prostu zamyka połączenie. Przy transportach trwałych (Streamable HTTP z sesją) serwer może wysyłać ping żeby sprawdzić czy klient nadal żyje.
Debugowanie przez curl — kompletny przykład
Webflux Słownik MCP działa na Streamable HTTP. Pełna sesja przez curl:
# Krok 1 — Initialize
curl -X POST https://webflux.pl/wp-json/webflux/v1/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "curl-test", "version": "1.0"}
}
}'
Odpowiedź powinna zawierać serverInfo z nazwą i wersją serwera. Jeśli dostajesz błąd — sprawdź URL, nagłówki Content-Type i czy serwer w ogóle odpowiada na POST.
# Krok 2 — Lista tools (w prawdziwej sesji po initialized, tu dla testu)
curl -X POST https://webflux.pl/wp-json/webflux/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}'
# Krok 3 — Wywołanie search_term
curl -X POST https://webflux.pl/wp-json/webflux/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "search_term",
"arguments": {"query": "context engineering"}
}
}'
Co czytać w odpowiedzi:
Gdy result jest w odpowiedzi — tool wykonał się poprawnie. Gdy error — patrz na code i message. Gdy brak odpowiedzi w ogóle — problem z transportem lub serwerem.
Transport-agnostic — co to znaczy w praktyce
JSON-RPC jest transport-agnostic. Te same wiadomości JSON działają przez różne mechanizmy transportu. MCP definiuje dwa:
stdio — wiadomości przez standardowe wejście/wyjście procesu. Klient uruchamia serwer jako podproces i komunikuje się przez stdin/stdout. Każda wiadomość JSON to osobna linia zakończona \n.
klient → serwer: {"jsonrpc":"2.0","id":1,"method":"initialize",...}\n
serwer → klient: {"jsonrpc":"2.0","id":1,"result":{...}}\n
Używany dla lokalnych serwerów MCP uruchomionych na maszynie użytkownika. Claude Desktop z serwerem GitHub — stdio.
Streamable HTTP — wiadomości przez HTTP POST. Jeden endpoint przyjmuje wszystkie wiadomości. Opcjonalnie serwer może streamować odpowiedzi przez SSE (Server-Sent Events) gdy odpowiedź jest długa lub wieloczęściowa.
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc":"2.0","id":1,"method":"initialize",...}
Używany dla zdalnych serwerów MCP dostępnych przez sieć. Webflux Słownik — Streamable HTTP. Każdy serwis który chce być dostępny przez internet — Streamable HTTP.
Dlaczego to rozróżnienie ważne: ten sam kod serwera który obsługuje JSON-RPC może działać przez oba transporty bez zmiany logiki biznesowej. Transport to tylko „kabel” — wiadomości są identyczne.
Nagłówki HTTP przy Streamable HTTP
Trzy nagłówki które musisz wysyłać przy każdym żądaniu do serwera MCP przez HTTP:
Content-Type: application/json # ciało żądania to JSON
Accept: application/json, text/event-stream # akceptuję JSON lub SSE stream
Jeśli serwer zwraca Content-Type: text/event-stream zamiast application/json — streamuje odpowiedź. Każda linia SSE zaczyna się od data: i zawiera JSON:
data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}
data: [DONE]
Biblioteki curl obsługują SSE domyślnie — po prostu czytasz output do końca.
Opcjonalny nagłówek sesji — gdy serwer wspiera sesje MCP, pierwsza odpowiedź może zawierać:
Mcp-Session-Id: abc123xyz
Dołączaj ten header do kolejnych żądań w tej sesji. Pozwala serwerowi zachować stan między requestami (np. subskrypcje resources).
Błędy które zobaczysz najczęściej
-32601 Method not found — wywołujesz metodę której serwer nie obsługuje. Sprawdź capabilities z initialize — czy serwer zadeklarował obsługę prymitywu którego używasz.
-32602 Invalid params — złe parametry. Sprawdź inputSchema toola — czy wszystkie required pola są w arguments, czy typy się zgadzają.
-32603 Internal error — błąd po stronie serwera. Sprawdź logi serwera — tam będzie stack trace.
HTTP 401/403 — problem z autoryzacją. Serwer wymaga tokenu lub klucza API który nie jest przekazywany. Sprawdź czy nagłówki auth są poprawne.
HTTP 400 z „Not an MCP request” — wysyłasz pod właściwy URL ale bez wymaganego nagłówka lub z nieprawidłowym JSON. Sprawdź Content-Type: application/json.
Brak odpowiedzi (timeout) — serwer przyjął request ale nie odpowiada. Może być zapętlony, może czekać na zewnętrzny zasób. Sprawdź logi, dodaj timeout do curl: curl --max-time 30 ...
JSON-RPC poza MCP — ten sam protokół, inne zastosowania
Znajomość JSON-RPC jest szersza niż tylko MCP. A2A (Agent-to-Agent Protocol od Google) używa JSON-RPC. Language Server Protocol (LSP) — ten który napędza autocomplete w VS Code — używa JSON-RPC. Debug Adapter Protocol (DAP) — JSON-RPC. Wiele wewnętrznych API mikroserwisów — JSON-RPC.
Inwestycja w rozumienie jednego prostego protokołu opłaca się szerzej niż w jednym kontekście.
W następnym artykule: wiemy jak wyglądają wiadomości. Czas zobaczyć jak te wiadomości podróżują — ewolucja transportu MCP od stdio przez SSE do Streamable HTTP i dlaczego ta zmiana miała znaczenie dla każdego kto buduje serwery dostępne przez internet.
Pojęcia ze słownika: JSON-RPC · MCP · SSE · Streamable HTTP · OAuth dla agentów











