Tutorial

OAuth 2.1 an einen MCP-Server hängen: vom Authorization Server zum Access Token für die Remote-MCP-API

Zustandsloses MCP löst Sitzungen, nicht wer Ihre Werkzeuge rufen darf. Hängt Remote-MCP im öffentlichen Netz, muss Auth vom Authorization-Server-Discovery bis zum Access Token laufen — kein geteiltes Geheimnis im JSON-RPC.

MCP 2026-07-28 markiert HTTP-Autorisierung als optional; sobald Sie eine Ressource schützen, müssen Sie die OAuth-2.1-Teilmenge umsetzen. Cursor, Claude Desktop, Remote-MCP von OpenAI Responses und selbst gebaute Agent-Hosts beginnen mit einer Anfrage ohne Token. Der Server antwortet 401 und zeigt in WWW-Authenticate auf Protected Resource Metadata. Der Client findet dann den Authorization Server, registriert sich, läuft Authorization Code + PKCE mit resource, bekommt ein Access Token und setzt Authorization: Bearer auf jeden HTTP-Hop. Das ist dieser Remote-MCP-API-Pfad in der Praxis: wer was tut, wie Metadata aussehen, wie Audience binden, wie Scope steigern. Spezifikation: MCP Authorization. Schon da: Was ist MCP, Zustandsloses MCP, inputSchema und Tool Calling — hier kommt nur „wer darf rufen“. Diesen Browser-Tanz nicht auf STDIO; Credentials aus der Umgebung.

Drei Rollen: der MCP-Server stellt keine Tokens aus

Der erste Fehler: den MCP-Server als Token-Aussteller zu behandeln. In der Spec ist ein geschützter MCP-Server ein OAuth-2.1-Resource-Server: er nimmt Access Tokens an, prüft sie und verbraucht sie an seine Audience gebunden. Der MCP-Client ist ein OAuth-2.1-Client: er holt ein Token für den Ressourceninhaber und ruft Werkzeuge damit. Der Authorization Server (AS) übernimmt Login, Einwilligung und Ausstellung. Der AS darf beim Resource-Server sitzen oder ein vorhandenes IdP sein (Okta, Keycloak, Auth0, eigenes OIDC). Die Spec sagt nicht, wie Sie einen AS bauen; sie sagt, wie der MCP-Server seinen Ort anzeigt — siehe Authorization Server Discovery.

Autorisierung ist für MCP optional. HTTP-Transporte, die Ressourcen schützen, SOLLTEN dieser Spec folgen. STDIO DARF NICHT. Einen geteilten API-Key in tools/call-params oder ein Token in JSON-RPC-_meta zu stecken, ist nicht Remote-MCP-Auth 2026-07-28. Auth lebt auf dem HTTP-Transport, nicht in der Methoden-Hülle.

Gegen unsere anderen Schichten: MCP ist Entdeckung und Aufruf; Zustandslosigkeit nahm die Session, nicht die Auth; inputSchema formt Argumente, und ein bestandenes Schema heißt nicht, der Aufrufer darf. OAuth beantwortet: „Wurde dieses Bearer für diese Ressource ausgestellt, reicht der Scope?“ Die Agent-Schleife: Was ist ein KI-Agent.

Rolle OAuth-2.1-Hut Was Sie liefern
MCP-Client / HostOAuth-ClientDiscovery, PKCE, Tokens speichern, Bearer auf jedem Hop
MCP-ServerResource-Server401 + PRM, Tokens prüfen, Audience binden
Authorization ServerAussteller / IdPLogin, Einwilligung, Auth-Code, Access Token

Authorization Server finden: 401 und RFC 9728

Das Labor beginnt mit einem fehlgeschlagenen Aufruf. Der Client trifft https://mcp.example.com/mcp mit tools/list oder beliebigem JSON-RPC ohne Authorization. Der Server MUSS HTTP 401 mit Bearer und resource_metadata auf WWW-Authenticate liefern. scope angeben, damit der Client das Minimum kennt. Clients MÜSSEN den Header parsen: resource_metadata nutzen, wenn vorhanden; sonst Well-Known nach RFC 9728 sondieren.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
  scope="invoices:read"

MCP-Server MÜSSEN OAuth 2.0 Protected Resource Metadata (RFC 9728) umsetzen. Das Dokument MUSS mindestens einen AS-Issuer in authorization_servers listen. Sie dürfen resource (kanonische URI dieses Servers), scopes_supported und bearer_methods_supported setzen. Der Client holt dann AS-Metadata: bei issuer ohne Pfad zuerst /.well-known/oauth-authorization-server, dann OpenID openid-configuration; mit Tenant-Pfad den Pfad in der Spec-Priorität einfügen. Der issuer im Dokument MUSS zeichenweise dem Issuer entsprechen, aus dem die URL gebaut wurde. Sonst Angriff, Dokument verwerfen.

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": [
    "https://auth.example.com"
  ],
  "bearer_methods_supported": ["header"],
  "scopes_supported": [
    "invoices:read",
    "invoices:write"
  ],
  "resource_documentation": "https://mcp.example.com/docs"
}

