Przejdź do głównej zawartości

Co serwer odbiera i musi zweryfikować

Serwer nigdy nie loguje użytkowników. Robi to bramka, a potem wywołuje serwer własnym, krótkotrwałym tokenem i, dla systemu docelowego, poświadczeniem użytkownika. Jeśli używasz startera (mcp-platform-starter), wszystkie poniższe kontrole wykonuje on za ciebie.

Klienty AI używają OAuth z dostawcą tożsamości platformy (Keycloak). Bramka jest chronionym zasobem w rozumieniu RFC 9728:

Co Gdzie
Metadane endpointu /mcp https://<platform>/.well-known/oauth-protected-resource/mcp
Metadane trasy jednego serwera https://<platform>/.well-known/oauth-protected-resource/s/mcp-<system>/mcp
Klucze publiczne, którymi serwery weryfikują bramkę https://<platform>/.well-known/mcp-jwks.json

Żądanie bez ważnego tokenu dostaje 401 z WWW-Authenticate: Bearer resource_metadata="..." wskazującym te metadane. Klient idzie za wskazaniem i rozpoczyna logowanie. Klient obsługujący RFC 9728 nie potrzebuje innej konfiguracji. Gdy zestaw kluczy dostawcy tożsamości jest niedostępny, bramka odpowiada 503 z Retry-After i bez WWW-Authenticate, żeby klient nie rozpoczynał logowania, które nic nie da.

Bramka podpisuje nowy token dla każdego żądania do serwera (ES256). Serwer weryfikuje go zestawem kluczy spod MCP_GATEWAY_JWKS_URI.

Claim Znaczenie
iss Wystawca bramki, domyślnie https://mcp-gateway.internal
aud Identyfikator serwera odbierającego, mcp-<system>. Jeden ciąg znaków, nigdy lista
sub sub użytkownika od dostawcy tożsamości; system:mcp-gateway w wariancie serwisowym
usr preferred_username użytkownika
grp Grupy użytkownika z prefiksem mcp-
azp Identyfikator klienta OAuth klienta AI
tool Nazwa narzędzia po stronie serwera. Obecne dokładnie przy tools/call i równe params.name
use Cel: call dla endpointu MCP, policy-data dla wysyłki danych polityk
rid Identyfikator żądania, musi być równy nagłówkowi X-Request-Id
prv Rewizja danych polityk bramki
mfa Sekundy od ostatniego logowania wieloskładnikowego, jeśli znane
iat, exp, jti Czas wystawienia, wygaśnięcie i unikalny identyfikator

Serwer musi odrzucić żądanie z 401, chyba że spełnione jest wszystko poniżej:

  • podpis jest poprawny, alg to ES256, a kid jest w zestawie kluczy (kid zaczynający się od test- lub dev- tylko w trybie dev);
  • aud to identyfikator tego serwera;
  • exp - iat wynosi najwyżej 60 sekund, a exp i iat mieszczą się w 30 sekundach od zegara serwera;
  • jti nie był wcześniej użyty (każdy token jest jednorazowy; domyślnie pamięć, Redis przy wielu instancjach);
  • rid jest równe X-Request-Id;
  • use odpowiada celowi endpointu: token policy-data przy tools/call jest odrzucany jako wrong_purpose, i odwrotnie;
  • tool jest obecne przy tools/call i nieobecne przy każdym innym żądaniu;
  • wariant serwisowy (bez użytkownika i bez tool) pojawia się tylko przy initialize, server/discover, tools/list, ping i wysyłce danych polityk.

GET /.well-known/mcp-manifest nie wymaga tokenu, a /actuator/health to jedyna inna otwarta ścieżka. Serwer nigdy nie czyta nagłówka Host i nie żąda certyfikatu klienta.

Wektory w mcp-contracts (test-vectors/jwt/) podają oczekiwany wynik każdej reguły, zobacz Kontrakty.

InternalJwtFilter wykonuje wszystkie powyższe kontrole przed jakimkolwiek kodem narzędzia. Odrzucenie to 401 z WWW-Authenticate: Bearer; powód trafia do logu (event=internal_jwt_rejected reason=<code>) i audytu, nie do wywołującego. Niedostępny zestaw kluczy lub Redis daje 503, nigdy przepuszczenie. Ustawiasz tylko adres zestawu kluczy i identyfikator serwera:

aiwcorpo.mcp.platform:
server-id: mcp-notes
internal-jwt:
jwks-uri: ${MCP_GATEWAY_JWKS_URI}

W narzędziu CallerContext.current() zwraca zweryfikowane subject, username, groups, client, tool, requestId, policyRevision i mfaAgeSeconds. Serwer nie decyduje, kto może wywołać narzędzie; robi to bramka. Serwer sprawdza zasoby, które narzędzie nazywa, zobacz Napisz serwer z szablonu.

Bramka pobiera poświadczenie użytkownika od brokera poświadczeń i wysyła je z każdym wywołaniem w dwóch nagłówkach: X-MCP-Credential (wartość) i X-MCP-Credential-Type. Typ to jeden z user-oauth, user-secret, token-exchange i service-account. Typ musi należeć do accepted-credential-types serwera; w przeciwnym razie serwer odpowiada internal_error, zanim dotknie polityki lub systemu.

BackendCredential credential = CredentialContext.require("notes");
restClient.get()
.uri("/api/records/{id}", id)
.headers(credential::applyTo)
.retrieve()
.body(Record.class);

applyTo ustawia Authorization: <scheme> <value>. CredentialContext.require rzuca wyjątek, gdy z żądaniem nie przyszło poświadczenie, a starter zamienia to na credential_required z linkiem do portalu z MCP_CONNECT_URL. Po odczytaniu starter ukrywa Authorization, X-MCP-Credential i X-PAT-* przed resztą żądania i usuwa wartość z każdego komunikatu i wpisu logu. Serwer nigdy nie przechowuje poświadczenia.