Een werkende MCP-server is tegenwoordig zo'n twintig regels Python. Je importeert één class, zet een decorator boven een functie, en draait het. Dat is de hele drempel.
En dat is het probleem.
Het Model Context Protocol heeft het zo makkelijk gemaakt om een tool aan een agent te geven dat niemand nog stilstaat bij de vraag of die tool wel een tool moet zijn. Het resultaat is een ecosysteem dat richting de twintigduizend servers kruipt, waarvan de meeste iets inpakken wat de agent allang kon. Dus laten we er netjes eentje bouwen, en daarna de test doen die bijna niemand doet: verdient dit ding wel een plek in je context window?
De hele bouw, in één bestand
Dit is de officiële quickstart, tot op het bot uitgekleed. FastMCP zit in de Python-SDK en doet het saaie werk voor je.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get the weather forecast for a location."""
# roep hier je weer-API aan
return f"Forecast for {latitude},{longitude}: clear, 18C"
if __name__ == "__main__":
mcp.run(transport="stdio")Dat is een complete, werkende server. De @mcp.tool()-decorator doet het deel dat vroeger vervelend was: FastMCP leest je type hints en je docstring en genereert daar het tool-schema uit. De functie-signatuur is het contract. latitude: float wordt een getypte parameter die het model correct moet invullen, en de docstring wordt de beschrijving die het model leest om te bepalen wanneer het de tool aanroept.
MCP geeft je drie primitieven, niet één. De tool is de bekende, maar de andere twee doen er net zo goed toe:
- Tools zijn functies die het model aanroept, met goedkeuring van de gebruiker. Acties met side effects.
- Resources zijn bestand-achtige data die de client kan lezen: een API-respons, een configbestand, de inhoud van een tabel. Decorator:
@mcp.resource("scheme://path"). - Prompts zijn herbruikbare templates die een gebruiker bewust aanroept. Decorator:
@mcp.prompt().
Die splitsing is het hele ontwerp. Een resource is iets wat de agent leest. Een tool is iets wat de agent doet. Geef je read-only data door en grijp je naar @mcp.tool(), dan heb je het al verkeerd gemodelleerd.
Om het te draaien wijs je een client naar het commando. Voor Claude Desktop is dat een paar regels JSON met de naam van de executable en de bijbehorende args. De regel transport="stdio" is de belangrijke: de client start jouw script en praat ermee via standard in en out. Dat detail oogt onschuldig. Het is ook, zoals we zo zien, waar veel van het gevaar zit.
Dat was de tutorial. Je hebt een eigen server die met een agent praat in de tijd die het kostte om tot hier te lezen.
Het werkt. Dat is het probleem
Twintig regels naar een werkende server klinkt als winst, en voor de juiste tool is het dat ook. Maar juist die laagdrempeligheid is de reden dat de registry verzuipt. Als een server uitbrengen vrijwel niets meer kost, brengt iedereen voor alles een server uit, inclusief de hele lijst aan dingen die allang opgelost waren.
De agent voor je neus kan al shell-commando's draaien. Kan al bestanden lezen. Kan al curl, jq, git, gh, psql en elke andere CLI op de machine aanroepen. Een flink deel van de MCP-servers in het wild is een dunne async-wrapper om een command-line tool die het model allang kan bedienen, nu herverpakt als een dependency die je installeert, als proces draait, en vertrouwt.
Dus voordat je de decorator schrijft, doe je de test.
De test: moet dit überhaupt een tool zijn?
Stel één vraag. Kan de agent dit al met iets wat hij heeft?
Is het antwoord "ja, hij zou gewoon de CLI kunnen draaien", dan heb je geen server nodig. Dan moet je de agent de CLI laten draaien. git log in een MCP-tool stoppen maakt de agent niet beter in git. Het voegt een proces toe, een dependency, en een permanente regel in je tool-lijst, in ruil voor niks wat de Bash-tool je niet al gaf.
Een MCP-server verdient zijn plek wanneer hij iets levert wat de agent echt niet zelf kan krijgen:
- Een credential-grens. De server houdt de API-key vast en geeft drie veilige operaties door, zodat de agent het geheim nooit ziet. Dat is echte waarde die een shell-commando niet kan namaken.
- Een eigen protocol of intern systeem. Iets zonder CLI, zonder publieke docs, zonder enige manier voor het model om de vorm ervan te kennen. Jouw eigen systeem. Hier is het protocol nou juist voor gemaakt.
- State die de agent niet kan vasthouden. Een connection pool, een langlopende sessie, een cache die tussen aanroepen moet blijven leven.
- Een echt onhandige API die je veilig en klein maakt. Niet "ik heb curl ingepakt", maar "ik heb een API met veertig parameters teruggebracht tot de drie operaties die iemand echt gebruikt".
Doet jouw server niks van dit alles, dan zou hij waarschijnlijk niet moeten bestaan. Dan is het een script dat de agent draait, of een regel in een CLAUDE.md, of helemaal niks.
Elke server die je toevoegt heeft een rekening
Twee zelfs.
De eerste is je context window. Elke tool-beschrijving, elke naam, parameter en omschrijving gaat de prompt van het model in nog voordat het iets doet. Tien servers met elk vijf tools is vijftig beschrijvingen die het model op elke beurt leest. Dat is hetzelfde overstromingsprobleem waar ik achteraan ging in contextdiscipline: hoe meer je erbij propt, hoe minder ruimte er overblijft voor het echte werk, en hoe slechter het model nog door de ruis heen kiest. Een tool die de agent zelden nodig heeft is niet gratis. Het is huur, en je betaalt hem elke beurt.
De tweede rekening is vertrouwen, en die slaan mensen over. Een tool-beschrijving is natuurlijke taal die het model leest en waarnaar het handelt. Het beschrijvingsveld is een executiepad. Ik schreef er twee dagen geleden een hele post over waarom de MCP supply chain de nieuwe npm is: configuratie-naar-commando-executie ingebakken in elke officiële SDK, die onschuldige stdio-regel die het pakket van een vreemde een shell op je machine geeft, 67% van de gescande servers met code-injectierisico. Elke server die je aan je tool-lijst toevoegt is een proces dat je vertrouwt met de meest bevoorrechte plek op je machine.
Dus de vraag "moet deze server bestaan" is geen muggenziften. Elke server die je doorlaat kost je context zodra je hem toevoegt, en vertrouwen zodra hij draait.
Voordat je bouwt, kijk wat er al is
Dit is de stap die de twintig-regel-tutorial overslaat. Voordat je een server schrijft, check je of het ding al bestaat en of het te vertrouwen valt. Het ecosysteem is te groot en beweegt te snel om op het oog te beoordelen.
Dat is het gat dat ik met MCP Observatory wilde dichten. Het volgt het hele MCP-landschap, tegen de twintigduizend servers verspreid over npm, PyPI, GitHub en de officiële registries, zodat je twee vragen kunt beantwoorden voordat je kiest. Bestaat dit al? Zoek het op en kom erachter voor je een wrapper opnieuw uitvindt die iemand al onderhoudt. Is het veilig om in te pluggen? Het geeft servers scores op verwaarlozing, ontbrekende licenties, kwetsbaarheden in dependencies en CVE-feeds, en draait statische analyse die precies de command-injectie- en SSRF-patronen markeert die overal bleken te zitten, plus de naamspelletjes die typosquatting laten werken.
Gebruik het beide kanten op. Als zoektool houdt het je tegen om te bouwen wat er al is. Als audittool houdt het je tegen om te vertrouwen wat je niet zou moeten.
De laagdrempeligheid snijdt twee kanten op
Diezelfde twintig regels waarmee jij een nuttige server opzet die credentials bewaakt of een protocol overbrugt, laten ook iedereen een overbodige opzetten, of een kwaadaardige, en die met een badge erop naar een registry sturen. Het protocol maakte bouwen triviaal. Het deed niks om beslissen triviaal te maken, en beslissen is het deel dat altijd al moeilijk was.
Dus bouw die server. De tutorial is echt zo kort. Doe alleen eerst de test, en de test is niet "kan ik dit bouwen". Dat kun je. Het is "moet dit een tool zijn, of ga ik de agent zo een proces laten installeren om iets te doen wat hij al kon".
Meestal is het eerlijke antwoord het tweede.