Oben: die 401 und ein minimales Protected-Resource-Metadata-Dokument. resource darf kein Fragment tragen und das Schema nicht weglassen. Beim trailing slash konsequent bleiben; die Spec bevorzugt keinen, außer er bedeutet etwas. Listen Sie mehrere Authorization Server, ist jeder ein unabhängiger Issuer — Clients MÜSSEN Registrierungszustand trennen.

Client registrieren: CIMD, Vorab-Registrierung, DCR

Nach dem AS MUSS der Client eine client_id haben, bevor er autorisiert. Drei Mechanismen in Spec-Priorität: ① Client ID Metadata Documents (CIMD) — die client_id ist eine HTTPS-URL; der AS holt dieses JSON und prüft redirect_uris; ② Vorab-Registrierung aus einer Konsole (vertraulich oder öffentlich); ③ Dynamic Client Registration (RFC 7591) POST /register. DCR ist veraltet und bleibt nur für AS ohne CIMD. Neue Arbeit bevorzugt CIMD oder Vorab-Registrierung.

Listet authorization_servers mehrere, ist jeder ein eigener AS. Speichern Sie client_id, Geheimnisse und Tokens pro AS. Schicken Sie nie die Credentials von A an den Token-Endpunkt von B. Das ist eine häufige Mix-up- / Confused-Deputy-Tür.

Öffentliche Clients (Desktop-Hosts, Browsererweiterungen) MÜSSEN unter OAuth 2.1 PKCE nutzen. Vertrauliche Clients sollten es auch. Erfinden Sie nicht „client_secret in die MCP-Server-Umgebung legen und Registrierung an den AS proxen.“ Der MCP-Server ist Resource-Server, nicht Client.

Auth-Code + PKCE + resource: Access Token holen

Erst dann der Authorization-Code-Fluss. Bevor der Browser öffnet, MUSS der Client: PKCE-code_verifier / code_challenge erzeugen; resource (kanonische URI des MCP-Servers, RFC 8707) auf Autorisierungs- und Token-Anfrage setzen; Scopes wählen (401-scope bevorzugen, sonst scopes_supported); den geprüften AS-issuer auf demselben Request-Satz wie den Verifier merken. Nach der Einwilligung iss im Callback per einfachem Stringvergleich (RFC 9207) gegen diesen Wert halten — kein Case-Folding, kein Slash-Abschneiden.

Die Token-Anfrage trägt code, code_verifier und resource. Erfolg ist JSON: access_token, token_type Bearer, expires_in, vielleicht refresh_token und scope. Kein Refresh-Token unterstellen. offline_access nicht in scopes_supported des MCP-Servers oder im 401-scope — das fragt der Client beim AS, das ist keine Ressourcenanforderung. Wichtige AS-Metadata-Felder folgen; ist authorization_response_iss_parameter_supported true, MUSS ein Callback ohne iss abgelehnt werden.

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/oauth/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
  "registration_endpoint": "https://auth.example.com/oauth/register",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "token_endpoint_auth_methods_supported": [
    "none",
    "client_secret_basic"
  ],
  "authorization_response_iss_parameter_supported": true,
  "scopes_supported": [
    "invoices:read",
    "invoices:write",
    "offline_access"
  ]
}

Für den Client ist ein Access Token oft undurchsichtig; beim Debug oft ein JWT. Lesen Sie iss, aud (oder resource), scope, exp, client_id. aud MUSS an diesen MCP-Server binden. Ein Token für die Zahlungs-API darf tools/call nicht ausführen. Das Payload unten hat das richtige aud; zeigen Sie auf die URI einer anderen API, MUSS der Resource-Server 401 liefern.

{
  "iss": "https://auth.example.com",
  "sub": "user_1842",
  "aud": "https://mcp.example.com/mcp",
  "client_id": "https://host.example.com/oauth/client.json",
  "scope": "invoices:read",
  "exp": 1790000000,
  "iat": 1789996400
}

Remote-MCP-API mit Bearer rufen und Audience prüfen

Nach dem Token MUSS jeder HTTP-Hop Client → MCP-Server Authorization: Bearer tragen: Discovery, tools/list, tools/call, resources/read. Tokens DÜRFEN NICHT in der Query stehen. JSON-RPC-Methodennamen und Argumente bleiben im Body — Auth-Header und Hülle sind zwei Schichten. Zustandsloses MCP hat keine Session „eingeloggt, Prüfung überspringen“; jede Anfrage authentisiert sich selbst. Siehe Zustandsloses MCP.

POST /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "searchInvoices",
    "arguments": {
      "startDate": "2026-01-01",
      "endDate": "2026-01-31",
      "status": "paid"
    }
  }
}

Der Server prüft nach OAuth 2.1 §5.2: Signatur oder Introspection, exp, iss und RFC-8707-Audience. Fehler sind 401. Nach der Audience noch bestätigen, dass dieser AS das Token ausgestellt hat. Server DÜRFEN Tokens für andere weder annehmen noch weiterreichen. Clients DÜRFEN das Token einer anderen Ressource nicht an diesen MCP-Server senden. Das ist die Confused-Deputy-Regel.

