# MCP-client verbinden [[TOC]] ## 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. :::info title="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 ```bash 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. ```typescript 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 ```typescript 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 ```typescript 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`: ```typescript 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, }); 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); ``` :::tip title="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. ```typescript 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. ```typescript import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; const transport = new SSEClientTransport( new URL("https://mijn-mcp-server.example.com/sse") ); ``` :::warn title="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. ```typescript const resourcesResponse = await client.listResources(); const resource = await client.readResource({ uri: "tickets://open", }); console.log(resource.contents[0].text); ``` ## Verbinding sluiten ```typescript 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. :::warn title="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. ::: :::faq ### 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. :::