JSON-RPC pod spodem — co faktycznie leci między klientem MCP a serwerem

przez Łukasz | cze 5, 2026

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)

json
{
  "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)

json
{
  "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)

json
{
  "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
  • -32000 do -32099 — zarezerwowane dla błędów serwera (MCP używa tego zakresu)

Powiadomienie (Notification)

json
{
  "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:

json
{
  "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

json
{
  "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:

json
{
  "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.

json
// 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:

bash
# 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.

bash
# 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"
  }'
bash
# 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:

bash
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

Spis treści

Kiedy nie budować agenta

Kiedy nie budować agenta

Cały ten hub uczy, jak budować agenty. Ten wpis jest o tym, że najczęściej nie powinieneś. Jest taka pokusa, która przychodzi po przeczytaniu kilku tekstów o agentach: zbudujmy agenta. Do obsługi maili. Do raportów. Do tego procesu, który teraz robi się ręcznie. Agent...