Wanneer bouw je een MCP-client?
Je bouwt een MCP-client als je een eigen applicatie wilt die gebruikmaakt van MCP-servers. Dat kan een chatbot zijn, een automatiseringspipeline, een CI-systeem dat code analyseert of een intern dashboard dat AI-functionaliteit integreert.
Bestaande AI-clients zoals Claude Desktop, Cursor en Cline zijn al volledig MCP-compatibel. Bouw een eigen client als je een specifieke workflow wilt automatiseren of als je MCP-servers wilt inzetten in een applicatie zonder grafische interface.
Client versus server
De MCP-terminologie is vanuit het netwerk-perspectief: jouw applicatie is de client die verbinding maakt met een server die tools aanbiedt. Het AI-model zit aan de kant van de client.
Installatie
npm install @modelcontextprotocol/sdk
npm install @anthropic-ai/sdk
Je hebt de Anthropic SDK nodig om Claude de tools te laten gebruiken. Het MCP-protocol zelf beschrijft niet hoe je het model aanstuurt, alleen hoe je tools ontdekt en aanroept.
Verbinding opzetten via stdio
Stdio gebruik je voor een lokale server die je als subprocess start. De transport-laag beheert dan de communicatie via stdin en stdout.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "node",
args: ["/pad/naar/mcp-server/dist/index.js"],
});
const client = new Client(
{ name: "mijn-app", version: "1.0.0" },
{ capabilities: { tools: {}, resources: {} } }
);
await client.connect(transport);
StdioClientTransport start de server als subprocess. Je geeft het startcommando mee en de transport-laag beheert de stdin/stdout-communicatie.
Tools ontdekken
const toolsResponse = await client.listTools();
for (const tool of toolsResponse.tools) {
console.log(`Tool: ${tool.name}`);
console.log(`Beschrijving: ${tool.description}`);
console.log(`Parameters:`, tool.inputSchema);
}
De inputSchema is een JSON Schema-object dat je kunt doorgeven aan het AI-model als tool-definitie.
Tools aanroepen via Claude
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const mcpToolsToAnthropicFormat = toolsResponse.tools.map((tool) => ({
name: tool.name,
description: tool.description ?? "",
input_schema: tool.inputSchema as Anthropic.Tool["input_schema"],
}));
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "Maak een ticket aan voor het login-probleem, hoge prioriteit" }
];
const response = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 4096,
tools: mcpToolsToAnthropicFormat,
messages,
});
De tool-loop implementeren
Claude geeft een tool_use-blok terug als het een tool wil aanroepen. Je roept dan de MCP-server aan en geeft het resultaat terug als tool_result:
while (response.stop_reason === "tool_use") {
const toolUseBlock = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
if (!toolUseBlock) break;
const toolResult = await client.callTool({
name: toolUseBlock.name,
arguments: toolUseBlock.input as Record<string, unknown>,
});
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUseBlock.id,
content: toolResult.content as Anthropic.ToolResultBlockParam["content"],
},
],
});
const nextResponse = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 4096,
tools: mcpToolsToAnthropicFormat,
messages,
});
response = nextResponse;
}
console.log("Eindantwoord:", response.content);
Maximaal aantal rondes begrenzen
Een tool-loop kan in theorie blijven doorlopen als het model steeds nieuwe tools wil aanroepen. Houd een teller bij en breek na bijvoorbeeld tien rondes af, zodat een vastgelopen of dure loop je rekening niet laat ontsporen.
Verbinding via Streamable HTTP
Voor remote MCP-servers gebruik je sinds de MCP-specificatie van maart 2025 de Streamable HTTP-transport. Die werkt via een enkel endpoint (meestal /mcp) en vervangt de oudere HTTP-met-SSE-transport.
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mijn-mcp-server.example.com/mcp")
);
await client.connect(transport);
De rest van de code blijft identiek: listTools(), callTool() en de tool-loop werken precies hetzelfde. Alleen het transport-object wisselt.
StreamableHTTPClientTransport ondersteunt een tweede argument voor onder meer een authProvider voor OAuth 2.0, zodat je tegen beveiligde remote-servers kunt verbinden.
Verbinding via de oudere SSE-transport
Werk je met een server die nog niet is gemigreerd, dan kun je terugvallen op SSEClientTransport. Deze transport is gemarkeerd als legacy en verschillende platforms stoppen de ondersteuning ervan in de loop van 2026.
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const transport = new SSEClientTransport(
new URL("https://mijn-mcp-server.example.com/sse")
);
SSE is verouderd
Gebruik Streamable HTTP voor nieuwe integraties. Houd SSEClientTransport alleen aan als fallback voor servers die nog niet zijn bijgewerkt, en plan de overstap. Een robuust patroon is: probeer eerst Streamable HTTP en val pas bij een verbindingsfout terug op SSE.
Resources lezen
Naast tools kan een server resources aanbieden: read-only data die je als context aan het model meegeeft.
const resourcesResponse = await client.listResources();
const resource = await client.readResource({
uri: "tickets://open",
});
console.log(resource.contents[0].text);
Verbinding sluiten
await client.close();
Sluit altijd de verbinding als je klaar bent. Bij de stdio-transport stopt dit ook het server-subprocess, zodat je geen processen laat rondzweven.
Foutafhandeling
Wikkel tool-aanroepen altijd in try-catch. Een MCP-server kan fouten gooien met specifieke error-codes. Log de fout en geef een nuttige foutmelding terug aan het model als context, zodat het kan herstellen of de gebruiker netjes kan informeren.
Kan ik meerdere MCP-servers tegelijk verbinden?
Ja, maak meerdere Client-instanties, een per server. Combineer de tools van alle servers in de tools-array die je aan het model doorgeeft.
Hoe weet het model welke server een tool aanbiedt?
Het model ziet alleen tool-namen en -beschrijvingen, niet de onderliggende server. Zorg voor unieke namen, of prefix tools met de server-naam, bijvoorbeeld drive_search of calendar_create.
Wat is het verschil tussen stdio en Streamable HTTP?
Stdio start een lokale server als subprocess en communiceert via stdin en stdout, ideaal voor lokale tooling. Streamable HTTP verbindt over het netwerk met een remote server via een enkel endpoint en is de aanbevolen keuze voor productie.
Moet ik nog SSEClientTransport gebruiken?
Voor nieuwe integraties niet. Streamable HTTP is sinds de spec van maart 2025 de standaard voor remote-servers. Houd SSE alleen aan als fallback voor servers die nog niet zijn gemigreerd.
Kan ik tool-aanroepen cachen?
Ja, voor read-only tools die bij dezelfde input altijd dezelfde output geven. Implementeer een cache-laag buiten de MCP-client, zodat je niet onnodig dezelfde server-call herhaalt.
Werkt dit ook met OpenAI-modellen?
Ja, converteer de MCP-tool-schemas naar het OpenAI functions-formaat. De tool-loop-logica blijft identiek, alleen de API-aanroepen verschillen.