OAuth für MCP-Server: wie ein Remote-Server einen Agenten authentifiziert und was bricht
Ein Remote-MCP-Server authentifiziert einen Agenten als OAuth-2.1-Resource-Server. Er antwortet auf einen Aufruf ohne Token mit 401 und einem WWW-Authenticate Header, der Client folgt ihm zu den Metadaten des Servers, durchläuft einen Authorization-Code-Flow mit PKCE und sendet das Token bei jedem Request als Bearer-Header. Hier ist jeder Schritt, heute live ausgeführt.
Autorisierung ist in MCP optional und nur für HTTP-Transporte: ein lokaler stdio-Server liest seine Zugangsdaten aus der Umgebung. Alles Folgende stammt aus der aktuellen Spec-Revision 2026-07-28, und jede Antwort ist ein echter Aufruf an https://api.plugkit.co/mcp vom 16. Oktober 2026.
Der Handshake, in vier Antworten
Schritt 1. Ohne Token aufrufen und die Challenge lesen. Der Server muss einen 401 mit einem WWW-Authenticate Header zurückgeben, der auf seine Metadaten zeigt.
curl -i -X POST https://api.plugkit.co/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://api.plugkit.co/.well-known/oauth-protected-resource", scope="mcp:read mcp:write"
{"error":"Missing credentials. Send `Authorization: Bearer <api key or session token>`.","type":"authentication_error","code":"unauthorized"}
Der scope Hinweis ist in der Spec ein SHOULD, kein MUST, und er macht den Unterschied zwischen einem Client, der die richtigen Rechte beim ersten Versuch anfragt, und einem, der rät. Clients müssen die Scopes aus der Challenge als maßgeblich für die Operation behandeln.
Schritt 2. Die Protected Resource Metadata abrufen. Das ist RFC 9728, und es ist ein MUST für jeden MCP-Server.
{"resource":"https://api.plugkit.co/mcp",
"authorization_servers":["https://api.plugkit.co"],
"scopes_supported":["mcp:read","mcp:write"],
"bearer_methods_supported":["header"],
"resource_documentation":"https://docs.plugkit.co"}
Schritt 3. Die Metadaten des Authorization Servers abrufen. RFC 8414 oder OpenID Connect Discovery; der Server muss mindestens eines anbieten, und der Client muss beide beherrschen.
{"issuer":"https://api.plugkit.co",
"authorization_endpoint":"https://api.plugkit.co/oauth/authorize",
"token_endpoint":"https://api.plugkit.co/oauth/token",
"registration_endpoint":"https://api.plugkit.co/oauth/register",
"revocation_endpoint":"https://api.plugkit.co/oauth/revoke",
"code_challenge_methods_supported":["S256"],
"token_endpoint_auth_methods_supported":["none"],
"client_id_metadata_document_supported":true,
"authorization_response_iss_parameter_supported":true}
Schritt 4. Den Code-Flow durchlaufen und erneut mit Token aufrufen. Authorization Code mit PKCE, der Parameter resource in beiden Requests, dann Authorization: Bearer <token> bei absolut jedem HTTP-Request, auch innerhalb derselben Session.
MCP-Server-Authentifizierung: woher die Client-ID kommt
Das ist der Teil, der sich geändert hat, und der Teil, den die meisten Tutorials noch falsch erzählen. Die aktuelle Revision listet drei Mechanismen und ordnet sie.
| Mechanismus | Status in der Revision 2026-07-28 | Was der Client sendet als client_id |
|---|---|---|
| Client ID Metadata Documents | SHOULD support | Eine HTTPS-URL, die der Server abruft, um die Metadaten des Clients zu lesen |
| Vorregistrierung | Erlaubt | Eine Client-ID, die du auf anderem Weg erhalten hast |
| Dynamic Client Registration (RFC 7591) | MAY support, veraltet | Eine Client-ID aus POST /register |
Dynamische Registrierung war in der Revision 2025-06-18 ein SHOULD. Jetzt heißt es ausdrücklich „deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents“. CIMD ersetzt das durch etwas Einfacheres: der Client nutzt eine URL als Identifier, der Authorization Server ruft diese URL ab, liest die Metadaten und validiert die Redirect-URIs. Kein Registrierungsaufruf, kein gespeicherter Client-Datensatz pro Client.
Der PlugKit-Server oben kündigt beides an, client_id_metadata_document_supported: true und einen aktiven registration_endpoint, und das ist die richtige Haltung, solange die Clients noch nachziehen.
OAuth 2.1 in MCP: PKCE, der resource-Parameter und die Audience-Bindung
Drei Anforderungen leisten die Sicherheitsarbeit, und alle drei sind MUSTs für Clients.
- PKCE. Pflicht für jeden Client.
S256ist die Methode, die der Server oben veröffentlicht. Sie verhindert, dass ein abgefangener Authorization Code von jemand anderem als dem Client eingelöst wird, der den Flow gestartet hat. - Der Parameter
resource. RFC 8707, im Authorization Request und im Token Request, mit der kanonischen URI des MCP-Servers:&resource=https%3A%2F%2Fapi.plugkit.co%2Fmcp. Clients müssen ihn senden, ob der Server ihn unterstützt oder nicht. - Audience-Prüfung. Der Server muss prüfen, dass das Token für ihn ausgestellt wurde, und alles andere ablehnen. Die Spec ist unmissverständlich: ein MCP-Server „MUST NOT accept or transit any other tokens“.
Das Letzte hat einen Namen. Wenn ein Server ein Token nimmt, das für jemand anderen bestimmt war, und es nach oben weitergibt, bekommst du das Confused-Deputy-Problem: die nachgelagerte API vertraut dem Aufruf, weil er vom Server kam. Token-Passthrough ist verboten, und deshalb muss ein Server, der eine fremde API aufruft, dafür sein eigenes separates Token halten. Das ist die Hälfte „Server zu Plattform“ des Problems, und sie hat ihre eigenen Fehlerquellen, behandelt in Social-Media-Access-Tokens.
Was bricht
Fünf Fehler, in der Reihenfolge, in der du sie wahrscheinlich triffst.
| Symptom | Status | Ursache | Lösung |
|---|---|---|---|
| Der Client startet den Flow nie | 401 | Kein WWW-Authenticate Header, oder keine Resource-Metadaten |
Implementiere RFC 9728 und gib den Header bei jedem 401 zurück |
| Tool-Aufruf abgelehnt, Token sieht in Ordnung aus | 403 | Dem Token fehlt ein Scope, den die Operation braucht | Gib error="insufficient_scope" mit scope zurück und lass den Client nachfordern |
| Endlosschleife von Zustimmungsdialogen im Browser | 401 | Refresh Token abgelaufen oder widerrufen, Client wiederholt blind | Begrenze die Versuche und behandle wiederholtes Scheitern als endgültig |
| Token von einem zweiten Server abgelehnt | 401 | Token an einen Server gesendet, für den es nicht ausgestellt war | Sende den Parameter resource und validiere die Audience |
| Der Flow endet beim falschen Server | keiner | Mix-up-Angriff, iss nicht geprüft |
Notiere den Issuer vor der Weiterleitung und vergleiche ihn beim Zurückkommen |
Die mit den Scopes verdient Details, weil sie neu und leicht falsch zu machen ist. Wenn ein Token gültig, aber zu eng ist, sollte der Server mit 403 antworten und mit error="insufficient_scope" sowie den Scopes, die die Operation braucht. Der Client autorisiert dann neu mit der Vereinigung aus dem, was er schon hatte, und dem, was gerade gefordert wurde, nicht nur mit dem neuen Scope, sonst verliert er die Rechte, die er vorher genutzt hat. Server sollten alle Scopes, die die Operation braucht, in einer einzigen Challenge senden, statt sie einen pro Runde nachzureichen.
Die iss Prüfung ist die stillste der fünf, weil nichts sichtbar fehlschlägt. Der Client muss den Issuer aus den validierten Metadaten notieren, bevor er den Browser öffnet, und dann den iss vergleichen, der im Callback zurückkommt, ohne jede Normalisierung: kein Angleichen der Groß- und Kleinschreibung, kein abschließender Slash, kein Aufräumen des Percent-Encodings. Der Server oben setzt authorization_response_iss_parameter_supported: true, ein fehlender iss in der Antwort muss also unmittelbar abgelehnt werden.
Remote-MCP-Server-Auth: OAuth oder API-Key?
Beides funktioniert, und die Abwägung dreht sich um Scopes und Widerruf, nicht um Sicherheitstheater.
| OAuth 2.1 | API-Key | |
|---|---|---|
| Granularität | Scopes, Nur-Lesen ist möglich | Vollzugriff, keine Scopes |
| Wer das Geheimnis hält | Der Client, erneuerbar | Du, in einer Config-Datei oder Umgebungsvariable |
| Widerruf | Pro verbundener App, durch den Nutzer | Key rotieren, was alle Nutzer davon bricht |
| Passt zu | Interaktive Clients, Dritte | Skripte, Cron, dein eigenes Backend |
Beim Server oben teilen sich die zwei Scopes sauber auf: mcp:read deckt Profile, Accounts, Beiträge, Konversationen und Analytics ab; mcp:write ergänzt Veröffentlichen, Nachrichten senden, auf Kommentare antworten und Accounts verbinden oder trennen. Ein API-Key trägt Vollzugriff und keine Scopes, und das ist der ehrliche Grund, OAuth für alles zu bevorzugen, was ein Modell interaktiv steuert. Lass einen Agenten eine Woche auf mcp:read laufen, bevor du ihn schreiben lässt, wie in Claude als dein Social-Media-Manager.
Derselbe Server ist aus Claude, ChatGPT, Claude Code, Cursor und VS Code erreichbar und kostet 49 $ pro Monat für 5 verbundene Accounts, jederzeit kündbar, mit 7 Tagen gratis ohne Karte. Ob ein MCP-Server überhaupt die richtige Form für deine Integration ist, ist eine andere Frage, beantwortet in MCP oder REST-API für deinen Agenten; ein durchgerechnetes Beispiel in einem Client steht in der Instagram-MCP-Server.
Vollständiger normativer Text: die MCP-Autorisierungsspezifikation, gelesen am 16. Oktober 2026.
FAQ
Braucht ein MCP-Server OAuth?
Nur wenn er remote läuft und du Autorisierung pro Nutzer willst. Autorisierung ist in der Spec optional, und lokale stdio-Server sollen sie gar nicht nutzen, sondern ihre Zugangsdaten aus der Umgebung holen. Ein HTTP-Server, der mehrere Nutzer bedient, sollte sie implementieren, denn ein API-Key gibt jedem Besitzer denselben Vollzugriff.
Welche OAuth-Version nutzt MCP?
OAuth 2.1, mit PKCE als Pflicht für alle Clients, Protected Resource Metadata (RFC 9728) als Pflicht für alle Server, und dem Parameter resource aus RFC 8707 als Pflicht in Authorization- und Token-Requests. Die aktuelle Revision ist 2026-07-28, die zusätzlich die Issuer-Identifikation aus RFC 9207 aufnimmt.
Ist dynamische Client-Registrierung in MCP noch Pflicht?
Nein. Sie ist von SHOULD in der Revision 2025-06-18 auf MAY in der aktuellen gerutscht, und die Spec nennt sie veraltet, erhalten für Server, die keine Client ID Metadata Documents unterstützen. CIMD ist der bevorzugte Weg: der Client nutzt eine HTTPS-URL als Client-ID, und der Authorization Server liest die Metadaten dort ab.
Warum fragt mich mein MCP-Client immer wieder nach dem Login?
Meist ein abgelaufenes oder widerrufenes Refresh Token und ein Client, der den Flow wiederholt, statt zu stoppen. Die Spec sagt Clients, sie sollen die Versuche begrenzen und wiederholtes Scheitern als endgültig behandeln. Prüfe, ob der Nutzer die App widerrufen hat, und ob dein Authorization Server Refresh Tokens rotiert, was er bei öffentlichen Clients tun muss.
Was bedeutet ein 403 von einem MCP-Server?
Das Token ist gültig, aber ihm fehlt ein Scope, den die Operation braucht. Der Server sollte error="insufficient_scope" samt den benötigten Scopes zurückgeben, und der Client sollte neu autorisieren und diese plus alles anfragen, was er schon hatte. Ein 401 bedeutet dagegen fehlende oder ungültige Zugangsdaten, und ein 400 bedeutet, dass der Authorization Request selbst fehlerhaft war.
Ein Plug für jede Plattform, die dein Agent anfasst
49 $ im Monat für 5 verbundene Accounts, jederzeit kündbar. Jede Funktion in jedem Tarif.
Hol dir deinen API-Key →