4.5 KiB
Les 16 — Lesopdracht
Eerste MCP server bouwen + laden in Cursor
Vak: AI-Assisted Development Duur: 30 min in-class
Doel
Werkende MCP server met 1 tool, geladen in Cursor of Claude Desktop, en getest met een echte chat-vraag.
Stap 1 — Setup
mkdir mcp-demo && cd mcp-demo
pnpm init
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D typescript @types/node tsx
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
package.json — voeg "type": "module" toe.
Stap 2 — Minimal server
src/index.ts:
#!/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: "demo-server",
version: "1.0.0",
});
// Mock data
const BANDS = [
{ name: "Lost Tigers", day: "Vrijdag", stage: "Main Stage", genre: "rock" },
{ name: "Polderfunk", day: "Zaterdag", stage: "Main Stage", genre: "funk" },
{ name: "Jazz Collective", day: "Zaterdag", stage: "Tent", genre: "jazz" },
{ name: "Echo Bay", day: "Zondag", stage: "Beach Stage", genre: "indie" },
// ... voeg er een paar toe
];
server.tool(
"searchBands",
"Zoek bands op dag, stage of genre",
{
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
stage: z.string().optional(),
genre: z.string().optional(),
},
async ({ day, stage, genre }) => {
const results = BANDS.filter(b =>
(!day || b.day === day) &&
(!stage || b.stage === stage) &&
(!genre || b.genre === genre)
);
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server started");
Stap 3 — Build
pnpm tsc
# resultaat: dist/index.js
Stap 4 — Test met Inspector
npx @modelcontextprotocol/inspector node dist/index.js
Browser opent automatisch op http://localhost:5173:
- Klik "Tools" tab
- Zie
searchBandsin de lijst - Klik "Call Tool"
- Vul
day: Zaterdagin - Verwacht: JSON met 2 bands
Werkt? Door naar Cursor.
Stap 5 — Laden in Cursor
Open ~/.cursor/mcp.json (maak aan als 'ie nog niet bestaat):
{
"mcpServers": {
"demo": {
"command": "node",
"args": ["/FULL/PATH/TO/mcp-demo/dist/index.js"]
}
}
}
Belangrijk:
- Vervang
/FULL/PATH/TO/met je echte absolute path (pwdin terminal) - Geen
~, geen relative paths
Restart Cursor (Cmd+Q + opnieuw openen).
Check: Cursor Settings → MCP → server "demo" zichtbaar met groen vinkje.
Stap 6 — Test in Cursor chat
In Cursor chat (Cmd+L of Cmd+I):
"Welke bands spelen op zaterdag?"
Cursor roept searchBands aan en toont resultaat. Werkt? Klaar.
Alternatief — Claude Desktop
Als Cursor niet wil:
~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"demo": {
"command": "node",
"args": ["/FULL/PATH/TO/mcp-demo/dist/index.js"]
}
}
}
Restart Claude Desktop. Chat input toont MCP-icon. Vraag stellen werkt identiek.
Eisen
- Project opgezet (package.json, tsconfig, ts source)
- Build succesvol (
dist/index.jsbestaat) - MCP Inspector toont tool + tool werkt
- Geladen in Cursor of Claude Desktop
- Chat-vraag werkt — AI roept tool aan, geeft antwoord
Veelvoorkomende problemen
| Symptoom | Oplossing |
|---|---|
cannot find module |
Check "type": "module" in package.json + .js extensies in imports |
| Cursor toont server niet | Restart Cursor volledig (Cmd+Q). Check absolute path. Check JSON valid. |
| Tool niet aanroepbaar | Check tool name + parameters in Inspector eerst |
| Stdout pollution | console.log → console.error (stdout = protocol) |
tsc errors |
Check tsconfig en moduleResolution |
Klaar?
Verder met huiswerk (Les16-Huiswerk.md):
- 3 tools + 1 write-tool naar Supabase
- 1 resource toevoegen
MCP.mdmet tool-lijst + voorbeeldprompts
Tijdsindeling (30 min)
| Stap | Tijd |
|---|---|
| 1-3 — Setup + minimal + build | 10 min |
| 4 — Inspector test | 5 min |
| 5 — Cursor config | 5 min |
| 6 — Chat test | 5 min |
| Buffer | 5 min |