OAuth i autoryzacja w MCP — kto ma prawo wywołać Twój serwer i jak to kontrolować

przez Łukasz | cze 5, 2026

Poprzednie artykuły skupiały się na tym co przepływa między klientem a serwerem MCP i jak. Ten artykuł pyta o coś innego: kto w ogóle ma prawo nawiązać to połączenie?

To jest pytanie o autoryzację — i ma dwie warstwy które łatwo pomylić.

Warstwa 1: kto może wywołać Twój serwer MCP? Warstwa 2: jakie akcje agent może wykonać w zewnętrznym serwisie przez Twój serwer?

Pierwsza warstwa to zabezpieczenie serwera. Druga to problem OAuth — jak agent działa w imieniu użytkownika w serwisach jak Gmail, GitHub czy Slack bez podawania mu hasła użytkownika.

Trzy poziomy autoryzacji serwera MCP

Zanim przejdziemy do OAuth — prostsza taksonomia. Serwery MCP dzielą się na trzy grupy pod kątem autoryzacji dostępu.

Publiczne — bez uwierzytelniania

Każdy klient MCP może się połączyć. Serwer nie sprawdza kto pyta.

Przykład: Webflux Słownik MCP. Słownik jest publiczny — definicje pojęć są dostępne bez logowania na stronie, więc serwer MCP też nie wymaga tokenu. Każdy agent który zna URL może odpytać słownik.

bash
# Działa bez żadnego nagłówka auth
curl -X POST https://webflux.pl/wp-json/webflux/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Kiedy sensowne: narzędzia do danych publicznie dostępnych, słowniki, kalkulatory, konwertery.

Chronione kluczem API

Serwer wymaga klucza API w nagłówku. Prosta kontrola dostępu — kto ma klucz, ten ma dostęp.

bash
curl -X POST https://twoj-serwer-mcp.pl/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-abc123def456" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{...}}'

PHP — weryfikacja po stronie serwera:

php
function handle_mcp_request(WP_REST_Request $request) {
    $auth = $request->get_header('Authorization');
    $token = str_replace('Bearer ', '', $auth ?? '');
    
    if (!$token || !validate_api_key($token)) {
        return new WP_REST_Response(
            ['jsonrpc' => '2.0', 'error' => ['code' => -32001, 'message' => 'Unauthorized']],
            401
        );
    }
    
    // Obsługa żądania...
}

Pułapka: klucz API nie powinien trafiać do kontekstu modelu językowego. Jeśli konfiguracja serwera MCP jest w system promptcie agenta lub w zmiennych które model widzi — klucz może zostać ujawniony przez credential leakage. Klucz API powinien być w zmiennych środowiskowych hosta, nie w treści dostępnej dla modelu.

Chronione OAuth

Serwer wymaga tokenu OAuth 2.0. Używane gdy serwer MCP działa w imieniu konkretnego użytkownika i musi weryfikować jego tożsamość. Najczęściej dla serwerów enterprise lub obsługujących dane wielu użytkowników.

Problem OAuth w świecie agentów

OAuth dla agentów to adaptacja standardu OAuth 2.0 do przypadku gdy autoryzację przeprowadza autonomiczny agent — nie człowiek klikający „Zezwól” w przeglądarce.

Zanim opiszę jak to działa — trzy problemy specyficzne dla agentów, których standardowy OAuth nie rozwiązuje.

Problem 1: Token w kontekście modelu

Standardowe podejście do autoryzacji: przekaż token do agenta w zmiennej środowiskowej lub w konfiguracji. Agent ma token, może działać.

Problem: cokolwiek trafia do kontekstu modelu językowego — może zostać z niego wyciągnięte. Agent który ma token OAuth w kontekście może go ujawnić gdy odpowie na pytanie „pokaż mi swoją konfigurację” albo gdy złośliwa instrukcja w przetwarzanym dokumencie poprosi go o wylistowanie zmiennych środowiskowych.

To jest credential leakage przez model — i jest realnym wektorem ataku na agenty z dostępem do zewnętrznych serwisów.

Rozwiązanie: token nigdy nie trafia do kontekstu modelu. Agent wywołuje wrapper który ma token — nie sam token.

Model językowy
    │
    │ wywołuje: send_email(to, subject, body)
    │
    ▼
Warstwa serwera MCP
    │
    │ pobiera token z vault/env (nie z kontekstu modelu)
    │ wysyła żądanie do Gmail API z tokenem
    │
    ▼
Gmail API

Model widzi tylko wywołanie narzędzia z parametrami. Token jest niewidoczny.

Problem 2: Interactive OAuth flow

Standardowy OAuth wymaga że użytkownik zatwierdza dostęp w przeglądarce. Agent który działa autonomicznie o 3 w nocy nie może czekać na kliknięcie „Zezwól”.

