MCP uitgelegd aan de hand van de spec: wat een server echt is
Eén regel JSON naar een subprocess, dertien tooldefinities terug — gelezen tegen de 2026-07-28-revisie zonder handshake.
Op deze pagina
Installeer een gepubliceerde MCP server, stuur hem één regel JSON en lees wat terugkomt.
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| npx @modelcontextprotocol/server-everything stdio{"result":{"tools":[{"name":"echo","title":"Echo Tool","description":"Echoes
back the input string","inputSchema":{"$schema":"http://json-schema.org/draft-07/
schema#","type":"object","properties":{"message":{"type":"string","description":
"Message to echo"}},"required":["message"]},"annotations":{"readOnlyHint":true,
… … 7,663 bytes on one line …
"jsonrpc":"2.0","id":1}Dertien tooldefinities, op één regel, van een process dat één regel van zijn standaardinvoer las. Je hebt nu het Model Context Protocol gesproken, zonder SDK, zonder client library en zonder framework. Dat is alles: een transport, een berichtformaat en een kleine set benoemde methoden.
Hoofdstuk 18 definieerde een tool als twee dingen — een JSON Schema dat het model ziet, en een endpoint in je code dat het model nooit ziet. Hoofdstuk 23 bouwde een harness dat er een catalogus van bijhoudt. Geen van beide beantwoordde de vraag die bepaalt of iets herbruikbaar is: wie schrijft het schema, en hoe komt het van degene die het schreef in je prompt terecht? MCP is één antwoord op die vraag, en het is de moeite waard om het origineel te lezen, omdat bijna alles wat erover geschreven is een revisie beschrijft die niet meer bestaat.
Er zijn drie dingen mis met de command die je net draaide, en elk daarvan is een sectie van dit hoofdstuk. Hij droeg geen protocolversie, dus een conforme server zou hem hebben geweigerd. Hij kreeg toch antwoord, om een reden die de specificatie een risico noemt in plaats van een feature. En hij vroeg om één van drie primitives zonder ooit te ontdekken dat de andere twee bestaan.
Het probleem dat het oplost, en de analogie die de spec zelf maakt
Link naar de sectie: Het probleem dat het oplost, en de analogie die de spec zelf maaktVóór de wire eerst de rekensom. Je hebt AI-applicaties en dingen die ze moeten kunnen bereiken — een agenda, een tickettracker, een warehouse-database, een ontwerptool. Zonder gedeeld contract schrijft iemand integraties, en elke integratie is een schema plus een endpoint plus een authenticatieverhaal plus onderhoudslast. Met zo’n contract schrijft de toolleverancier een server, de applicatieleverancier een client, en het totaal is .
Dat is geen nieuwe observatie, en de specificatie zegt van wie het idee kwam:
MCP takes some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools. In a similar way, MCP standardizes how to integrate additional context and tools into the ecosystem of AI applications.1
Neem die vergelijking letterlijk, niet als compliment. Vóór dat protocol betekende ondersteuning voor een taal in een editor een plugin per editor; daarna leverde een taalteam één server en kreeg elke editor ondersteuning. De maatstaf voor succes was niet elegantie, maar dat het aantal integraties ophield te vermenigvuldigen. Hier volgt hetzelfde uit: de waarde zit in het aantal implementaties, niet in het ontwerp. Een protocol dat twee producten spreken is een dataformaat met extra ceremonie.
Wat er echt over de wire gaat
Link naar de sectie: Wat er echt over de wire gaatMCP-berichten zijn JSON-RPC 2.0. Een request is een object met jsonrpc, een id, een method en optionele params; een response draagt dezelfde id en óf result óf error; een notification is een request zonder id en krijgt geen antwoord. De specificatie voegt daar drie constraints bovenop toe: de id moet een string of een number zijn en mag niet null zijn, hij mag niet botsen met een andere request die nog onderweg is, en elk result moet een resultType-veld dragen.2
Op het stdio transport — het transport dat de command hierboven gebruikte — is de framing-regel één regel per bericht:
Messages are delimited by newlines, and MUST NOT contain embedded newlines. […] The server MUST NOT write anything to its
stdoutthat is not a valid MCP message.3
Die laatste clausule is de meest voorkomende manier waarop een zelfgemaakte server breekt, en hij breekt stilletjes: een verdwaalde console.log, een voortgangsbalk, een deprecation warning uit een dependency, en de line parser van de client raakt iets dat geen JSON is. De uitweg staat in dezelfde sectie — de server mag alles wat hij wil naar stderr schrijven, en de client zou dat niet als fout moeten behandelen. De reference server hierboven print bij elke start Starting default (STDIO) server..., op stderr, waardoor de pipe nog steeds werkte.
Het andere standaardtransport is Streamable HTTP: elk bericht is een POST naar één endpoint, en het antwoord is óf een JSON-object óf een request-scoped stream van Server-Sent Events — het wire-formaat dat Hoofdstuk 14 met de hand parseerde. De semantiek is op beide identiek, omdat een transport een binding is: het definieert framing en levering, niet betekenis.4
Het eerste dat mis was: er was geen versie
Link naar de sectie: Het eerste dat mis was: er was geen versieDe command hierboven stuurde tools/list en verder niets. Onder de huidige revisie is die request malformed, en een conforme server moet hem weigeren.
Sinds 2026-07-28 is MCP een stateless protocol, en de specificatie zegt dat zonder voorbehoud:
The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.2
Elke request draagt dus zijn eigen protocolversie en zijn eigen client capabilities, in een gereserveerd _meta-object binnen params. Twee van die velden zijn verplicht op elke afzonderlijke request; een request die een van beide mist is malformed en de server moet antwoorden met -32602:2
_meta key | verplicht | wat het is |
|---|---|---|
io.modelcontextprotocol/protocolVersion | ja | de revisie die deze request spreekt, bijv. "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | ja | wat de client voor de server kan doen op deze request |
io.modelcontextprotocol/clientInfo | nee (maar zou moeten) | clientnaam en -versie, alleen voor weergave en logs |
io.modelcontextprotocol/logLevel | nee | het minimale logniveau dat de server voor deze request moet uitstoten |
Voluit geschreven ziet een correcte tools/list er zo uit — en dit is de laatste keer dat dit hoofdstuk de metadata volledig toont, omdat die vanaf hier op elke request staat:
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},
"io.modelcontextprotocol/clientInfo":{"name":"bare-hands","version":"0.0.1"}}}}Het capability-object is de onderhandeling. Er is geen aparte onderhandelingsstap meer: de client declareert per request wat hij kan, de server declareert in het result wat hij kan, en geen van beide kanten mag een feature gebruiken die de ander niet heeft geclaimd. Een server die een capability nodig heeft die de client niet declareerde moet -32021 antwoorden en de ontbrekende capability in data.requiredCapabilities noemen. Een server die de gevraagde versie niet spreekt moet -32022 antwoorden en de versies noemen die hij wel spreekt.2
Clients die het antwoord vooraf willen, kunnen erom vragen: server/discover is een verplichte RPC die ondersteunde versies, capabilities, identity en een optioneel blok instructions in één round trip teruggeeft.5 Die aanroepen is optioneel. Die implementeren niet.
Het tweede dat mis was: de server was legacy
Link naar de sectie: Het tweede dat mis was: de server was legacyDe command werkte. Onder de huidige revisie had dat niet gemogen, en de reden verdient eerder een meting dan een alinea, omdat hij de staat van het hele ecosystem in één regel weergeeft.
Probe de reference server zoals de specificatie een moderne client opdraagt te proben:
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}' \
| npx @modelcontextprotocol/server-everything stdio{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}Dat is de derde tak van de compatibiliteitsregel: een DiscoverResult betekent modern, een herkende moderne error betekent modern-maar-verkeerde-versie, en alles anders — inclusief -32601 — betekent legacy, val terug op de initialize handshake.3 Doe dat dus, terwijl je om de huidige revisie vraagt:
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28",
"capabilities":{},"clientInfo":{"name":"bare-hands","version":"0.0.1"}}}
← {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":true},
"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},
"logging":{},"tasks":{…},"completions":{}},"serverInfo":{"name":"mcp-servers/everything",
"title":"Everything Reference Server","version":"2.0.0"},"instructions":"…"}}De client vroeg om 2026-07-28 en de server antwoordde 2025-11-25. Op 7 september 2026 implementeert de officiële reference server — npm package @modelcontextprotocol/server-everything, versie 2026.8.31, gepubliceerd op 31 augustus 2026 — de huidige revisie niet. Dat geldt, afgaande op de datums, ook voor de TypeScript SDK waarop hij is gebouwd: release 1.30.0 kwam uit op 27 juli 2026, de dag vóór de revisie.
Lees de consequentie in plaats van de roddel. Bijna alles wat over MCP is geschreven beschrijft een protocol met een initialize handshake, een session, een roots/list request die de server naar de client stuurt, en een HTTP+SSE transport. Alle vier zijn weg of op weg naar buiten. Wanneer je iets over MCP leest, inclusief deze pagina, is het eerste waar je naar moet zoeken een revisienummer.
En de reden dat de allereerste command werkte, staat in de specificatie als een risico, niet als feature:
some legacy servers do not validate that a request arrives after
initializeand would process an era-ambiguous method (such astools/call) under legacy semantics. Probing yields a deterministic failure instead.3
Gemeten: tools/list naar die server sturen zonder enige handshake geeft de volledige catalogus terug. Een methode die geweigerd had moeten worden, werd bediend, en dat is precies waarom de specificatie zegt dat je eerst met server/discover moet proben, zelfs als je alleen moderne versies ondersteunt.
Drie rollen, en de zin die je uit het hele document moet citeren
Link naar de sectie: Drie rollen, en de zin die je uit het hele document moet citerenMCP heeft drie partijen, en het onderscheid tussen de eerste twee is precies wat mensen op één hoop gooien:
Host. De applicatie: het chatproduct, de editor, de agent. Hij bezit de conversatie, het model, de credentials en de toestemming van de gebruiker. Hij maakt clients aan en handhaaft de security boundary ertussen.
Client. Een connector binnen de host. Elke client praat met exact één server — een strikte 1:1-relatie — en hangt de protocolversie en capabilities aan elke request die hij route.
Server. Een process of service die resources, tools en prompts exposeert. Hij kan lokaal of remote zijn, werkt onafhankelijk, en zijn hele taak is één afgebakend gebied.6
Die regel „exact één server” is geen administratie. Hij maakt het ontwerpprincipe hieronder implementeerbaar, en dit is de zin die je uit de specificatie moet meenemen als je er maar één meeneemt:
Servers should not be able to read the whole conversation, nor "see into" other servers. Servers receive only necessary contextual information. Full conversation history stays with the host. Each server maintains isolation. Cross-server interactions are controlled by the host.6
Dat keert het mentale model om waarmee de meeste mensen aankomen. Een weerserver die je met je assistant verbindt ziet niet wat je vroeg. Hij ziet een tools/call met de arguments die het model koos, en verder niets — niet de eerdere turns, niet je system prompt, niet de results die de agendaserver zojuist teruggaf. Als twee servers moeten samenwerken, draagt de host bewust een waarde van de ene naar de andere, omdat het model daarom vroeg. Daarom is isolation de security-eigenschap waarop Hoofdstuk 30 leunt: een gecompromitteerde server heeft een kleine, gedefinieerde blast radius, en die vergroten vereist medewerking van de host.
Het derde: drie primitives, gesorteerd op wie de leiding heeft
Link naar de sectie: Het derde: drie primitives, gesorteerd op wie de leiding heeftDe eerste command vroeg die server om tools en kreeg er dertien. Stel hem de andere twee vragen en hij beantwoordt die ook: resources/list geeft zeven terug, prompts/list geeft vier terug. Geen daarvan verscheen, omdat niets erom vroeg. Daarmee komen we bij de pedagogische ruggengraat van MCP, die in de specificatie staat als een tabel die bijna niemand citeert:
| Primitive | Control | Description | Example |
|---|---|---|---|
| Prompts | User-controlled | Interactieve templates die door gebruikerskeuze worden aangeroepen | Slash commands, menuopties |
| Resources | Application-controlled | Contextuele data die door de client wordt toegevoegd en beheerd | Bestandsinhoud, git-geschiedenis |
| Tools | Model-controlled | Functies die aan de LLM worden exposed om acties uit te voeren | API POST requests, bestanden schrijven |
Niet „drie manieren om een capability te exposen”. Drie antwoorden op wie beslist dat dit gebeurt. Het model beslist een tool aan te roepen. De applicatie beslist een resource toe te voegen. De persoon beslist een prompt te draaien. Begrijp je dat verkeerd, dan werkt de feature nog steeds, maar op het verkeerde moment en om de verkeerde reden.
De duidelijkste manier om het te voelen is een agenda. Hier is een server die dezelfde agenda drie keer exposeert, één keer als elke primitive, in honderd regels plain Node zonder dependencies:
const TOOL = {
name: "create_event",
description: "Create a calendar event. Writes to the calendar.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "Event title." },
startsAt: { type: "string", format: "date-time", description: "Start, ISO 8601 UTC." },
},
required: ["title"],
},
};
switch (method) {
case "resources/read":
return ok(id, { contents: [{ uri: "calendar://week",
mimeType: "application/json", text: JSON.stringify(EVENTS) }],
ttlMs: 60000, cacheScope: "private" });
case "prompts/get":
return ok(id, { description: PROMPT.description, messages: [{ role: "user",
content: { type: "text", text: `Read calendar://week and draft a plan. ` +
`Focus: ${params.arguments?.focus ?? "balance"}.` } }] });
case "tools/list":
return ok(id, { tools: [TOOL], ttlMs: 300000, cacheScope: "public" });
}Draai hem en vraag het op alle drie manieren. Echte output, één bericht per regel op de wire, hier voor de pagina gewrapt, met de request _meta en het identity-blok van de server weggelaten:
→ resources/read {"uri":"calendar://week"}
← {"resultType":"complete","contents":[{"uri":"calendar://week",
"mimeType":"application/json","text":"[{\"id\":\"e1\",\"title\":\"Standup\",
\"startsAt\":\"2026-09-07T09:00:00Z\"},{\"id\":\"e2\",\"title\":\"Design review\",
\"startsAt\":\"2026-09-09T15:00:00Z\"}]"}],"ttlMs":60000,"cacheScope":"private"}
→ prompts/get {"name":"prepare_week","arguments":{"focus":"deep work"}}
← {"resultType":"complete","description":"Read the week and draft a plan.",
"messages":[{"role":"user","content":{"type":"text",
"text":"Read calendar://week and draft a plan. Focus: deep work."}}]}
→ tools/call {"name":"create_event","arguments":{"title":"Dentist",
"startsAt":"2026-09-10T08:30:00Z"}}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],
"structuredContent":{"id":"e3","title":"Dentist","startsAt":"2026-09-10T08:30:00Z"},
"isError":false}Drie methoden, drie vormen, één agenda. Nu het punt:
De week lezen is een resource
Link naar de sectie: De week lezen is een resourceHij wordt aangesproken via een URI, is inert, en de applicatie beslist of hij aan de conversatie wordt toegevoegd. Niets in het protocol laat het model er uit zichzelf naar grijpen. Het result draagt ttlMs en cacheScope, nieuw in deze revisie, zodat de client de week een minuut kan cachen in plaats van te pollen.
Een event maken is een tool
Link naar de sectie: Een event maken is een toolHij heeft een schema, hij heeft side effects, en het model beslist wanneer hij wordt aangeroepen. Het result draagt isError, het veld waar Hoofdstuk 18 voor pleitte: een validatiefout komt terug als een tool result dat het model kan lezen en corrigeren, niet als protocol error.
„Bereid mijn week voor” is een prompt
Link naar de sectie: „Bereid mijn week voor” is een promptHet is een benoemde template met arguments die de persoon aanroept — de slash command in het menu. Hij geeft messages terug, geen antwoord. Het is een manier voor een serverauteur om de phrasing te leveren die werkt met zijn eigen tools, wat precies de kennis is die de serverauteur heeft en de gebruiker niet.
Bijna iedereen maakt van alle drie tools. Het resultaat is een catalogus waarin een read die de applicatie stil had moeten toevoegen concurreert om de attention van het model met een write die approval nodig heeft, en waarin dat ene ding waarvoor een persoon een knop wilde, begraven ligt in een schema. Het kost niets om dit goed te doen, en het wordt beslist voordat je één regel schrijft.
De server kan jou niet bellen
Link naar de sectie: De server kan jou niet bellenDe calendar tool heeft één verplicht argument, title, en een optioneel startsAt. Vraag hem een event te maken zonder datum, en er komt iets interessants terug:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"}}
← {"resultType":"input_required",
"inputRequests":{"when":{"method":"elicitation/create","params":{"mode":"form",
"message":"When should \"Dentist\" start?",
"requestedSchema":{"type":"object",
"properties":{"startsAt":{"type":"string","format":"date-time"}},
"required":["startsAt"]}}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}De server stuurde geen request. Hij beantwoordde de request die hij kreeg, met resultType: "input_required" en een beschrijving van wat hij nog nodig heeft. De client verzamelt het antwoord van de persoon, en stuurt daarna de oorspronkelijke call opnieuw — met een nieuwe id, met inputResponses en een echo van de opaque requestState:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"},
"inputResponses":{"when":{"action":"accept",
"content":{"startsAt":"2026-09-10T08:30:00Z"}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],"isError":false}Dit is Multi Round-Trip Requests, geïntroduceerd in de huidige revisie, en het verving het oudere ontwerp waarin servers JSON-RPC requests terug naar clients stuurden. De transportspecificatie stelt de regel nu vlak: „servers do not initiate JSON-RPC requests and clients do not send JSON-RPC responses”.4 Er is één richting van initiatief, en die behoort aan de host.
Twee client-side features liften mee op dat mechanisme, en één daarvan heeft een naam die je laat struikelen.
Elicitation is de server die de persoon om iets vraagt: een formulier met een bewust beperkt JSON Schema — platte objecten, primitieve properties, geen nesting — zodat elke client het zonder layout engine kan renderen. Het draagt een harde regel: servers mogen niet form mode gebruiken om te vragen naar „passwords, API keys, access tokens, or payment credentials”, en moeten URL mode gebruiken voor die dingen, waarbij de gebruiker naar een pagina gaat die de client nooit leest.7
Sampling is de server die het model van de host om een generation vraagt, zodat een server intelligent kan zijn zonder een API key te houden. En hier is de vocabulairewaarschuwing, omdat dit woord in deze cursus al iets anders betekent: dit is niet de sampling uit Hoofdstuk 17. Niets hier gaat over temperature, top-p of de vorm van een probability distribution. Het is een nested model call die achteruit door een protocol reist.
Er is een tweede reden om er niet naar te grijpen: vanaf deze revisie is sampling deprecated, naast roots en logging, onder SEP-2577, met een botte voorgestelde migratie — „integrate directly with LLM provider APIs instead of Sampling”.8 Het idee faalde niet technisch; het rechtvaardigde zijn surface area niet, en een protocol dat dingen kan verwijderen is gezonder dan een protocol dat dat niet kan.
Maak het expres stuk: connections zijn geen sessions
Link naar de sectie: Maak het expres stuk: connections zijn geen sessionsStatelessness klinkt als een wire-formatdetail totdat je het test. Neem de drie-berichtenuitwisseling hierboven en draai elk bericht in een apart process — een verse node calendar.mjs, geen gedeeld geheugen, niets meegenomen:
process A tools/call (no date) → resultType: input_required
requestState: eyJ0aXRsZSI6IkRlbnRpc3QifQ==
process B tools/call (with the answer, same requestState)
→ resultType: complete
"Created e3: Dentist at 2026-09-10T08:30:00Z"
process C resources/read calendar://week
→ events: 2 (Standup, Design review)Process B, dat de vraag nooit zag, voltooide een multi-round-trip call die process A startte. Dat is het punt van requestState: de continuation reist in het bericht, dus niets hangt ervan af dat het process hetzelfde is.
Process C is de failure. Het event is gemaakt en staat er niet — omdat de toy server EVENTS bewaart in een array op module-niveau, en een array op module-niveau is connection state. De note van de specificatie benoemt de fout precies:
an open connection, such as a STDIO process, is not a conversation or session: clients may interleave unrelated requests on the same transport, and a server must not treat connection or process identity as a proxy for conversation or session continuity.2
De voorgeschreven fix is geen session. Het is een expliciete handle: een creation tool geeft een opaque identifier terug, en elke latere call neemt die als gewoon argument. Het protocol heeft er helemaal geen concept van — „from the wire's perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls”.9 Dat zet het model aan het stuur om hem mee te dragen, en zet de server aan het stuur om bij elke afzonderlijke call te valideren dat deze caller hem mag gebruiken, omdat een handle een naam is en geen toestemming.
Wat een server kost voordat hij iets doet
Link naar de sectie: Wat een server kost voordat hij iets doetElke tool die een server exposeert is een schema dat bij elke request je prompt in gaat, en Hoofdstuk 24 mat wat dat met een window doet. MCP voegt een tweede regel toe die makkelijk te missen is, dus beide zijn het waard om te tellen op de reference server hierboven.
13 tool definitions (name + description + inputSchema): 1,307 tokens
cheapest tool, get-tiny-image 52
costliest tool, gzip-file-as-resource 235
server `instructions`, returned by discovery: 312 tokens
------
one server, connected, before it is used: 1,619 tokensTwee observaties. De eerste is rekenkunde: verbind vijf servers van deze grootte en grofweg achtduizend tokens van je window zijn bij elke turn bezet, voor altijd, of het model ze nu gebruikt of niet — dat is het mechanisme achter de reductie van 150.000 naar 2.000 die Hoofdstuk 24 citeerde, en de reden dat just-in-time tool discovery bestaat.
De tweede is een security note vermomd als boekhouding. instructions is natural-language text, geschreven door de serverauteur, die in de prompt van de host belandt, en de tool descriptions ernaast zijn hetzelfde. De specificatie zegt in haar eigen security principles wat je daarmee moet doen: tool annotations en descriptions „should be considered untrusted, unless obtained from a trusted server”, en hosts „must obtain explicit user consent before invoking any tool”.1 Een MCP server verbinden is geen dependency toevoegen. Het is een vreemde 1.619 tokens van je system prompt geven en het recht om aangeroepen te worden. Hoofdstuk 30 is wat er gebeurt wanneer die vreemde vijandig is.
Gedateerde sectie: de 2026-07-28-revisie, en wat die breekt
Link naar de sectie: Gedateerde sectie: de 2026-07-28-revisie, en wat die breektAlles in deze sectie is waar voor protocolrevisie 2026-07-28, de huidige, gelezen op 7 september 2026. Revisies zijn gedateerd YYYY-MM-DD en de datum is de laatste keer dat een backwards-incompatible change is gemaakt.10 Het normatieve document is een TypeScript-bestand, schema/2026-07-28/schema.ts; het JSON Schema ernaast wordt daaruit gegenereerd, en daarom wordt de specificatie hier in TypeScript gelezen en is MCP onderwijzen vanuit iets anders een vertaling onderwijzen.
| Wat veranderde | Was | Is nu | Breekt |
|---|---|---|---|
| De handshake | initialize + notifications/initialized, één keer per connection | verwijderd; elke request draagt _meta versie en capabilities | elke client die vóór deze revisie is geschreven |
| Sessions | Mcp-Session-Id header, connection-scoped state | verwijderd; state reist in expliciete, door de server geminte handles | list endpoints die per connection varieerden |
| Discovery | afgeleid uit het initialize result | server/discover, dat servers moeten implementeren | niets, maar implementeren is nu verplicht |
| Server-to-client calls | server stuurde roots/list, sampling/createMessage, elicitation/create | InputRequiredResult en een client retry | elke server die een request naar een client pushte |
| Result shape | elk object | vereiste resultType: "complete" of "input_required" | niets: een afwezig veld moet als "complete" worden gelezen |
| Subscriptions | HTTP GET stream, resources/subscribe | één subscriptions/listen stream met opt-in types | het GET endpoint is weg |
| Stream resumption | Last-Event-ID replay op Streamable HTTP | verwijderd; een broken stream verliest de request, opnieuw uitgeven met een nieuwe id | clients die op redelivery vertrouwden |
| Roots | een client feature waar servers om konden vragen | deprecated (SEP-2577); geef paths door als tool arguments of resource URIs | nog niets — twaalfmaandsvenster |
| Sampling en logging | client features | deprecated (SEP-2577) | nog niets — twaalfmaandsvenster |
| HTTP+SSE transport | deprecated sinds 2025-03-26 | Deprecated onder de lifecycle policy (SEP-2596) | migreer naar Streamable HTTP |
| Client registration | OAuth 2.0 Dynamic Client Registration, RFC 7591 | deprecated ten gunste van Client ID Metadata Documents | behouden voor authorization servers zonder die documenten |
| Error codes | -32002 voor resource not found | -32602; -32020–-32099 gereserveerd voor de spec | nieuwe codes -32020, -32021, -32022 |
De governancewijziging onder die tabel is belangrijker dan één losse rij. Deze revisie nam een feature lifecycle and deprecation policy aan: features zijn Active, Deprecated of Removed, een deprecated feature documenteert zijn migratiepad en blijft minstens twaalf maanden in de specificatie voordat hij in aanmerking komt voor removal, en er is een registry met alles wat momenteel Deprecated is.8 Vóór dat beleid betekende „deprecated” in een AI-protocol wat de laatste blogpost zei. Nu betekent het een datum.
Details tonen
Extensions, het deel waar nog niemand over heeft geschreven.
Naast de core definieert MCP optionele extensions — „always opt-in and require explicit support from both client and server”, gedeclareerd via een extensions-veld in de capabilities van client en server.1 Drie zijn het waard om bij naam te kennen:
- Tasks (
io.modelcontextprotocol/tasks), in deze revisie uit het core protocol verplaatst naar een officiële extension: asynchronous execution van langlopende operations, met polling viatasks/get, input halverwege viatasks/update, en durable handles. Het is het antwoord op een tool die twintig minuten duurt, wat Hoofdstuk 23 oploste met een progress event en een signal dat de tool bereikt. - Skills over MCP, een working group die agent skills — het onderwerp van Hoofdstuk 28 — discoverable en consumable maakt via het protocol.
- MCP Apps, interactieve UI inline gerenderd in de conversatie: charts, forms, video players.
En merk op wat „negotiated” nu betekent: er is geen initialization om bij te negotiaten, dus een extension wordt per request gedeclareerd zoals alles.
Waar MCP staat, tegenover alles waarmee het wordt verward
Link naar de sectie: Waar MCP staat, tegenover alles waarmee het wordt verwardDit is de vocabulaire van het hele blok op één plek.
| Wat het is | Wie praat met wie | Wanneer het het antwoord is | |
|---|---|---|---|
| Een gewone API | Een interface voor een programma | jouw code ↔ een service | Jij schrijft de caller. Jij beheert het schema, de auth en de error handling, en er is geen discovery-probleem om op te lossen. |
| MCP | Een protocol om tools, data en templates aan een AI-applicatie te exposen | host ↔ server, elk één client | Iemand anders schreef de capability en veel hosts moeten die kunnen gebruiken zonder bespoke integration. |
| RAG | Een techniek om tekst te vinden en in de prompt te zetten | jouw code ↔ jouw index | Het model moet iets weten. Hoofdstuk 19. MCP is een manier om een retriever te leveren; het is geen retriever. |
| Agent skills | Een map met een SKILL.md die het model leest | model ↔ een document | De kennis is procedureel — hoe wij dit doen — en is proza, geen functie. Hoofdstuk 28. |
| A2A | Een protocol waarin agents als peers samenwerken | agent ↔ agent | De andere kant redeneert, plant en houdt state vast over een lange task, in plaats van een call te beantwoorden. |
| ACP | Was een apart agent-communication protocol | — | Het is geen live vergelijking meer. Zie hieronder. |
Twee daarvan verdienen elk een zin, omdat daar de verwarring echt zit.
MCP tegenover A2A is geen rivaliteit, en beide specificaties zeggen dat ook. De A2A-documentatie trekt de lijn op basis van wat er aan de andere kant zit: MCP „defines how an AI agent interacts with and utilizes individual tools and resources, such as a database or an API”, waarbij een tool „specific, often stateless, functions” uitvoert; A2A gaat over agents, „more autonomous systems” die „reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues”. De eigen samenvatting is de zin om te onthouden: „A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.”11 De twee nesten — een applicatie gebruikt A2A om andere agents te bereiken, en elke agent gebruikt MCP om zijn eigen tools te bereiken. Hoofdstuk 25 trok die lijn binnen één process, tussen een sub-agent iets vragen en hem de conversatie overdragen; A2A trekt hem tussen organisaties.
MCP tegenover ACP is een vergelijking met een verouderde premisse, en precies daarom is hij het beantwoorden waard. Het Agent Communication Protocol was een aparte open standaard voor agent-to-agent messaging. De eigen documentatie opent nu met de melding: „ACP is now part of A2A under the Linux Foundation!”12 Het eerlijke antwoord op „MCP of ACP?” in september 2026 is dat de vraag één optie minder heeft dan de pagina’s die ervoor ranken suggereren.
En de vergelijking waar mensen het meest om vragen, mcp vs api, heeft het minst interessante antwoord: MCP is een API. Wat het toevoegt is geen power, maar conventies — een vaste set method names, een discovery call, een control hierarchy over de primitives, en een isolation model. Je geeft de vrijheid op om je eigen interface te ontwerpen en krijgt elke host die het protocol spreekt, de ruil die elk protocol ooit heeft aangeboden.
Waar dit hierna naartoe gaat
Link naar de sectie: Waar dit hierna naartoe gaatJe kunt de specificatie nu zonder vertaler lezen, een resource van een tool van een prompt onderscheiden op basis van wie erover gaat, een request met de hand typen wanneer een client library tegen je liegt, en elk MCP-artikel dat je leest dateren aan welke deprecated features het nog als actueel onderwijst.
Wat je nog niet hebt gedaan, is er één shippen. Hoofdstuk 27 schrijft dezelfde server twee keer — TypeScript en Python naast elkaar, omdat MCP het ene echt tweetalige terrein in deze cursus is en de cijfers dat in beide richtingen zeggen. Het behandelt de twee live transports correct, de inspector, packaging, en de helft van het protocol die dit hoofdstuk bewust met rust liet: authorization. Want zodra je server remote is in plaats van een subprocess op je eigen laptop, presenteert de client van een vreemde een token, en de regel van de specificatie over wat je daarmee mag doen is ongewoon streng.
Dat roept de vraag op die het volgende hoofdstuk moet beantwoorden, en het is geen vriendelijke: als een token bij je server aankomt en is uitgegeven voor het audience van iemand anders, wat houdt je dan precies tegen om het door te sturen?
Bronnen en methode
Link naar de sectie: Bronnen en methodeElk citaat, elke method name, error code en regel in dit hoofdstuk is gelezen uit de Model Context Protocol-specificatie, revisie 2026-07-28, op 7 september 2026. Elke trace is lokaal geproduceerd op Node 22: de toy calendar server is 101 regels zonder dependencies, en de reference server is het hieronder genoemde gepubliceerde npm package. Er is geen betaalde API aangeroepen om dit hoofdstuk te schrijven — niets hier heeft een model nodig, wat op zichzelf het punt is.
De metingen: @modelcontextprotocol/server-everything@2026.8.31, gepubliceerd op 31 augustus 2026, gebouwd op @modelcontextprotocol/sdk@1.30.0, gepubliceerd op 27 juli 2026 — één dag vóór de revisie die dit hoofdstuk beschrijft. Het antwoordt op server/discover met -32601, onderhandelt 2025-11-25 wanneer om 2026-07-28 wordt gevraagd, en serveert tools/list zonder enige handshake. De catalogus is 13 tools in 7.663 bytes; token counts zijn o200k_base via tiktoken, over de name, description en inputSchema van elke definition, wat is wat een provider in je prompt rendert en niet wat het JSON-RPC frame weegt.
Anthropic, Code execution with MCP: building more efficient agents, 4 november 2025, is de bron van het 150.000-naar-2.000-cijfer, geciteerd en gebruikt in Hoofdstuk 24 en hier alleen naar verwezen.
Referenties
Link naar de sectie: Referenties-
Specification,
modelcontextprotocol.io/specification/latest(redirecting naar/2026-07-28), gelezen op 7 september 2026. Bron van de vergelijking met het Language Server Protocol; de uitspraak dat de specificatie „based on the TypeScript schema inschema.ts” is; de samenvatting van het base protocol („Stateless, self-contained requests”, „Per-request capability negotiation”); de extension-lijst (Tasks, Skills over MCP, MCP Apps) en de uitspraak dat extensions „are always opt-in and require explicit support from both client and server”; en de Security and Trust & Safety principles, inclusief „Hosts must obtain explicit user consent before invoking any tool” en de behandeling van tool annotations als untrusted. ↩ ↩2 ↩3 -
Base Protocol,
modelcontextprotocol.io/specification/2026-07-28/basic. Bron van de JSON-RPC-constraints (non-null id, geen id reuse, vereisteresultType); de sectie Statelessness en de note dat een open stdio process geen session is; de reserved-key-tabel_metaen de verplichte/optionele status van elk per-request veld; de-32602-regel voor een ontbrekend verplicht veld; deMissingRequiredClientCapability(-32021)-regel; en het error-code allocation policy. ↩ ↩2 ↩3 ↩4 ↩5 -
stdio transport,
modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. Bron van de newline-delimited framing rules, de purity requirement voorstdout, de allowance voorstderr, en de backward-compatibility probe met drie outcomes — inclusief de waarschuwing dat sommige legacy servers era-ambiguous methods zonder handshake verwerken, wat de meting in dit hoofdstuk reproduceert. ↩ ↩2 ↩3 -
Transports overview,
modelcontextprotocol.io/specification/2026-07-28/basic/transports. Bron van de framing „a transport is a binding” en van de uitspraak dat servers geen JSON-RPC requests initiëren en clients geen JSON-RPC responses sturen. ↩ ↩2 -
Discovery,
modelcontextprotocol.io/specification/2026-07-28/server/discover. Bron van de verplichte status vanserver/discover, de vorm vanDiscoverResult, en het veldinstructionsdat wordt beschreven als „optional natural-language guidance for LLMs on how to use this server effectively”. ↩ -
Architecture,
modelcontextprotocol.io/specification/2026-07-28/architecture. Bron van de definities van host/client/server, de 1:1-regel van client naar server, de vier ontwerpprincipes, waarvan het isolation principle hier is geciteerd zonder de vijfde bullet, „Host process enforces security boundaries”, en de sectie over capability negotiation. ↩ ↩2 -
Elicitation,
.../client/elicitation, en Sampling,.../client/sampling. Bron van de twee elicitation modes en hun beperkte schema; het verbod om credentials via form mode te vragen; de sampling definition, de human-in-the-loop requirement, en de deprecation warning die eraan hangt. ↩ -
Key Changes,
modelcontextprotocol.io/specification/2026-07-28/changelog, en Feature lifecycle and deprecation policy,.../community/feature-lifecycle. Bron van elke rij van de changetabel: removal van sessions en deMcp-Session-Idheader (SEP-2567); statelessness en de removal vaninitialize(SEP-2575);server/discover(SEP-2575);subscriptions/listen(SEP-2575); Multi Round-Trip Requests enresultType(SEP-2322); removal van stream resumability (SEP-2575); de deprecation van Roots, Sampling en Logging (SEP-2577); de herclassificatie van HTTP+SSE (SEP-2596); de deprecation van Dynamic Client Registration ten gunste van Client ID Metadata Documents; de error-code renumbering; en het twaalfmaands deprecation window. ↩ ↩2 -
Tools,
modelcontextprotocol.io/specification/2026-07-28/server/tools, en Server Features,.../server. Bron van de hierboven gereproduceerde control-hierarchy table; de vormentools/listentools/call; het onderscheidisErrortussen protocol errors en tool execution errors; de tool-name rules en de namespace note die „prefixing tool names with a server identifier” aanbeveelt; en de non-normative guidance „Stateful Tools” over expliciete handles. ↩ -
Versioning,
modelcontextprotocol.io/specification/versioning. Bron van hetYYYY-MM-DD-schema, de revisiestatussen Draft/Current/Final, de bevestiging dat 2026-07-28 current is, en de per-request negotiation rules. De SDK tier table opmodelcontextprotocol.io/docs/sdkvermeldt TypeScript, Python, C#, Go en Rust als Tier 1, Java en Ruby als Tier 2, en Swift, PHP en Kotlin als Tier 3. ↩ -
A2A Protocol, version 1.0.0,
a2a-protocol.org— de specificatie en de pagina A2A and MCP: Relationship and Distinction, gelezen op 7 september 2026. Bron van het onderscheid tussen tools en agents, de uitspraak dat de twee protocollen „address distinct but highly complementary needs”, en de partnering/using-formulering. ↩ -
Agent Communication Protocol,
agentcommunicationprotocol.dev, gelezen op 7 september 2026: „ACP is now part of A2A under the Linux Foundation!”, een banner toegevoegd boven een specificatie die nog steeds volledig wordt geserveerd — architecture, agent manifest, agent discovery, message structure, stateful agents, run lifecycle en de REST endpoint list antwoorden allemaal nog 200. De specificatie ging niet weg; het project wel. ↩