# 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
