Transport MCP: stdio, SSE i Streamable HTTP — historia ewolucji i wybór dla własnego serwera

przez Łukasz | cze 5, 2026

Poprzedni artykuł pokazał że wiadomości MCP to JSON-RPC — zawsze ta sama struktura, niezależnie od kontekstu. Teraz pytanie: jak te wiadomości fizycznie podróżują między klientem a serwerem?

To jest właśnie transport. Protokół definiuje co wysyłasz. Transport definiuje jak.

MCP przeszło przez trzy transporty: stdio (nadal aktywny), SSE (deprecjonowany), Streamable HTTP (aktualny standard dla połączeń sieciowych). Każdy powstał z konkretnego powodu i każdy rozwiązuje inne problemy. Rozumienie tej historii tłumaczy dlaczego Twój stary serwer przestał działać po aktualizacji klienta — i co z tym zrobić.

Transport 1: stdio — lokalny, prosty, niezastąpiony

Stdio (standard input/output) to najprostszy możliwy transport. Klient uruchamia serwer MCP jako podproces i komunikuje się z nim przez stdin i stdout — standardowe wejście i wyjście procesu.

Jak to działa fizycznie:

Claude Desktop
    │
    ├─ uruchamia: node /path/to/github-mcp/index.js
    │
    │  stdin (klient → serwer):
    │  {"jsonrpc":"2.0","id":1,"method":"initialize",...}\n
    │
    │  stdout (serwer → klient):
    │  {"jsonrpc":"2.0","id":1,"result":{...}}\n
    │
    └─ każda wiadomość to jedna linia JSON zakończona \n

Każda wiadomość JSON-RPC to osobna linia. Linia przychodzi na stdin → serwer przetwarza → odpowiedź wychodzi na stdout. Prosto jak rura.

Konfiguracja w Claude Desktop (claude_desktop_config.json):

json
{
  "mcpServers": {
    "github": {
      "command": "node",
      "args": ["/Users/lukasz/.npm-global/lib/node_modules/@modelcontextprotocol/server-github/dist/index.js"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
      }
    },
    "webflux-slownik": {
      "command": "python",
      "args": ["/path/to/webflux-mcp-client.py"],
      "env": {}
    }
  }
}

Claude Desktop uruchamia każdy serwer jako osobny podproces. Gdy Claude Desktop się zamyka — podprocesy są kończone.

Zalety stdio:

Zero infrastruktury sieciowej. Nie potrzebujesz portu, nie potrzebujesz serwera HTTP, nie martwisz się o CORS ani TLS. Serwer uruchamiany jest tylko gdy klient go potrzebuje.

Naturalny sandbox. Serwer działa z uprawnieniami użytkownika który uruchomił klienta. Nie możesz przypadkowo wystawić go na sieć.

Proste debugowanie. Możesz uruchomić serwer ręcznie, wklejać JSON na stdin i czytać stdout:

bash
# Uruchom serwer i wyślij ręcznie inicjalizację
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node index.js

Wady stdio:

Działa tylko lokalnie. Serwer musi być zainstalowany na maszynie użytkownika. Nie możesz hostować serwera stdio w chmurze i udostępniać go wielu użytkownikom.

Każdy użytkownik instaluje osobno. Dystrybucja wymaga package managera (npm, pip) i lokalnej instalacji.

Transport MCP: stdio, SSE i Streamable HTTP — historia ewolucji i wybór dla własnego serwera

Transport MCP: stdio, SSE i Streamable HTTP — historia ewolucji i wybór dla własnego serwera

Transport 2: SSE — pierwsze podejście do sieci (i dlaczego nie wystarczyło)

SSE (Server-Sent Events) to standard W3C który pozwala serwerowi wysyłać strumień danych do klienta przez długotrwałe połączenie HTTP. Używany od lat do streamingu odpowiedzi modeli AI — ten efekt „tekst pojawia się słowo po słowie” gdy piszesz do ChatGPT lub Claude to jest właśnie SSE.

MCP w pierwszej wersji (2024-11-05) używał SSE jako transport dla połączeń sieciowych. Pomysł był prosty: serwer wystawia endpoint SSE, klient się podłącza i dostaje stream wiadomości.

Problem: SSE jest jednokierunkowe.