Rozwiązanie: autoryzacja z wyprzedzeniem. Użytkownik przeprowadza OAuth flow raz — ręcznie lub przez interfejs aplikacji. Token jest zapisany bezpiecznie. Agent używa zapisanego tokenu i odświeża go automatycznie gdy wygasa, bez ponownej interakcji użytkownika.

[Etap 1 — jednorazowo, z udziałem użytkownika]
Użytkownik → klikam "Połącz z GitHubem" w aplikacji
Aplikacja → GitHub OAuth flow
GitHub → zwraca access_token + refresh_token
Aplikacja → zapisuje tokeny w vault

[Etap 2 — wielokrotnie, automatycznie]
Agent → chce wywołać GitHub API
Serwer MCP → pobiera access_token z vault
             → jeśli wygasł, używa refresh_token żeby dostać nowy
             → wywołuje GitHub API

Problem 3: Scope creep

Agent który dostał szeroki zakres OAuth może zrobić więcej niż użytkownik zamierzał. Token z scope repo na GitHubie pozwala na wszystkie operacje na repozytoriach — nie tylko te które agent akurat potrzebuje.

Rozwiązanie: principle of least privilege. Token powinien mieć minimalny zakres potrzebny do konkretnego zadania.

# Złe — szeroki scope "na wszelki wypadek"
scope: repo user admin:org

# Dobre — dokładnie to czego potrzebujesz
scope: repo:read issues:write

Jeszcze lepiej: serwer MCP tworzy różne tokeny dla różnych narzędzi. Tool list_repos używa tokenu tylko do odczytu. Tool create_issue używa tokenu z prawem zapisu do issues. Kompromitacja jednego toola nie daje dostępu do wszystkiego.

MCP OAuth extension — specyfikacja 2025-03-26

Specyfikacja MCP 2025-03-26 dodała oficjalne wsparcie dla OAuth — serwery MCP mogą teraz deklarować że wymagają autoryzacji OAuth i prowadzić klienta przez flow.

Discovery — serwer informuje o wymaganiu OAuth:

Gdy niezautoryzowany klient próbuje się połączyć, serwer zwraca HTTP 401 z nagłówkiem WWW-Authenticate:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
                  authorization_uri="https://api.github.com/login/oauth/authorize",
                  token_uri="https://github.com/login/oauth/access_token",
                  scopes="repo issues:write"

Klient MCP który obsługuje OAuth extension wie że musi przeprowadzić flow autoryzacji przed ponowną próbą połączenia.

Flow z PKCE:

MCP rekomenduje Authorization Code Flow z PKCE (Proof Key for Code Exchange) — bez client secret, odporne na przechwycenie kodu autoryzacyjnego.

1. Klient generuje code_verifier (losowy string)
   code_challenge = BASE64(SHA256(code_verifier))

2. Klient otwiera URL autoryzacji z code_challenge:
   https://github.com/login/oauth/authorize
     ?client_id=abc
     &redirect_uri=http://localhost:PORT/callback
     &code_challenge=xyz
     &code_challenge_method=S256
     &scope=repo

3. Użytkownik zatwierdza dostęp
   GitHub przekierowuje na redirect_uri z ?code=AUTH_CODE

4. Klient wymienia kod na token, podając code_verifier:
   POST https://github.com/login/oauth/access_token
     code=AUTH_CODE
     code_verifier=ORIGINAL_VERIFIER

5. GitHub weryfikuje że SHA256(code_verifier) == code_challenge
   i zwraca access_token + refresh_token

Bez client secret — klucz do weryfikacji jest generowany jednorazowo przez klienta i nigdy nie opuszcza jego środowiska.

Używanie tokenu:

Po autoryzacji każde żądanie MCP zawiera token w nagłówku:

json
// Żądanie HTTP do serwera MCP
POST /mcp HTTP/1.1
Authorization: Bearer eyJhbGc...
Content-Type: application/json

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

Implementacja: kontrola dostępu per tool

Zaawansowany wzorzec: różne narzędzia na tym samym serwerze MCP mają różne wymagania autoryzacji.

php
// Mapa uprawnień per tool
$tool_permissions = [
    'list_repos'    => ['scope' => 'repo:read',  'require_auth' => true],
    'create_issue'  => ['scope' => 'issues:write', 'require_auth' => true],
    'get_public_info' => ['scope' => null, 'require_auth' => false],
];

function handle_tools_call($id, $params, $token) {
    $tool_name = $params['name'] ?? '';
    $permissions = $tool_permissions[$tool_name] ?? null;
    
    if (!$permissions) {
        return mcp_error($id, -32601, 'Tool not found');
    }
    
    // Sprawdź czy tool wymaga auth
    if ($permissions['require_auth']) {
        if (!$token) {
            return new WP_REST_Response(
                ['error' => 'Unauthorized'],
                401
            );
        }
        
        // Sprawdź scope tokenu
        $token_scopes = get_token_scopes($token);
        if ($permissions['scope'] && !in_array($permissions['scope'], $token_scopes)) {
            return new WP_REST_Response(
                ['error' => 'Insufficient scope'],
                403
            );
        }
    }
    
    // Wywołaj tool z tokenem w warstwie biznesowej
    return execute_tool($tool_name, $params['arguments'], $token);
}

