# Les 16 — Huiswerk
## MCP server uitbreiden + Supabase + MCP.md

**Vak:** AI-Assisted Development
**Deadline:** Voor Les 17 — Externe APIs in diepte
**Inleveren:** GitHub repo + `MCP.md` in root

---

## Doel

Bouw je MCP server uit naar productie-niveau: meerdere tools incl. een write-tool, een resource, geladen in zowel Cursor als Claude Desktop, en gedocumenteerd.

---

## Onderdeel A — Tools naar Supabase (verplicht)

Vervang de mock-data uit de lesopdracht door echte Supabase queries.

### A1 — Supabase client in MCP server

`src/lib/supabase.ts`:
```typescript
import { createClient } from "@supabase/supabase-js";

export const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!  // anon key voor read, service voor write
);
```

`package.json` install: `pnpm add @supabase/supabase-js`

### A2 — Drie read-tools

- **`searchBands`** — al gedaan, maak hem nu echt (query Supabase)
- **`getBandStats`** — counts per dag of per genre
- **`getBandByName`** — exact lookup

```typescript
server.tool(
  "getBandStats",
  "Aantal bands per groep (dag, stage, genre)",
  { groupBy: z.enum(["day", "stage", "genre"]) },
  async ({ groupBy }) => {
    const { data, error } = await supabase
      .from("bands")
      .select(groupBy)
      .order(groupBy);
    if (error) {
      return {
        content: [{ type: "text", text: error.message }],
        isError: true,
      };
    }
    const counts = data!.reduce((acc, row) => {
      acc[row[groupBy]] = (acc[row[groupBy]] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
    return { content: [{ type: "text", text: JSON.stringify(counts, null, 2) }] };
  }
);
```

### A3 — Eén write-tool

`addFavorite` — schrijft naar `user_favorites` tabel (uit Les 12 huiswerk).

```typescript
server.tool(
  "addFavorite",
  "Voeg band toe aan favorieten van gebruiker. Alleen bij expliciete user-intent.",
  {
    userEmail: z.string().email(),
    bandName: z.string(),
  },
  async ({ userEmail, bandName }) => {
    const { data: band } = await supabase.from("bands").select("id").ilike("name", bandName).single();
    if (!band) return { content: [{ type: "text", text: `Band '${bandName}' niet gevonden` }], isError: true };
    const { error } = await supabase.from("user_favorites").insert({ user_email: userEmail, band_id: band.id });
    if (error) return { content: [{ type: "text", text: error.message }], isError: true };
    return { content: [{ type: "text", text: `Toegevoegd: ${bandName}` }] };
  }
);
```

### Eisen

- [ ] 3 read-tools werken tegen Supabase
- [ ] 1 write-tool werkt en heeft 'alleen bij user-intent' in description
- [ ] env vars worden gelezen vanuit MCP config (zie Cursor config voor `env`)
- [ ] Alle tools getest via MCP Inspector

---

## Onderdeel B — Resource toevoegen (verplicht)

Voeg minimaal 1 resource toe — read-only data die AI mag inladen als context.

Ideeën:
- `bands-list` — volledige bandenlijst (text/plain of JSON)
- `today-schedule` — vandaag's optredens
- `stages-info` — info over de stages

```typescript
server.resource(
  "bands-list",
  "bands://list",
  async () => {
    const { data } = await supabase.from("bands").select("*");
    return {
      contents: [{
        uri: "bands://list",
        mimeType: "application/json",
        text: JSON.stringify(data, null, 2),
      }],
    };
  }
);
```

In Cursor: `@your-server:bands-list` om resource in chat te laden.

### Eisen

- [ ] 1 resource gedefinieerd
- [ ] Test via Inspector: resource lijst en preview werken
- [ ] Test in Cursor: `@server:resource-name` werkt

---

## Onderdeel C — Laden in twee clients (verplicht)

Server moet werken in **zowel Cursor als Claude Desktop**.

### Cursor (`~/.cursor/mcp.json`):
```json
{
  "mcpServers": {
    "polderfest": {
      "command": "node",
      "args": ["/absolute/path/dist/index.js"],
      "env": {
        "SUPABASE_URL": "https://...supabase.co",
        "SUPABASE_KEY": "eyJ..."
      }
    }
  }
}
```

### Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json` op macOS):
Identieke JSON structuur, ander bestand.

### Eisen

- [ ] Cursor toont server met groen vinkje
- [ ] Claude Desktop toont MCP-icon, server beschikbaar
- [ ] Test-prompt werkt in beide

---

## Onderdeel D — `MCP.md` (verplicht)