SSE to kanał serwer → klient. Serwer może wysyłać dane do klienta w dowolnym momencie. Ale klient który chce wysłać coś do serwera — musi użyć osobnego żądania HTTP POST.

Pierwsza wersja MCP z SSE wymagała więc dwóch połączeń naraz:

Klient ──────── GET /sse ────────▶ Serwer
        ◀── stream zdarzeń ───────
        
Klient ──────── POST /message ──▶ Serwer
        ◀──── odpowiedź HTTP ─────

Jeden kanał do odbierania (SSE), drugi do wysyłania (POST). Serwer musiał korelować żądania POST z sesją SSE przez identyfikator sesji w URL lub nagłówku.

Dlaczego to było problematyczne w produkcji:

Loadbalancery i CDN nie lubią długotrwałych połączeń. SSE utrzymuje otwarte połączenie HTTP dopóki sesja trwa. Typowe timeouty infrastruktury (60-120 sekund) przerywają połączenie. Serverless (AWS Lambda, Vercel Functions) ma twarde limity czasu wykonania — SSE nie działa.

Skalowanie poziome jest trudne. Gdy masz wiele instancji serwera za loadbalancerem, żądanie POST może trafić na inną instancję niż ta która obsługuje połączenie SSE tej sesji. Potrzebujesz sticky sessions lub zewnętrznego brokera — dodatkowa złożoność.

Transport 3: Streamable HTTP — aktualny standard

Specyfikacja MCP 2025-03-26 (ogłoszona w marcu 2025, obowiązująca od Webflux MCP v2.2.0) deprecjonuje SSE jako transport MCP i wprowadza Streamable HTTP.

Kluczowa zmiana: jeden endpoint POST obsługuje całą komunikację dwukierunkową.

Klient ──── POST /mcp ────▶ Serwer
            (wiadomość JSON-RPC)
       ◀─── odpowiedź ─────
            (JSON lub SSE stream)

Klient wysyła żądanie HTTP POST z wiadomością JSON-RPC w ciele. Serwer odpowiada — i tutaj jest elastyczność Streamable HTTP.

Dwa tryby odpowiedzi:

Tryb JSON — serwer odpowiada normalnym JSON-em w ciele odpowiedzi HTTP. Używany gdy wynik jest gotowy od razu:

POST /mcp
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

HTTP/1.1 200 OK
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}

Tryb SSE stream — serwer odpowiada strumieniem SSE gdy wynik jest długi lub wieloczęściowy. Używany gdy serwer chce wysyłać progresywne aktualizacje:

POST /mcp
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{...}}

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Analizuję..."}]}}

data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Analiza zakończona. Wynik: ..."}]}}

data: [DONE]

Klient deklaruje że akceptuje oba formaty przez nagłówek Accept: application/json, text/event-stream. Serwer wybiera który zwrócić.

Opcjonalne sesje:

Streamable HTTP obsługuje opcjonalne sesje przez nagłówek Mcp-Session-Id. Przy pierwszym żądaniu serwer może zwrócić identyfikator sesji:

HTTP/1.1 200 OK
Mcp-Session-Id: sess_abc123
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"result":{...}}

Klient dołącza ten nagłówek do kolejnych żądań. Pozwala serwerowi zachować stan (np. subskrypcje resources) między requestami. Ale sesje są opcjonalne — serwer który nie potrzebuje stanu może je ignorować i obsługiwać każdy request niezależnie.

Dlaczego Streamable HTTP jest lepsze:

Standard request-response HTTP — działa ze wszystkimi loadbalancerami, CDN, reverse proxy bez specjalnej konfiguracji.

Serverless-friendly — każdy request to osobne wywołanie funkcji. AWS Lambda, Vercel, Cloudflare Workers — wszystkie działają.

Łatwiejsze debugowanie — zwykły POST, zwykła odpowiedź. curl działa bez żadnych sztuczek.

Porównanie transportów

                    stdio           SSE (stary)      Streamable HTTP
─────────────────────────────────────────────────────────────────────
Lokalne serwery     ✅ idealne      ❌ niepotrzebne   ✅ możliwe
Zdalne serwery      ❌ niemożliwe   ✅ działało       ✅ idealne
Serverless          ❌ niemożliwe   ❌ nie działa     ✅ działa
Loadbalancer        N/A             ⚠️ sticky sess.  ✅ bezproblemowe
CDN                 N/A             ❌ problem        ✅ działa
Debugowanie curl    ⚠️ ręczny pipe  ⚠️ dwa połącz.  ✅ prosty POST
Wsteczna komp.      ✅ zawsze       ⚠️ deprecjonowane ✅ aktualny std.
WordPress REST API  N/A             ⚠️ trudne         ✅ naturalne