Ein gültiges Token macht Argumente nicht gültig. Bearer beantwortet „wer, welche Ressource, welche Scopes“. Daten und Enums von searchInvoices brauchen weiter parse + Schema + Geschäftsregeln. Machen Sie „Token ok“ nicht zum einzigen Tor von tools/call. Formvertrag: MCP und JSON Schema.

Scopes, 403 und Fehlersuche in JSONVue

Ist das Token gut, der Scope nicht, SOLLTE der Server 403 mit error="insufficient_scope", dem für diese Operation nötigen scope und demselben resource_metadata auf WWW-Authenticate liefern. Alle Scopes dieser Operation in eine Challenge — nicht einen fehlenden Scope pro Versuch. Beim Step-up vereinigt der Client alte Scopes mit der Challenge, damit andere Werkzeuge Grants nicht verlieren.

HTTP Bedeutung Als Nächstes
401Nicht authentifiziert, oder Token ungültig / abgelaufen / falsche AudiencePRM lesen; neu autorisieren oder refreshen
403 + insufficient_scopeToken gültig, Recht zu knappStep-up: Scopes mergen und neu autorisieren
400Autorisierungsanfrage missgebildetClient-Anfrage reparieren; das Werkzeug nicht erneut versuchen
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
  scope="invoices:write",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
  error_description="Write scope required to create a credit note"

Ein Labor erzeugt mindestens vier JSON-Dokumente: Protected Resource Metadata, AS-Metadata, Token-Antwort und (falls JWT) das Payload — plus params.arguments von tools/call. Sie haben verschiedene Jobs; nicht in ein „universelles“ Schema quetschen. Je ein Fehler-Fixture: PRM ohne authorization_servers, AS-Metadata mit falschem Issuer, JWT mit falschem aud, Argumente ohne Feld.

Im Browser reicht: JSON-Formatierer, ob Metadata parsen; JWT-Dekodierer für aud / scope / exp; JSON-Schema-Validator gegen den Vertrag, den Sie für PRM geschrieben haben; JSON Diff für Payloads vor/nach Refresh oder Modell-Arguments gegen MCP-Params. Nichts verlässt die Maschine.

Weiterlesen: Was ist MCP, Zustandsloses MCP, MCP und JSON Schema, A2A vs MCP, Was ist ein KI-Agent.

FAQ

Braucht ein lokales STDIO-MCP OAuth 2.1?

Nein. Die Spec sagt, STDIO soll diesen Browserfluss nicht fahren; Credentials aus der Umgebung. OAuth 2.1 ist für HTTP-Remote-MCP. Dieselben Werkzeuge können STDIO und Streamable HTTP anbieten; nur die HTTP-Seite braucht 401, PRM und Bearer.

Darf das Access Token in JSON-RPC-Params oder _meta?

Nicht als konforme Umsetzung. Das Access Token MUSS im HTTP-Authorization-Header fahren und DARF NICHT in der Query stehen. Hüllen-_meta ist für Protokollversion, nicht Login. Ein Token im Body landet in Logs, Proxys und Modellkontext.

Wenn ich selbst JWTs ausstelle und der Server ein geteiltes Geheimnis prüft — habe ich MCP-OAuth umgesetzt?

Signaturprüfung reicht nicht. Der Client entdeckt einen Authorization Server, nicht „einen Schlüssel im Server“. Sie brauchen weiter RFC-9728-Metadata, authorization_servers, resource auf Authorize- und Token-Anfragen, Audience-Bindung und 401-/403-Challenges. Ein selbst gebauter AS darf JWTs ausstellen; Discovery und Resource-Parameter sind nicht optional.

Unser API-Gateway prüft schon JWTs. Brauchen wir das trotzdem?

Das Gateway kann Signatur und Ablauf prüfen. MCP-Hosts müssen den AS trotzdem finden, resource senden und WWW-Authenticate parsen. „Jedes gültige JWT“ ohne Audience-Bindung lässt Tokens für andere APIs herein. MCP wie schlichtes REST zu schützen, und Hosts schaffen den Handshake oft nicht.

Fazit und nächste Schritte

OAuth 2.1 auf Remote-MCP ist nicht „eine Login-Seite dazu“. Es ist ein Transportvertrag: 401-Discovery, AS-Metadata, Client-Registrierung, PKCE, resource, Access Token, Bearer auf jedem Hop. Der MCP-Server ist der Resource-Server. Der Authorization Server stellt Tokens aus.

In dieser Reihenfolge liefern: zuerst 401 + Protected Resource Metadata für einen bestehenden MCP-Client parsebar machen, dann den AS anschließen, dann die tools/call-Fachlogik. Metadata- und JWT-Fixtures in JSONVue halten, getrennt von Schema-Validierung. Protokolleinstieg: Was ist MCP. Zustandsloser Transport: Zustandsloses MCP.