Bei deinem MCP-Server sollte niemand ein Token einfügen müssen

oauth

Letzten Monat habe ich Kookees MCP-Server auf persönliche Tokens umgestellt. Jeder erstellt sein eigenes, der Assistent handelt als diese Person, und bei jeder Änderung steht, wer sie gemacht hat. In Claude Code und Cursor funktioniert das gut: Du fügst dein Token in einen Befehl oder eine Konfigurationsdatei ein und bist fertig.

Und jetzt erklär das jemandem, der Release Notes in Claude auf dem Laptop schreibt oder Feedback auf dem Handy durchgeht. Die Chat-Apps von Claude und ChatGPT haben kein Feld für einen Authorization-Header. Sie verbinden sich auf genau eine Art mit einem Remote-MCP-Server: Du gibst ihnen eine URL, sie schicken dich zur Anmeldung und behalten die Sitzung. Das ist OAuth, und bis diese Woche konnte Kookee das nicht.

Jetzt kann es das. Du fügst https://api.kookee.dev/api/mcp in Claude, ChatGPT oder VS Code hinzu, meldest dich bei Kookee an, wählst „Lesen und schreiben“ oder „Nur lesen“, und das ist die ganze Einrichtung. Unten steht, was ich gebaut habe und welche Entscheidungen länger gedauert haben als der Code.

So sieht das Verbinden jetzt aus

Auf der Seite MCP-Server jedes Projekts gibt es eine neue Karte mit der URL und den Schritten für jede App. Für Claude gibt es einen Button Zu Claude hinzufügen. Er öffnet Claudes Dialog Add custom connector mit bereits ausgefülltem Namen und URL, und du klickst nur noch auf Add.

Die Karte KI-App verbinden auf der Seite MCP-Server: die Connector-URL mit einem Kopieren-Button, Tabs für Claude, ChatGPT, VS Code und Claude Code, drei Schritte und ein Button Zu Claude hinzufügen

Claude öffnet dann eine Seite von Kookee. Du meldest dich an oder legst ein Konto an und entscheidest, was die App darf:

Die Zustimmungsseite von Kookee: Claude erlauben, dein Kookee-Konto zu nutzen? mit der Wahl zwischen Lesen und schreiben, ausgewählt, und Nur lesen, dazu die Buttons Erlauben und Abbrechen

Kein Token, das du erstellen, kopieren, aufbewahren oder rotieren musst. Der Connector erscheint dann in Claude im Web, auf dem Desktop und auf dem Handy.

Sieben Entscheidungen hinter OAuth für meinen MCP-Server

1. Eine Verbindung pro Person, nicht pro Projekt

Mein erster Plan hat das Token-Modell kopiert: Ein Token gehört zu einem Projekt, also sollte auch eine Verbindung zu einem Projekt gehören. Claude hat diesen Plan zunichtegemacht. Es lässt dich keinen zweiten Custom Connector mit derselben URL hinzufügen, also hätte eine Verbindung, die an ein Projekt gebunden ist, jeden Claude-Nutzer auf ein Projekt beschränkt. Eine eigene URL pro Projekt umgeht das, aber wer in fünf Projekten ist, muss dann fünf Connectors einrichten und im Blick behalten.

Deshalb gehört eine Verbindung zu deinem Konto und erreicht jedes Projekt, in dem du Mitglied bist, mit deiner Rolle im jeweiligen Projekt. Jedes Tool außer list_projects nimmt ein optionales Argument project. Bist du nur in einem Projekt, braucht der Assistent es nie. Bist du in mehreren, sagst du „im Projekt Lumen“, und er übergibt den Slug. list_projects deckt den Fall ab, dass er nachfragen muss.

Fehlt das Projekt oder ist es falsch, antwortet der Server nicht mit einem HTTP-Fehler. Er gibt ein gewöhnliches Tool-Ergebnis zurück, das sagt, was schiefgelaufen ist, und deine Projekte auflistet:

