Naar inhoud springen
26/30Hoofdstuk 26 van 30

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.

terminalBASH
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | npx @modelcontextprotocol/server-everything stdio
TEXT
{"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 maakt

Vóór de wire eerst de rekensom. Je hebt NN AI-applicaties en MM dingen die ze moeten kunnen bereiken — een agenda, een tickettracker, een warehouse-database, een ontwerptool. Zonder gedeeld contract schrijft iemand N×MN \times M 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 N+MN + M.

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.

MCP-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 stdout that 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

De 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 keyverplichtwat het is
io.modelcontextprotocol/protocolVersionjade revisie die deze request spreekt, bijv. "2026-07-28"
io.modelcontextprotocol/clientCapabilitiesjawat de client voor de server kan doen op deze request
io.modelcontextprotocol/clientInfonee (maar zou moeten)clientnaam en -versie, alleen voor weergave en logs
io.modelcontextprotocol/logLevelneehet 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:

one line, split for the pageTEXT
{"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 legacy

De 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:

terminalBASH
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
TEXT
{"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:

TEXT
→ {"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 initialize and would process an era-ambiguous method (such as tools/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 citeren

MCP 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 heeft

De 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:

PrimitiveControlDescriptionExample
PromptsUser-controlledInteractieve templates die door gebruikerskeuze worden aangeroepenSlash commands, menuopties
ResourcesApplication-controlledContextuele data die door de client wordt toegevoegd en beheerdBestandsinhoud, git-geschiedenis
ToolsModel-controlledFuncties die aan de LLM worden exposed om acties uit te voerenAPI 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:

calendar.mjs — the parts that matterJS
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:

TEXT
→ 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:

Hij 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.

Hij 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.

Het 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 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:

TEXT
→ 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:

TEXT
→ 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 sessions

Statelessness 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:

TEXT
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.

Elke 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.

o200k_base tokensTEXT
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 tokens

Twee 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 breekt

Alles 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 veranderdeWasIs nuBreekt
De handshakeinitialize + notifications/initialized, één keer per connectionverwijderd; elke request draagt _meta versie en capabilitieselke client die vóór deze revisie is geschreven
SessionsMcp-Session-Id header, connection-scoped stateverwijderd; state reist in expliciete, door de server geminte handleslist endpoints die per connection varieerden
Discoveryafgeleid uit het initialize resultserver/discover, dat servers moeten implementerenniets, maar implementeren is nu verplicht
Server-to-client callsserver stuurde roots/list, sampling/createMessage, elicitation/createInputRequiredResult en een client retryelke server die een request naar een client pushte
Result shapeelk objectvereiste resultType: "complete" of "input_required"niets: een afwezig veld moet als "complete" worden gelezen
SubscriptionsHTTP GET stream, resources/subscribeéén subscriptions/listen stream met opt-in typeshet GET endpoint is weg
Stream resumptionLast-Event-ID replay op Streamable HTTPverwijderd; een broken stream verliest de request, opnieuw uitgeven met een nieuwe idclients die op redelivery vertrouwden
Rootseen client feature waar servers om konden vragendeprecated (SEP-2577); geef paths door als tool arguments of resource URIsnog niets — twaalfmaandsvenster
Sampling en loggingclient featuresdeprecated (SEP-2577)nog niets — twaalfmaandsvenster
HTTP+SSE transportdeprecated sinds 2025-03-26Deprecated onder de lifecycle policy (SEP-2596)migreer naar Streamable HTTP
Client registrationOAuth 2.0 Dynamic Client Registration, RFC 7591deprecated ten gunste van Client ID Metadata Documentsbehouden voor authorization servers zonder die documenten
Error codes-32002 voor resource not found-32602; -32020-32099 gereserveerd voor de specnieuwe 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 via tasks/get, input halverwege via tasks/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 verward

Dit is de vocabulaire van het hele blok op één plek.

Wat het isWie praat met wieWanneer het het antwoord is
Een gewone APIEen interface voor een programmajouw code ↔ een serviceJij schrijft de caller. Jij beheert het schema, de auth en de error handling, en er is geen discovery-probleem om op te lossen.
MCPEen protocol om tools, data en templates aan een AI-applicatie te exposenhost ↔ server, elk één clientIemand anders schreef de capability en veel hosts moeten die kunnen gebruiken zonder bespoke integration.
RAGEen techniek om tekst te vinden en in de prompt te zettenjouw code ↔ jouw indexHet model moet iets weten. Hoofdstuk 19. MCP is een manier om een retriever te leveren; het is geen retriever.
Agent skillsEen map met een SKILL.md die het model leestmodel ↔ een documentDe kennis is procedureel — hoe wij dit doen — en is proza, geen functie. Hoofdstuk 28.
A2AEen protocol waarin agents als peers samenwerkenagent ↔ agentDe andere kant redeneert, plant en houdt state vast over een lange task, in plaats van een call te beantwoorden.
ACPWas een apart agent-communication protocolHet 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.

Je 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?


Elk 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.

  1. 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 in schema.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

  2. Base Protocol, modelcontextprotocol.io/specification/2026-07-28/basic. Bron van de JSON-RPC-constraints (non-null id, geen id reuse, vereiste resultType); de sectie Statelessness en de note dat een open stdio process geen session is; de reserved-key-tabel _meta en de verplichte/optionele status van elk per-request veld; de -32602-regel voor een ontbrekend verplicht veld; de MissingRequiredClientCapability (-32021)-regel; en het error-code allocation policy. 2 3 4 5

  3. stdio transport, modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. Bron van de newline-delimited framing rules, de purity requirement voor stdout, de allowance voor stderr, 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

  4. 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

  5. Discovery, modelcontextprotocol.io/specification/2026-07-28/server/discover. Bron van de verplichte status van server/discover, de vorm van DiscoverResult, en het veld instructions dat wordt beschreven als „optional natural-language guidance for LLMs on how to use this server effectively”.

  6. 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

  7. 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.

  8. 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 de Mcp-Session-Id header (SEP-2567); statelessness en de removal van initialize (SEP-2575); server/discover (SEP-2575); subscriptions/listen (SEP-2575); Multi Round-Trip Requests en resultType (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

  9. Tools, modelcontextprotocol.io/specification/2026-07-28/server/tools, en Server Features, .../server. Bron van de hierboven gereproduceerde control-hierarchy table; de vormen tools/list en tools/call; het onderscheid isError tussen 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.

  10. Versioning, modelcontextprotocol.io/specification/versioning. Bron van het YYYY-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 op modelcontextprotocol.io/docs/sdk vermeldt TypeScript, Python, C#, Go en Rust als Tier 1, Java en Ruby als Tier 2, en Swift, PHP en Kotlin als Tier 3.

  11. 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.

  12. 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.

Klaar om LIA te laten kiezen?

Bouw met elk AI-model op één plek — begin vandaag nog gratis.