Veel werk begint tegenwoordig in een gesprek met een AI-assistent. Iemand beschrijft een bug aan Claude, zoekt uit wat er aan de hand is, en moet dan dat gesprek verlaten om ergens anders een ticket aan te maken en te wachten tot iemand het oppakt. Wij wilden dat de assistent het werk zelf kan overdragen: de taak aanmaken, de specificatie laten opstellen en goedkeuren, de run starten en later de deployment aftrappen.
Het Model Context Protocol is daarvoor de logische weg, omdat de assistenten die mensen al gebruiken het spreken. Dus heeft CodeLynq een MCP-endpoint, met een OAuth 2.1-server ervoor. Dit stuk gaat over hoe dat werkt, wat het begrenst, en de twee serieuze fouten die we erin vonden voordat iemand anders dat deed.
Wat een assistent kan
Het endpoint biedt vijftien tools die het leven van een taak volgen:
- Lezen: projecten, repositories en branches opvragen, de status van een run bekijken, de architectuurkaart lezen, releases opvragen.
- Plannen: een taak aanmaken, de technische specificatie laten opstellen, de specificatie goedkeuren.
- Bouwen en opleveren: een run starten, documentatie laten genereren, een deployment aftrappen.
- Onderzoeken: een alleen-lezen troubleshootsessie op een taak openen, vervolgvragen stellen, de diagnose lezen.
De belangrijkste ontwerpkeuze zie je niet: elke tool roept dezelfde route-handler aan als de webapp. Een run starten via MCP loopt door dezelfde functie als op de knop drukken, dus dezelfde limieten uit je abonnement, dezelfde maximale gelijktijdigheid, goedkeuringsregels, controles op projectlidmaatschap en audit-regels gelden. Er is geen tweede implementatie die uit de pas kan gaan lopen met de eerste, en geen manier om via MCP bij een project te komen waar je in de browser niet bij kunt.
Tokens zijn API-tokens, met scopes
We hebben geen nieuw soort credential bedacht. Een token uit de OAuth-flow is een gewoon CodeLynq API-token, vernoemd naar de app die erom vroeg, zichtbaar in je beveiligingsinstellingen met een label "Gekoppelde app", en je trekt het op dezelfde manier in. Het gaat door dezelfde controles als een token dat je zelf maakt, inclusief de IP-allowlist en het MFA-beleid van je organisatie als die zijn ingesteld.
Elk token heeft een scope: read of write. Elke tool geeft aan of hij schrijft, en een schrijvende tool die je met een leestoken aanroept, geeft een foutmelding in plaats van iets te doen. Daarvoor was één bewuste uitzondering nodig. In de rest van de API kijken we naar de HTTP-methode, waarbij een GET een leesactie is. MCP stuurt elke aanroep, lezen of schrijven, als POST, dus de methode zegt niets en een leestoken zou zijn geweigerd voordat het bij een tool aankwam. Het MCP-endpoint is uitgezonderd van die controle en dwingt de scope per tool af.
Aan de OAuth-kant is het authorization code met PKCE, en verder niets. S256 is verplicht en plain challenges worden geweigerd, redirect-URI's moeten exact overeenkomen, codes worden alleen als hash opgeslagen, zijn eenmalig en verlopen na tien minuten, en de access tokens die ze opleveren verlopen na 90 dagen. Je kunt geen app autoriseren terwijl een platformbeheerder als jou is ingelogd, of voordat je e-mailadres is bevestigd, en de autorisatie zelf komt in de auditlog.
Wat onze eigen review vond
Na het bouwen hebben we de publieke endpoints bekeken zoals een aanvaller dat zou doen. Het meeste hield stand: elke tool controleert lidmaatschap van project en organisatie, omdat de handler die hij aanroept dat doet. Twee dingen niet.
Een autorisatiecode kon twee keer worden gebruikt. Het token-endpoint controleerde of een code al gebruikt was en markeerde hem daarna als gebruikt, in twee losse stappen. Twee verzoeken met dezelfde code op hetzelfde moment konden allebei door de controle komen en allebei een token krijgen. Nu is het één voorwaardelijke update die alleen slaagt zolang de code ongebruikt is; precies één verzoek wint, en het andere wordt geweigerd als replay.
Iedereen kon een client registreren die om schrijfrechten vroeg. MCP-clients registreren zichzelf zonder in te loggen. Zo is het protocol bedoeld, en dat stonden we toe. Maar we gaven die anonieme clients ook elke scope waar ze om vroegen, met elke https-redirect. Een aanvaller kon een client met een geloofwaardige naam registreren, een slachtoffer door ons echte toestemmingsscherm sturen en een schrijftoken op zijn eigen adres ontvangen. Anonieme registratie is nu beperkt tot alleen lezen, en de toegekende scope kan nooit verder gaan dan waarmee de client is geregistreerd, wat het toestemmingsverzoek ook zegt.
In dezelfde ronde hebben we twee kleinere problemen opgelost: een terugvaloptie die een scope kon toevoegen die de client niet mocht hebben, en een state-parameter die zonder encoding in de redirect werd overgenomen.
De grenzen, eerlijk gezegd
- Door de fix hierboven krijgt een assistent die via de OAuth-flow koppelt leesrechten. Wil je dat hij taken aanmaakt of runs start, dan maak je zelf een API-token met
write-scope en geef je dat aan de client. Er is nog geen selfservice-manier om een app te registreren die via OAuth om schrijfrechten mag vragen. - De assistent handelt als jou. Hij kan in die projecten wat jij kunt, niet meer, maar ook niet minder. Geef hem een token met de kleinste scope die volstaat.
- MCP slaat geen enkele controle over. Een specificatie moet nog steeds worden goedgekeurd, een run toont nog steeds wat hij kost, en de pull request wacht nog steeds op een menselijke reviewer.
Wil je het proberen, begin dan met een koppeling met alleen leesrechten en vraag de assistent je projecten op te sommen. De stappen staan op de pagina over de MCP-integratie, en hoe we verder met toegang omgaan lees je bij beveiliging.