Webflux Słownik jako przykład Streamable HTTP

Webflux MCP Server v2.2.0 (aktualizowany właśnie ze względu na zmianę specyfikacji) to standardowy endpoint REST API WordPressa który obsługuje Streamable HTTP:

POST https://webflux.pl/wp-json/webflux/v1/mcp

Implementacja w PHP — jeden endpoint obsługuje wszystkie metody MCP:

php
// Uproszczony schemat endpointu
add_action('rest_api_init', function() {
    register_rest_route('webflux/v1', '/mcp', [
        'methods'  => 'POST',
        'callback' => 'handle_mcp_request',
        'permission_callback' => '__return_true',
    ]);
});

function handle_mcp_request(WP_REST_Request $request) {
    $body = $request->get_json_params();
    $method = $body['method'] ?? '';
    $id = $body['id'] ?? null;
    
    // Capability negotiation
    if ($method === 'initialize') {
        return new WP_REST_Response([
            'jsonrpc' => '2.0',
            'id'      => $id,
            'result'  => [
                'protocolVersion' => '2025-03-26',
                'capabilities'    => ['tools' => new stdClass()],
                'serverInfo'      => ['name' => 'webflux-slownik', 'version' => '2.2.0'],
            ]
        ]);
    }
    
    // Routing metod
    if ($method === 'tools/list')  return handle_tools_list($id);
    if ($method === 'tools/call')  return handle_tools_call($id, $body['params'] ?? []);
    
    // Powiadomienia (brak id, brak odpowiedzi)
    if (!$id) return new WP_REST_Response(null, 204);
    
    return mcp_error($id, -32601, 'Method not found');
}

Żaden specjalny framework MCP — zwykły WordPress REST API endpoint który rozumie JSON-RPC.

Migracja: co zrobić jeśli masz serwer SSE

Jeśli zbudowałeś serwer MCP przed marcem 2025 i używał SSE — prawdopodobnie część klientów przestała się z nim łączyć po aktualizacjach.

Trzy opcje:

Opcja 1 — Pełna migracja do Streamable HTTP. Usuń SSE endpoint, zastąp jednym POST endpointem. Dla większości serwerów to kilkanaście linii kodu — logika biznesowa (tools, resources) zostaje bez zmian.

Opcja 2 — Obsługa obu transportów. Utrzymaj oba endpointy: stary SSE dla starych klientów, nowy Streamable HTTP dla nowych. Klienci MCP wykrywają transport przez próbę połączenia.

Opcja 3 — Wrapper. Zbuduj cienką warstwę Streamable HTTP która forward-uje żądania do istniejącego serwera SSE. Najmniej zmian w istniejącym kodzie, ale dodatkowa latency.

Sprawdź wersję specyfikacji klienta który przestał działać — jeśli używa protocolVersion: "2025-03-26" a Twój serwer odpowiada błędem — problem w transportie lub wersji.

Kiedy który transport

Budujesz narzędzie developerskie instalowane lokalnie (jak GitHub Copilot plugin, integracja z IDE): stdio. Zero infrastruktury, zero konfiguracji sieci, działa offline.

Budujesz serwis dostępny dla wielu użytkowników przez internet: Streamable HTTP. Jeden endpoint, standardowy HTTP, łatwy deployment.

Budujesz serwer MCP jako plugin WordPress: Streamable HTTP przez REST API. Naturalny wybór — jeden register_rest_route i gotowe.

Masz istniejący serwer SSE z 2024 roku: zaplanuj migrację do Streamable HTTP. SSE jest deprecjonowane — nie będzie nowych funkcji, kompatybilność z nowymi klientami może się pogorszyć.

 

W następnym artykule: serwer MCP działa przez sieć — kto ma prawo go wywołać i jak agent się autoryzuje w zewnętrznych serwisach? OAuth dla agentów i zarządzanie tożsamością w ekosystemie MCP.


Pojęcia ze słownika: Streamable HTTP · SSE · MCP · JSON-RPC · 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...