# Les 16 — Lesstof ## MCP — Model Context Protocol **Vak:** AI-Assisted Development **Opleiding:** NOVI Hogeschool Utrecht **Vorige les:** Les 15 — Cursor + Vercel deploy **Volgende les:** Les 17 — Externe APIs in diepte --- ## Inhoud 1. [Wat is MCP en waarom](#1-wat-is-mcp-en-waarom) 2. [Architectuur — client / server / transport](#2-architectuur--client--server--transport) 3. [De drie primitieven](#3-de-drie-primitieven) 4. [Bestaande MCP servers](#4-bestaande-mcp-servers) 5. [Eigen server bouwen — TypeScript SDK](#5-eigen-server-bouwen--typescript-sdk) 6. [Laden in Cursor + Claude Desktop](#6-laden-in-cursor--claude-desktop) 7. [Resources + Prompts](#7-resources--prompts) 8. [MCP Inspector](#8-mcp-inspector) 9. [MCP vs Tool Calling](#9-mcp-vs-tool-calling) 10. [Productie + distributie](#10-productie--distributie) --- ## 1. Wat is MCP en waarom **Model Context Protocol** is een open standaard van Anthropic (gelanceerd november 2024) voor de communicatie tussen AI-assistenten en externe data/tools. ### Het probleem dat MCP oplost Stel je hebt een handige tool — bijvoorbeeld 'zoek bands in onze database'. Je wilt 'm gebruiken in: - Je eigen Next.js chat-app - Cursor (tijdens coden vragen stellen over je data) - Claude Desktop (research-werkflow) - ChatGPT plugins (in de toekomst) **Zonder MCP:** vier integraties, vier configuraties, vier onderhouds-aspecten. **Met MCP:** één server, alle clients begrijpen het protocol. > MCP is voor AI-tools wat USB-C is voor apparaten — één standaard connector. ### Waarom dit nu hot is - Anthropic Claude Desktop ondersteunt het out-of-the-box - Cursor ondersteunt MCP sinds early 2025 - OpenAI heeft mei 2025 MCP-support in ChatGPT aangekondigd - Honderden bedrijven publiceren MCP servers (GitHub, Stripe, Linear, Notion, ...) Voor jouw eindopdracht: optionele bonus, maar erg handig om interne tools te bouwen die je in elke AI-client kunt gebruiken. --- ## 2. Architectuur — client / server / transport ``` ┌──────────────┐ JSON-RPC ┌──────────────┐ │ MCP Client │ ◄────────────────────► │ MCP Server │ │ │ stdio / HTTP/SSE │ (jouw code) │ │ Cursor, │ │ │ │ Claude │ │ exposes: │ │ Desktop, │ │ - tools │ │ custom apps │ │ - resources │ │ │ │ - prompts │ └──────────────┘ └──────────────┘ ``` ### Drie rollen - **MCP Client** — de AI-assistent die jouw server gebruikt. Cursor, Claude Desktop, of een custom app die jij bouwt. - **MCP Server** — JOUW code. Een proces dat tools, data, of prompts beschikbaar maakt. - **Transport** — hoe ze communiceren. Twee opties: - **stdio** (default) — server runt lokaal als child-process, communicatie via stdin/stdout - **HTTP/SSE** — server runt remote, communicatie over HTTP met Server-Sent Events ### Protocol JSON-RPC 2.0 onder de motorkap. Je hoeft dat niet zelf te schrijven — de SDK regelt het. Wat je wel zou kunnen zien als je inspecteert: ```json { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "searchBands", "arguments": { "day": "Vrijdag" } }, "id": 1 } ``` --- ## 3. De drie primitieven Een MCP server kan drie soorten dingen exposeren. ### 3.1 Tools Uitvoerbare functies. Vergelijkbaar met tool calling uit Les 12. ```typescript server.tool( "searchBands", // naam "Zoek bands op dag, stage of genre", // beschrijving { // input schema (Zod) day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(), stage: z.string().optional(), }, async ({ day, stage }) => { // execute const bands = await query(day, stage); return { content: [{ type: "text", text: JSON.stringify(bands) }], }; } ); ``` AI kan deze aanroepen tijdens een conversatie. ### 3.2 Resources Read-only data die AI mag inladen als context. ```typescript server.resource( "bands-list", // naam "bands://list", // URI scheme async () => ({ contents: [{ uri: "bands://list", mimeType: "application/json", text: JSON.stringify(allBands), }], }) ); ``` In Cursor type je `@polderfest:bands-list` om resource toe te voegen aan context. ### 3.3 Prompts Herbruikbare prompt-templates. ```typescript server.prompt( "daily-recap", "Genereer een samenvatting van een festivaldag", { day: z.string() }, ({ day }) => ({ messages: [{ role: "user", content: { type: "text", text: `Geef een gedetailleerde recap van ${day}: - Top 3 acts - Drukke / rustige momenten - Bijzonderheden`, }, }], }) ); ``` In Cursor commando palette of Claude Desktop slash-menu krijg je deze prompts. --- ## 4. Bestaande MCP servers ### Officieel (Anthropic) Geïnstalleerd via npm: - `@modelcontextprotocol/server-filesystem` — file system access - `@modelcontextprotocol/server-github` — repos, issues, PRs - `@modelcontextprotocol/server-slack` — Slack workspace - `@modelcontextprotocol/server-postgres` — Postgres queries - `@modelcontextprotocol/server-puppeteer` — browser automation - `@modelcontextprotocol/server-memory` — persistent memory ### Community - `linear-mcp`, `notion-mcp`, `asana-mcp`, `figma-mcp`, `stripe-mcp` - `youtube-mcp`, `arxiv-mcp`, `wikipedia-mcp` - Lijst: https://github.com/punkpeye/awesome-mcp-servers (~500+ servers) ### Gebruik Voorbeeld Cursor config (`~/.cursor/mcp.json`): ```json { "mcpServers": { "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." } }, "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/tim/Documents"] } } } ``` Restart Cursor → servers actief. --- ## 5. Eigen server bouwen — TypeScript SDK ### Setup ```bash mkdir mcp-polderfest && cd mcp-polderfest pnpm init pnpm add @modelcontextprotocol/sdk zod pnpm add -D typescript @types/node tsx ``` `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "strict": true, "esModuleInterop": true }, "include": ["src/**/*"] } ``` ### Minimal server `src/index.ts`: ```typescript #!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const server = new McpServer({ name: "polderfest-server", version: "1.0.0", }); server.tool( "searchBands", "Zoek bands op dag, stage, genre", { day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(), stage: z.string().optional(), }, async ({ day, stage }) => { // Echte DB query of mock const bands = MOCK_BANDS.filter(b => (!day || b.day === day) && (!stage || b.stage === stage) ); return { content: [{ type: "text", text: JSON.stringify(bands, null, 2) }], }; } ); const transport = new StdioServerTransport(); await server.connect(transport); ``` ### Build + run ```bash pnpm tsc # build → dist/index.js node dist/index.js # runt server (wacht op stdio input) ``` Eerste keer: niets gebeurt zichtbaar — server wacht op JSON-RPC over stdio. Test via Inspector (zie sectie 8). ### Best practices - **Logging via `console.error`** — `stdout` is gereserveerd voor protocol - **Errors als data** — return `{ content: [...], isError: true }` - **Schema's zo strict mogelijk** — Zod schema = AI weet exact wat het mag invullen - **Description = belangrijk** — AI kiest tools op basis van description --- ## 6. Laden in Cursor + Claude Desktop ### Cursor `~/.cursor/mcp.json`: ```json { "mcpServers": { "polderfest": { "command": "node", "args": ["/Users/jouw-naam/dev/mcp-polderfest/dist/index.js"], "env": { "SUPABASE_URL": "https://...supabase.co", "SUPABASE_KEY": "..." } } } } ``` Belangrijk: gebruik **absolute paths** (geen `~` of relative). Cursor → Settings → MCP → check server-status (groen = werkt). ### Claude Desktop `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS): ```json { "mcpServers": { "polderfest": { "command": "node", "args": ["/Users/jouw-naam/dev/mcp-polderfest/dist/index.js"] } } } ``` Op Windows: `%APPDATA%\Claude\claude_desktop_config.json`. Restart Claude Desktop → MCP icon zichtbaar in chat input. ### Beide tegelijk Zelfde server, beide configs. Eén keer onderhouden, beide AI-clients gebruiken hem. --- ## 7. Resources + Prompts ### Resources gebruik-cases - **Documentatie** — handleiding, RFCs, design docs - **Database snapshots** — read-only views - **API responses** — gecachede externe data - **Logs** — recent errors voor debugging ```typescript server.resource( "recent-logs", "logs://recent", async () => ({ contents: [{ uri: "logs://recent", mimeType: "text/plain", text: await fs.readFile("/var/log/app.log", "utf-8"), }], }) ); ``` In Cursor: `@polderfest:recent-logs` voegt logs toe aan chat context. AI kan vragen erover beantwoorden. ### Prompts gebruik-cases - **Code review template** - **Bug report opmaak** - **Vergader-recap structuur** ```typescript server.prompt( "code-review", "Doe een grondige code review", { file: z.string() }, ({ file }) => ({ messages: [{ role: "user", content: { type: "text", text: `Review ${file} op: 1. Bugs / edge cases 2. Naming / readability 3. Performance 4. Security Geef per punt concrete suggesties.` }, }], }) ); ``` Cursor command palette → "MCP: code-review" → file selecteren → AI start met die exacte prompt. --- ## 8. MCP Inspector Debug-tool om je server te testen zonder Cursor/Claude. ```bash npx @modelcontextprotocol/inspector dist/index.js ``` Opent browser-UI op `http://localhost:5173`: - Linker panel: server tools/resources/prompts lijst - Hoofdvenster: parameters invullen + call uitvoeren - Result + raw JSON-RPC zichtbaar **Workflow:** 1. Schrijf nieuwe tool 2. Build (`pnpm tsc`) 3. Test in Inspector 4. Werkt? Restart Cursor en gebruik daar 5. Werkt niet? Inspector toont errors direct --- ## 9. MCP vs Tool Calling | Aspect | Tool Calling (Les 12) | MCP (Les 16) | |--------|----------------------|--------------| | Waar leeft tool | In jouw Next.js app | Aparte server, los proces | | Clients | Alleen jouw chat | Alle MCP-clients | | Setup | Function definitie in code | Server proces + config | | Distributie | Niet — alleen in app | npm publish | | State | Per-request, geen state | Server proces heeft state | | Best voor | App-specifieke features | Herbruikbare tools | ### Mentale model - **Tool calling** = function in mijn app - **MCP server** = library die elke AI-client kan importeren ### Wanneer welke - App-feature (alleen voor jouw users) → tool calling - Interne dev-tools (jij + collega's) → MCP server - Open-source tool voor de community → MCP server (publish naar npm) - Beide patronen zijn complementair — productie-teams gebruiken vaak beide --- ## 10. Productie + distributie ### npm publish Maak je server beschikbaar voor anderen: ```json // package.json { "name": "@jouw-org/mcp-polderfest", "version": "1.0.0", "bin": { "mcp-polderfest": "./dist/index.js" }, "files": ["dist"] } ``` ```bash pnpm publish --access public ``` Anderen installeren: ```json { "mcpServers": { "polderfest": { "command": "npx", "args": ["-y", "@jouw-org/mcp-polderfest"] } } } ``` ### Remote servers (HTTP/SSE) Voor cloud deploys — anders dan stdio: ```typescript import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import express from "express"; const app = express(); app.get("/sse", (req, res) => { const transport = new SSEServerTransport("/messages", res); server.connect(transport); }); app.listen(3001); ``` Cursor config: ```json { "mcpServers": { "polderfest-remote": { "url": "https://mcp.example.com/sse" } } } ``` ### Security - **Authenticate** — als server gevoelige data exposeert, auth-token in env-var - **Validate input** — Zod schema's strict - **Audit log** — log elke tool-call - **Rate limit** — server is gewoon een proces, gebruik standaard rate-limit libs - **Geen secrets in code** — env vars only --- ## Bronnen - **MCP officiële site:** https://modelcontextprotocol.io/ - **Spec:** https://spec.modelcontextprotocol.io/ - **TS SDK:** https://github.com/modelcontextprotocol/typescript-sdk - **MCP Inspector:** https://github.com/modelcontextprotocol/inspector - **Cursor MCP docs:** https://docs.cursor.com/context/model-context-protocol - **Claude Desktop MCP:** https://docs.anthropic.com/en/docs/build-with-claude/mcp - **Anthropic launch post:** https://www.anthropic.com/news/model-context-protocol - **Awesome MCP:** https://github.com/punkpeye/awesome-mcp-servers