Schrijf in repo-root.

### Sectie 1 — Tools

Tabel met je tools:

| Tool | Description | Type |
|------|-------------|------|
| searchBands | Filter op dag/stage/genre | Read |
| getBandStats | Counts per groep | Read |
| getBandByName | Exact lookup | Read |
| addFavorite | Toevoegen aan favorites | **Write** |

### Sectie 2 — Resources

| Resource | URI | Inhoud |
|----------|-----|--------|
| bands-list | bands://list | Volledige bandenlijst (JSON) |

### Sectie 3 — Voorbeeld-prompts

5 prompts die je tools triggeren — getest in Cursor of Claude Desktop:

```markdown
**Prompt 1:** "Welke jazz bands spelen op zaterdag?"
→ AI roept searchBands({ day: "Zaterdag", genre: "jazz" })
→ Antwoord: ...

**Prompt 2:** "Hoeveel bands per stage?"
→ AI roept getBandStats({ groupBy: "stage" })
→ Antwoord: ...
```

### Sectie 4 — Setup voor anderen

Hoe iemand anders jouw server kan installen:

```markdown
1. Clone repo
2. `pnpm install && pnpm tsc`
3. Maak `.env.local` met SUPABASE_URL en SUPABASE_KEY
4. Voeg toe aan ~/.cursor/mcp.json:
   {...config...}
5. Restart Cursor
```

### Sectie 5 — Eén observatie

Iets dat opviel:
- Welke tool koos AI vaker dan verwacht?
- Was de description duidelijk genoeg?
- Verschil tussen Cursor en Claude Desktop in gedrag?
- Eén keer dat AI verkeerde tool koos — waarom?

### Vorm

- Max 600 woorden
- Concrete voorbeelden + tool-calls
- Mag wat informeel

---

## Bonus (optioneel)

### Bonus 1 — Eigen prompt-template

Voeg een `server.prompt()` toe — bv. "daily-recap" of "compare-stages". Documenteer.

### Bonus 2 — Publish naar npm

Maak je package npm-installeerbaar (`bin` in package.json, `pnpm publish`). Anderen kunnen je server installen met `npx`.

### Bonus 3 — HTTP/SSE variant

Naast stdio: maak ook een HTTP/SSE versie voor remote loading. Deploy bv. naar Vercel (zoals Les 15).

### Bonus 4 — Andere data-bron

In plaats van Polderfest: maak een MCP server voor jouw eindopdracht-data. Eigen API, eigen DB. Direct nuttig.

---

## Inleveren

1. **GitHub repo URL** in Brightspace
2. **`MCP.md`** in repo-root
3. **Screenshots:** server werkend in Cursor + Claude Desktop (in MCP.md)
4. **Een korte demo-video (optioneel)** van een chat-vraag

---

## Beoordeling

| Criterium | Punten |
|-----------|--------|
| A — 3 read-tools + 1 write-tool werkend | 3 |
| B — 1 resource werkend | 1 |
| C — Geladen in 2 clients | 2 |
| D — MCP.md compleet (5 secties) | 3 |
| Server runt zonder errors | 1 |
| **Totaal** | **10** |

Voldoende = 6+. Bonus telt mee.

---

## Tijd-indicatie

| Onderdeel | Tijd |
|-----------|------|
| A — Supabase tools (3 read + 1 write) | 45 min |
| B — Resource | 15 min |
| C — Beide clients laden + testen | 20 min |
| D — MCP.md | 30 min |
| **Totaal** | **~2 uur** |

---

## Veelvoorkomende valkuilen

| Probleem | Oplossing |
|----------|-----------|
| Env vars niet gelezen | Check `env` block in Cursor mcp.json |
| Server crash on Supabase call | Check service key (write needs more permissions than anon) |
| AI roept verkeerde tool aan | Description specifieker — wat doet het, wanneer gebruiken |
| Inspector kan server niet vinden | Build eerst (`pnpm tsc`) en check `dist/index.js` bestaat |
| Resource niet zichtbaar in Cursor | Cursor doet niet alles met resources — check Claude Desktop ook |

---

## Tips

- **Test in Inspector eerst** — sneller dan via Cursor heen-en-weer
- **Descriptions zijn cruciaal** — AI kiest op basis hiervan
- **isError: true** bij failures — AI weet dan dat het verkeerd ging
- **Server is gewoon Node** — log via `console.error` voor debug
- **Eindopdracht-tip:** als je MCP server voor je eindopdracht-data maakt, kun je 'm in Cursor laden en tijdens dev al data-vragen stellen
