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

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