JSON
{
  "error": "MCP_PROJECT_REQUIRED",
  "message": "This account belongs to several projects. Pass `project` with the slug of the one the user means.",
  "projects": [
    { "slug": "lumen", "name": "Lumen", "role": "OWNER" },
    { "slug": "lumen-docs", "name": "Lumen Docs", "role": "MEMBER" }
  ]
}

Ein Modell liest das und versucht es mit dem richtigen Slug noch einmal. Ein 400 von der Transportschicht dagegen hält es für einen kaputten Server.

2. Nur lesen ist eine Wahl auf der Zustimmungsseite

Bei Tokens wählst du den Zugriff, wenn du das Token erstellst. Bei OAuth gibt es keine Token-Seite, also ist die Wahl in den Moment gewandert, in dem du die App zulässt.

Technisch sind die beiden Optionen OAuth-Scopes, read und write, und die Wahl „Nur lesen“ streicht write, bevor die App ihren Code bekommt. Wie bei Tokens lehnt eine Verbindung mit „Nur lesen“ Schreibzugriffe nicht nur ab: Sie listet die schreibenden Tools gar nicht erst. Ein Modell, das move_feedback sieht, plant eine ganze Triage darum herum und scheitert am letzten Schritt. Ein Modell, das es nicht sieht, sagt dir, dass die Verbindung nur lesen darf.

3. Getrennt muss getrennt heißen

Das hat am längsten gedauert. Eine OAuth-App hält zwei Tokens: ein Access-Token, das eine Stunde gilt, und ein Refresh-Token, das sie gegen neue Access-Tokens eintauscht. Von Haus aus prüfte der Refresh-Grant im OAuth-Server, auf dem ich aufgebaut habe, nicht, ob du die App noch zulässt. Hättest du der App die Zustimmung entzogen, hätte sie weiter frische Access-Tokens bekommen können.

Deshalb passieren jetzt zwei Dinge. Jede MCP-Anfrage liest das Access-Token, die Zustimmung dahinter und das Konto in einer Abfrage und lehnt die Anfrage ab, wenn eins davon fehlt oder das Konto deaktiviert ist. Und das Trennen löscht die Zustimmung, die Refresh-Tokens und die Access-Tokens zusammen, in einer Transaktion. Trennst du eine App in Kookee, wird schon ihre nächste Anfrage abgelehnt.

Wer sein Passwort ändert oder zurücksetzt, trennt damit auch jede App. Wenn du ein Passwort zurücksetzt, weil etwas schiefgelaufen ist, ist ein Assistent irgendwo mit einer funktionierenden Sitzung das Letzte, was du willst. Gastkonten können sich gar nicht verbinden.

4. Tokens, die du zurücknehmen kannst, statt Tokens, die du aussitzen musst

Für OAuth-Access-Tokens werden oft JWTs gewählt: signiert, in sich geschlossen, ohne Datenbankabfrage prüfbar. Sie lassen sich aber auch nicht zurücknehmen, bevor sie ablaufen. Für einen Assistenten, der unter deinem Namen veröffentlichen kann, reicht „hört innerhalb einer Stunde auf zu funktionieren“ nicht.

Die Tokens von Kookee sind Zufallsstrings, nur als SHA-256-Hashes gespeichert, mit eigenen Präfixen: mcpa_ für Access-Tokens und mcpr_ für Refresh-Tokens. Ein Secret-Scanner oder ein Mensch, der ein Log liest, kann sie von einem persönlichen Token (kkm_) oder einem API-Schlüssel (kk_) unterscheiden. Der Preis ist eine Abfrage pro Anfrage, und die macht der Server ohnehin schon.

5. Nur bekannte Apps

MCP-Clients weisen sich mit einem Client-ID-Metadatendokument aus: Die Client-ID ist eine URL, und der Server ruft sie ab, um den Namen der App zu erfahren und wohin sie dich zurückschicken darf. Das ist ein sauberes Design. Es bedeutet aber auch, dass ein Server, der jede URL akzeptiert, alles abruft, worauf man ihn richtet, und dafür einen Client speichert, bevor sich überhaupt jemand angemeldet hat.