Checklist autoryzacji przed wdrożeniem serwera MCP

Zanim Twój serwer MCP trafi na sieć:

  • Klucze API i tokeny nigdy nie trafiają do kontekstu modelu — są w vault lub env, dostępne tylko dla warstwy serwera
  • Każdy tool ma zdefiniowany minimalny scope — nie jeden szeroki token dla wszystkiego
  • Tokeny mają czas wygaśnięcia — nie permanentne klucze bez TTL
  • Refresh token jest przechowywany bezpiecznie — nie w tym samym miejscu co access token
  • Logi autoryzacji są aktywne — kto wywołał które narzędzie i kiedy
  • Rate limiting per token — jeden token nie może wywołać nieskończonej liczby requestów

 

W następnym artykule: mamy teorię, mamy protokół, mamy autoryzację. Czas zbudować własny serwer MCP od zera — praktyczny tutorial w PHP z WordPressem jako platformą.


Pojęcia ze słownika: OAuth dla agentów · MCP · Credential leakage · Streamable HTTP · Principal hierarchy

Agent kończy, ale zadania nie wykonał

Agent kończy, ale zadania nie wykonał

Objawy Agent odpowiada: „Przygotowałem i wysłałem potwierdzenie do klienta." Potwierdzenie nie zostało wysłane. Albo: „Zaktualizowałem wszystkie rekordy" — zaktualizował trzy z dwunastu. Albo: „Nie znalazłem żadnych zamówień" — bo narzędzie zwróciło błąd, którego nikt...

Agent zrobił coś, o co nikt nie prosił

Agent zrobił coś, o co nikt nie prosił

Objawy Operacja, której nikt nie zlecił. Dane wysłane pod adres, którego nie ma w żadnej konfiguracji. Rekord zmieniony poza zakresem zadania. Odpowiedź, z której wynika, że agent dostał instrukcje od kogoś innego niż ty. Cecha wspólna: technicznie wszystko zadziałało...

Agent zrobił to samo dwa razy

Agent zrobił to samo dwa razy

Objawy Ten syndrom różni się od pozostałych jedną rzeczą: objaw widzi klient, nie ty. Trzy identyczne wiadomości w skrzynce. Dwa zamówienia zamiast jednego. Podwójne obciążenie. Duplikaty rekordów, które ktoś zauważa tydzień później. W twoim dzienniku wszystko wygląda...

Agent gubi wątek w długim zadaniu

Agent gubi wątek w długim zadaniu

Objawy Pierwsze kroki idą wzorowo. Po kilkunastu agent zaczyna się rozjeżdżać. Przestaje przestrzegać reguły z promptu systemowego, której trzymał się na początku. Zmienia format odpowiedzi w połowie zadania. Wraca do czegoś, co już ustalił, i ustala to inaczej....

Agent podaje dane, których nie ma

Agent podaje dane, których nie ma

Objawy Numer zamówienia w idealnym formacie, którego nie ma w bazie. Kwota, której w dokumencie nie ma. Nazwa pola API, które nigdy nie istniało. Cytat z regulaminu, brzmiący dokładnie jak reszta regulaminu i w nim nieobecny. Cecha wspólna wszystkich tych przypadków:...

Działał wczoraj, dziś nie działa

Działał wczoraj, dziś nie działa

Objawy Nic się nie wywala. Nie ma wyjątków, nie ma timeoutów, dziennik wygląda tak samo jak zawsze. Po prostu odpowiedzi są gorsze. Postacie, w jakich to się objawia: Jakość. Agent zaczyna pomijać kroki, które wcześniej wykonywał, albo odpowiada ogólniej. Format....

Agent wybiera złe narzędzie

Agent wybiera złe narzędzie

Objawy Agent odpowiada nie na to pytanie. Pobiera listę zamówień, gdy pytano o jedno konkretne. Odpowiada z pamięci, choć miał sprawdzić w bazie. Albo sięga po właściwe narzędzie i wpisuje w argumenty coś, czego nie da się użyć. Sygnał, który odróżnia ten syndrom od...

Agent kręci się w kółko

Agent kręci się w kółko

Objawy Agent wykonuje kolejne obroty pętli, nie zbliżając się do zakończenia zadania. W dzienniku widać jedną z trzech postaci. Powtórzenie. To samo narzędzie, te same argumenty, raz za razem — czasem dziesiątki razy pod rząd. Oscylacja. Agent wywołuje na przemian dwa...