Skills, MCP en waar de credentials thuishoren
Een simpele vraag — hebben onze agents MCP nodig? — die stilletjes uitmondde in een helderder beeld van skills, MCP-servers en de identity-laag die onder beide zit.
We draaien een kleine vloot AI-agents in de homelab — systemen, netwerk, security, identity, endpoint. Elk is een dunne schil rond een set skills, waarbij een skill een tooldefinitie is plus de code die een echte backend aanroept. Het werk van deze week voegde geen skill toe. Het begon met een vraag over plumbing en eindigde ergens nuttigers: een heldere regel voor wanneer je bouwt, wanneer je koopt, en waar secrets eigenlijk horen te leven.
De oorspronkelijke vraag
Het Model Context Protocol (MCP) is nu overal. Dus kwam de eerlijke vraag boven: lijken onze zelfgebouwde skills op MCP-servers — of moeten we MCP op onze agents beginnen schroeven?
Het klinkt als een verpakkingsvraag. Het bleken drie vragen in één jas te zijn.
Wat een skill eigenlijk is
Een skill in ons framework is bewust geen MCP-server. Het zijn twee dingen: een TOOLS-lijst — elk item een naam, een beschrijving en een JSON Schema voor zijn inputs — en een dispatch(tool_name, inputs)-methode die de call naar een client routeert. Meer niet. Geen transport, geen aannames over een protocol.
Net die neutraliteit is het hele punt. Hetzelfde skill-object voedt twee verschillende front-ends. De ene is de chat-agent (die een Anthropic API-key nodig heeft). De andere is een MCP-stdio-server, één keer gebouwd in ons gedeelde framework, die over de tools van elke skill loopt en calls rechtstreeks doorgeeft aan dispatch().
Dus het eerste antwoord landde voor we één regel code hadden geschreven: onze agents zijn al MCP-servers. Elke skill is al ontsloten over MCP, onder de canonieke mcp__<agent>__<skill>__<tool>-naamgeving, vandaag aanroepbaar vanuit Claude Code. De schemavorm die we voor het chatpad aan het schrijven waren, was al die tijd al MCP-compatibel. Er viel niets toe te voegen.
Het vervolg: moeten we MCP binnenin onze logica gebruiken?
Het volgende instinct is om verder te grijpen: als MCP de standaard is, zouden de agents het dan ook intern moeten spreken? Drie manieren om dat te lezen, en alle drie zijn voor ons een neen:
- De chat-loop met skills laten praten over MCP. Vandaag is dat een in-process functieaanroep. Het door MCP routeren zou een subprocess en een serialisatiestap toevoegen om een methodeaanroep te vervangen die al werkt. Pure overhead.
- Skills herschrijven als wrappers rond MCP-servers van derden. Onze clients zijn ~100 regels standard library tegen een vendor-API — gepind, offline testbaar, CVE-gescand onder ons eigen dependencybeleid. De meeste backend-MCP-servers zijn jong en wispelturig; ze zouden op dag één zakken voor “≥4 weken oud, gepind, CVE-vrij”. We zouden net het risico binnenhalen dat ons beleid moet buitenhouden.
- Agents elkaar laten aanroepen over MCP. Verleidelijk — de endpoint-agent flagt een verouderd pakket, de security-agent heeft de CVE-data. Maar we hebben al twee zuiverdere antwoorden: gedeelde code hoort in een gedeelde client-library, en cross-agent-orkestratie gebeurt al een laag hoger. Claude Code houdt de MCP-tools van elke agent tegelijk vast. Het is de orkestrator. Agent-naar-agent-plumbing bouwen zou dat dupliceren en agents koppelen die net onafhankelijk moeten blijven.
MCP verdiende zijn plaats als onze egress — de manier waarop de buitenwereld binnenkomt. Het naar binnen trekken levert niets op.
De vraag die er echt toe deed: waar leven de secrets?
Toen kwam de echte boven. Vandaag leest elke skill een langlevende API-key uit een .env-bestand. Dat willen we wegwerken: elke agent een credential just-in-time uit een PAM-dienst laten checken — read-only of read-write, scoped, met automatische of menselijke goedkeuring, en elke sessie tot in detail gelogd.
Dus: zou een skill vervangen door een MCP-server ons dat geven?
Neen — en waarom inzien is het waardevolle stuk. PAM-checkout, RO/RW-scoping, goedkeuring en audit zijn eigenschappen van hoe de credential gebrokerd wordt. Dat staat los van of de tool een skill of een MCP-server is. Een MCP-server heeft net als een skill een credential nodig om zijn backend te bereiken — en standaard krijgt hij een statische secret in een configbestand, exact het ding dat we proberen weg te halen, verplaatst.
De as skill-versus-MCP is simpelweg de verkeerde as hiervoor. De juiste is waar de credential vandaan komt en waar goedkeuring en audit gebeuren:
- Geen statische secret → de configloader van de skill stopt met
.envlezen en roept in de plaats een broker aan die een JIT-checkout doet en een kortlevende, scoped lease teruggeeft. - RO versus RW → PAM geeft een andere lease per scope uit; read-tools vragen een read-lease, write-tools een write-lease.
- Goedkeuring → ofwel PAM’s eigen workflow, ofwel onze bestaande safety guard op toollaag (kill-switch, two-phase confirm, blast-radius-poort), ofwel beide.
- Audit → PAM logt elke checkout; onze dispatch-laag logt de actie plus het lease-id, zodat een sessie terugkoppelt naar een lease die terugkoppelt naar een backend-wijziging.
Dat alles leeft in één broker in het gedeelde framework. Bedraad het één keer in de skill-laag en het MCP-pad erft het gratis — want de MCP-servers laden dezelfde skills. Je bedraadt het nooit twee keer.
Eerlijk tegenover MCP: er is één echt voordeel. Een apart serverproces houdt de secret volledig buiten het model-gedreven proces — de client ziet enkel tool-resultaten, nooit de key. Als je dreigingsmodel het model zelf is dat onder druk gezet wordt om een credential te lekken, dan helpt die grens. Maar kortlevende, scoped, intrekbare PAM-leases neutraliseren het meeste daarvan toch: een gelekte lease is een ingeperkt incident, geen inbraak. De sterke houding combineert beide, en je kunt die grens net zo goed bij een broker-subprocess leggen als bij een MCP-server.
En de valkuil die beslist of dit allemaal echt veiliger is: je hebt secret-zero verschoven. De broker heeft nu zelf een credential nodig om zich bij PAM te authenticeren. Wordt dat alweer een statisch token in .env, dan heb je het probleem verplaatst, niet opgelost. Het moet een host-gebonden machine-identiteit zijn — AppRole, mTLS, TPM, workload identity — geen opgeslagen token. En voor logs die het bewaren waard zijn, rijg je ook de identiteit van de menselijke operator erdoor, zodat de audit leest “Jitser checkte, via de endpoint-agent, een RW-lease van 10 minuten uit” — niet “een machine deed iets”.
De conclusie
Drie regels kwamen uit één vraag:
-
Bouw standaard een skill. Neem een externe MCP-server enkel over als ze de lat haalt — first-party en beleidsproper (gepind, ≥4 weken oud, CVE-vrij), én je die backend niet ook in het chatpad nodig hebt. “Er bestaat een onderhouden MCP” maakt het een kandidaat, geen default. Voor iets als Microsoft Graph zouden we nog altijd wellicht een gedeelde in-process client verkiezen, want twee agents hebben het nodig en we willen het onder onze eigen pin-en-audit-controle.
-
Houd MCP als egress, niet als interne plumbing. Onze agents zijn al MCP-servers; daar hoort het protocol thuis. Cross-agent-werk gebeurt bij de orkestrator erboven, niet in de bedrading ertussen.
-
De identity-laag is een aparte, orthogonale laag die met beide koppelt. Credential-brokering zit onder zowel skills als MCP en trekt zich niets aan van wie er aanroept. Bouw het één keer tegen de skill-laag, en de MCP-servers krijgen PAM-checkout, scoping, goedkeuring en per-sessie-audit zonder extra kost.
De les
De vraag waarmee we begonnen — “hebben we MCP nodig?” — was de verkeerde, en ze eerlijk beantwoorden leidde naar de juiste: waar leven de credentials? Verpakking en identity zijn verschillende lagen, en ze door elkaar halen is hoe je eindigt met “moderniseren” door een statische secret van het ene bestand naar het andere te verhuizen. Beslis ze apart. Het protocol is hoe de wereld met je agents praat; de broker is hoe je agents het recht verdienen om te handelen. Houd die uit elkaar en beide worden eenvoudiger.