Kookee akzeptiert stattdessen eine kurze Liste: Claude, Claude Code, ChatGPT und VS Code, jeweils erkannt an der Adresse, unter der ihr Metadatendokument liegt. Dann kann die Zustimmungsseite „Claude erlauben“ sagen und es so meinen, und niemand kann anderswo ein Dokument hosten, das sich Claude nennt.

Das hat einen Preis. Cursor registriert sich per Dynamic Client Registration, die ich ausgeschaltet gelassen habe, also nutzen Cursor-Nutzer weiter persönliche Tokens. Die funktionieren genau wie vorher.

6. Die Zustimmungsseite lässt sich nicht einbetten

Eine Seite mit einem Erlauben-Button ist das klassische Ziel für Clickjacking: Man lädt sie in einen unsichtbaren Frame und legt etwas Verlockendes unter den Button. Kookees Seite sendet Content-Security-Policy: frame-ancestors 'none' und X-Frame-Options: DENY, sodass keine andere Website sie in einen Frame laden kann. Zwei Header, und leicht zu vergessen, bis jemand deswegen schreibt.

7. Dieselbe Zuordnung wie vorher

Die Anmeldung ändert, wie du dich verbindest, nicht, was aufgezeichnet wird. Eine Änderung aus Claude wird unter deinem Namen gespeichert, wie eine Änderung mit einem Token, und das Server-Log vermerkt, welche App sie gemacht hat. Auf dem Feedback-Board sind ihre Spaltenwechsel, Neuzuweisungen und Antworten mit über MCP markiert, sodass deine Teammitglieder sehen, wer den Beitrag verschoben hat und dass ein Assistent es war.

Was du in Kookee siehst

Alles, was du verbunden hast, steht in deinen Kontoeinstellungen unter Sicherheit → Verbundene Apps, mit dem Zugriff und dem Tag der Verbindung. Trennen braucht einen Klick und eine Bestätigung.

Verbundene Apps unter Einstellungen → Sicherheit: Claude Code und Claude mit Lesen und schreiben, VS Code mit Nur lesen, jeweils mit einem Button Trennen

Persönliche Tokens gibt es weiterhin. Sie sind immer noch das richtige Werkzeug für Cursor, für Skripte und CI und für eine .mcp.json, die das ganze Team committet, und sie stehen jetzt in einer eigenen Karte auf der Seite MCP-Server, unter der neuen.

Wenn du einen MCP-Server betreibst

  • Unterstütze OAuth. Wer in Claude, ChatGPT und anderen Chat-Apps arbeitet, kann keinen Header einfügen.

  • Mach die Verbindung pro Person und nimm den Workspace als Argument des Tools. Claude akzeptiert dieselbe URL nicht zweimal.

  • Leg die Wahl „Nur lesen“ auf die Zustimmungsseite und liste keine Tools, die die Freigabe nicht nutzen kann.

  • Prüf die Zustimmung bei jeder Anfrage und lösch die Refresh-Tokens, wenn jemand die Verbindung trennt. Verlass dich nicht darauf, dass der Refresh-Grant das für dich erledigt.

  • Nimm lieber opake Tokens, die du widerrufen kannst, als JWTs, die du aussitzen musst.

  • Akzeptiere eine Liste bekannter Client-Metadaten-URLs, statt abzurufen, was ein Client schickt.

  • Halte die Zustimmungsseite aus Frames heraus.

  • Beantworte „welches Projekt?“ mit einem Tool-Ergebnis, mit dem das Modell etwas anfangen kann, nicht mit einem HTTP-Fehler.


Kookees MCP-Server funktioniert jetzt per Anmeldung mit Claude, ChatGPT und VS Code. Die Schritte stehen in Claude, ChatGPT oder VS Code mit Kookee verbinden, und was ein Assistent nach dem Verbinden tun kann, in Inhalte mit einem KI-Assistenten verwalten (MCP).