fix: les
This commit is contained in:
228
Les15-MCP/Les15-Docenttekst-Autocue.md
Normal file
228
Les15-MCP/Les15-Docenttekst-Autocue.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Les 15 — Docenttekst (Autocue)
|
||||
|
||||
**Fysieke les — letterlijk voorleesbaar. Slide-nummers tussen `[SLIDE N]`.**
|
||||
|
||||
> **Structuur:** ±1 uur theorie + live demos → kwartier pauze → ±1,5 uur hands-on
|
||||
>
|
||||
> **Voorbereiding — accounts + tokens:**
|
||||
> - GitHub Personal Access Token (fine-grained, test-repo permissies)
|
||||
> - Supabase Personal Access Token (`sbp_...`) + je `project_ref`
|
||||
> - Vercel account (geen token vooraf — OAuth bij eerste gebruik)
|
||||
> - Cursor + Pro Student ingelogd
|
||||
>
|
||||
> **Voorbereiding — `~/.cursor/mcp.json`:**
|
||||
> - Alle 4 servers vooraf geconfigureerd
|
||||
> - Cursor herstart — 4 indicators groen (github + supabase + vercel + polderfest)
|
||||
> - Voor Vercel: OAuth-flow vooraf doorlopen
|
||||
>
|
||||
> **Voorbereiding — code:**
|
||||
> - `polderfest-mcp-finished` uitgepakt + `npm install` gedaan
|
||||
> - `.env.local` werkt — `npm run dev` getest
|
||||
> - Eén commit gedaan in `git init` (klaar voor Chat 1)
|
||||
> - Backup productie-URL op Vercel als demo-fallback
|
||||
>
|
||||
> **Voor uitgebreide handleiding zie `Les15-Docenttekst.md` (normale versie).**
|
||||
|
||||
---
|
||||
|
||||
## BLOK 1 — Welkom + Vandaag
|
||||
|
||||
`[SLIDE 1]`
|
||||
|
||||
Welkom bij Les 15. Vandaag hebben we het over MCP — Model Context Protocol. Dit is de open standaard die in een jaar tijd is opgenomen door Cursor, Claude Desktop, OpenAI en veel meer. En vandaag heb jij hem ook.
|
||||
|
||||
`[SLIDE 2]`
|
||||
|
||||
Vandaag doen we drie blokken. Eerst kort de theorie — wat is MCP, waar komt het vandaan. Dan koppelen we live vier MCP servers — GitHub, Supabase, Vercel, en daarna onze eigen Polderfest. En in het laatste blok komt alles samen in een live demo van drie chats, waarmee we van leeg-project naar volledig gedeployd gaan zonder zelf één terminal-commando te typen.
|
||||
|
||||
Chat één maakt een GitHub repo aan via de GitHub MCP. Chat twee bouwt een nieuwe feature aan onze Polderfest server via de Supabase MCP. En chat drie maakt een Vercel project aan en deploy't dat naar productie.
|
||||
|
||||
## BLOK 2 — Wat is MCP
|
||||
|
||||
`[SLIDE 3]`
|
||||
|
||||
Wat is MCP. Het is een open standaard die beschrijft hoe AI-clients verbinding maken met externe tools en data. Drie dingen worden vastgelegd: hoe ze contact maken, hoe servers beschrijven wat ze aanbieden, en hoe data heen-en-weer stroomt.
|
||||
|
||||
Anthropic heeft het eind 2024 geïntroduceerd. Open spec. En in een jaar tijd hebben Cursor, Claude Desktop, OpenAI, Windsurf en Zed het allemaal omarmd. Geen vendor lock-in.
|
||||
|
||||
De analogie die ik je wil laten onthouden: MCP is USB-C voor AI. Eén stekker, elke laptop.
|
||||
|
||||
`[SLIDE 4]`
|
||||
|
||||
Waarom is dit zo'n big deal. Stel je voor: jij hebt een handige tool gebouwd. Je wilt hem in Cursor gebruiken — Cursor heeft zijn eigen extension API. Je wilt hem in Claude Desktop — dat is een plugin systeem. Voor ChatGPT moet je een GPT maken met Custom Actions. Voor Windsurf weer iets anders.
|
||||
|
||||
Eén feature, vijf integraties. Allemaal anders.
|
||||
|
||||
Met MCP bouw je het één keer. En het werkt overal waar MCP gesproken wordt. Vijf integraties worden er één.
|
||||
|
||||
`[SLIDE 5]`
|
||||
|
||||
De architectuur is klein. Drie rollen om te onthouden.
|
||||
|
||||
De host is de app waarin je werkt — Cursor of Claude Desktop. De client is de MCP library in die host — daar hoef je niets aan te doen. En de server is wat WIJ bouwen of wat we koppelen.
|
||||
|
||||
Een server kan drie dingen aanbieden. Tools — functies die de AI mag aanroepen. Resources — read-only data. En prompts — voorgedefinieerde templates.
|
||||
|
||||
Vandaag focussen we op tools.
|
||||
|
||||
`[SLIDE 6]`
|
||||
|
||||
Het ecosysteem is enorm. Honderden MCP servers beschikbaar. GitHub voor issues en code. Supabase voor database. Vercel voor deployment. Filesystem, Slack, Linear, Notion, Postgres — noem maar op.
|
||||
|
||||
Vandaag koppelen we er vier in Cursor: GitHub, Supabase, Vercel — en onze eigen Polderfest. Met die vier kunnen we van leeg project tot live productie-URL.
|
||||
|
||||
## BLOK 3 — Vier MCPs koppelen
|
||||
|
||||
`[SLIDE 7]`
|
||||
|
||||
Hier zie je de vier MCPs op een rij. Allemaal via HTTP, want dat is de moderne manier in Cursor 3. Geen losse processen meer, gewoon URLs.
|
||||
|
||||
GitHub gebruikt een Bearer token — dat is je Personal Access Token. Supabase ook een Bearer — je sbp-token. Vercel doet het anders en eleganter: OAuth via de browser, geen token nodig vooraf. En Polderfest is onze eigen lokale server zonder auth.
|
||||
|
||||
Eén patroon, vier servers. Allemaal in dezelfde mcp.json.
|
||||
|
||||
`[SLIDE 8]`
|
||||
|
||||
Hier zie je de complete mcp.json. Dit bestand staat in mijn home folder onder dot cursor slash mcp.json, dus globaal — werkt vanuit elke folder die ik open.
|
||||
|
||||
Vier entries onder mcpServers. Supabase met de mcp dot supabase punt com URL, een project ref als query parameter — read only true om veiligheid in te bouwen — en een Bearer header met mijn sbp token.
|
||||
|
||||
GitHub met api dot githubcopilot punt com slash mcp, Bearer met mijn ghp token.
|
||||
|
||||
Vercel — heel simpel — alleen type http en de URL mcp dot vercel punt com. Geen header. Cursor doet de OAuth bij eerste gebruik.
|
||||
|
||||
En Polderfest, lokaal op localhost drie duizend slash api slash mcp.
|
||||
|
||||
`[SLIDE 9]`
|
||||
|
||||
Even over de tokens. Drie tokens, één OAuth.
|
||||
|
||||
GitHub. Fine-grained PAT, niet classic. Op github.com onder Settings, Developer settings, Personal access tokens, Fine-grained. Permissies die je nodig hebt: Contents, Issues en Pull requests, allemaal read plus write. Token begint met ghp underscore.
|
||||
|
||||
Supabase. Op supabase punt com onder Account, Access Tokens, Generate new token. Belangrijk om te onthouden — dit is een account-level token, NIET de project anon key en NIET de service role key. Hij heeft toegang tot al je projecten. Begint met sbp underscore. De project ref vind je in je Supabase project URL.
|
||||
|
||||
Vercel. Geen token vooraf. Bij eerste gebruik in Cursor verschijnt er "needs login". Klik erop, OAuth-flow in de browser, autoriseer Cursor. Klaar.
|
||||
|
||||
`[SLIDE 10]`
|
||||
|
||||
Live koppelen. Vier stappen.
|
||||
|
||||
Eerst maak ik de dot cursor folder in mijn home directory en open mcp.json. Twee, ik plak de complete JSON van slide acht en vul mijn tokens in. Drie, ik herstart Cursor volledig met Cmd-Q en open hem opnieuw — een reload pakt mcp.json niet opnieuw in. En vier, ik open een chat en check onderaan de indicator. Vier groene servers moet ik zien.
|
||||
|
||||
Voor Vercel klik ik nog op "needs login" en doorloop de OAuth-flow.
|
||||
|
||||
*(Ik laat dit live zien — open mcp.json, herstart Cursor, MCP-indicator gaat groen voor alle vier.)*
|
||||
|
||||
## BLOK 4 — Polderfest MCP
|
||||
|
||||
`[SLIDE 11]`
|
||||
|
||||
Tot zover bestaande servers. Nu de eigen kant.
|
||||
|
||||
Vandaag laat ik jullie kennismaken met onze Polderfest MCP. Zit in de finished zip die jullie straks krijgen.
|
||||
|
||||
Een complete Next.js app. Het meeste is gewoon Next.js zoals jullie het kennen. Het echte werk gebeurt in één bestand: app slash api slash mcp slash route.ts. Daarin staat de hele MCP server.
|
||||
|
||||
Vier tools zitten erin. searchBands om bands op te zoeken. getStageSchedule voor het schema van één dag. festivalStats voor totalen en verdelingen. En getWeather voor het weer via Open-Meteo.
|
||||
|
||||
`[SLIDE 12]`
|
||||
|
||||
Even een korte code-tour. Hoe ziet één tool eruit.
|
||||
|
||||
Het patroon is heel compact. Je roept server.tool aan met vier argumenten. Een naam — searchBands. Een beschrijving — let op, die leest de AI om te beslissen wanneer hij de tool gebruikt. Slechte beschrijving wordt niet aangeroepen. Het input-schema in Zod — die kennen jullie uit Les 12 en 13. En een async functie die de logica doet.
|
||||
|
||||
Wat nieuw is ten opzichte van Les 12 en 13: het return-formaat. Geen plat object meer, maar een object met een content-array. Elk item heeft een type en een tekst. Dat is MCP-specifiek.
|
||||
|
||||
*(Ik open even de finished route.ts en wijs de structuur aan.)*
|
||||
|
||||
`[SLIDE 13]`
|
||||
|
||||
Lokaal draaien is drie commando's plus de env-vars.
|
||||
|
||||
Uitpakken, cd erin, npm install. Copy env.example naar env.local en vul de Supabase-keys in — die hebben jullie nog uit Les 11 en 12. Daarna npm run dev.
|
||||
|
||||
Op localhost drie duizend zie je een landingspagina. Het MCP-endpoint zit op slash api slash mcp. En polderfest stond al in mijn mcp.json, dus na herstart van Cursor zie ik vier groene indicators in plaats van drie.
|
||||
|
||||
*(Ik draai dit live. Open localhost. Test in Cursor met "Welke jazz-bands spelen op zondag" — Polderfest MCP roept searchBands aan.)*
|
||||
|
||||
## BLOK 5 — LIVE: De drie chats workflow
|
||||
|
||||
`[SLIDE 14]`
|
||||
|
||||
Nu komt waar we naartoe hebben gewerkt. De climax van de demo.
|
||||
|
||||
Ik open drie aparte chats in Cursor. Elke chat heeft één doel. Chat één gebruikt GitHub MCP om een repo aan te maken en als remote te koppelen. Chat twee gebruikt Polderfest MCP plus Supabase MCP om een nieuwe feature te bouwen. Chat drie gebruikt Vercel MCP om een project aan te maken en te deployen.
|
||||
|
||||
Aan het eind: complete deploy van een nieuwe feature, vanaf mijn editor, zonder dat ik een terminal-commando of een browser hoef te openen voor GitHub of Vercel.
|
||||
|
||||
`[SLIDE 15]`
|
||||
|
||||
Chat één. GitHub MCP.
|
||||
|
||||
Ik open een nieuwe Cursor chat — Cmd-L, vers begin. En ik typ:
|
||||
|
||||
"Maak een nieuwe public GitHub repo polderfest-mcp aan via GitHub MCP. Voeg toe als remote origin aan deze folder. Push de main branch."
|
||||
|
||||
Wat ik vervolgens zie. GitHub MCP roept create-onderstreep-repository aan. Ik krijg de URL terug. Cursor draait vervolgens git remote add origin met die URL in mijn terminal. En git push min-u origin main.
|
||||
|
||||
Ik switch naar github punt com om te bevestigen — de repo staat er, met onze code.
|
||||
|
||||
Geen browser. Geen gh repo create zelf typen. Cursor heeft het via MCP gedaan.
|
||||
|
||||
`[SLIDE 16]`
|
||||
|
||||
Chat twee. Polderfest features.
|
||||
|
||||
Nieuwe Cursor chat — Cmd-L, opnieuw vers. Ik typ de hele taak:
|
||||
|
||||
"Ik wil een review-systeem voor de Polderfest MCP. Eerste stap: vraag Supabase MCP om een tabel band-onderstreep-reviews te maken — id, band-id als foreign key, score één tot tien, comment, en created-at. Tweede stap: voeg twee tools toe aan at-file app slash api slash mcp slash route.ts — addReview met bandName score en comment, plus getReviews met bandName. Gebruik dezelfde patroon als bestaande tools."
|
||||
|
||||
Wat er gebeurt: Supabase MCP genereert de migration en draait hem, vraagt om bevestiging, ik zeg ja, tabel staat. Daarna gaat Cursor zelf de tool-code genereren in route.ts. Ik accept all.
|
||||
|
||||
Test in dezelfde chat — twee vragen die nu via Polderfest MCP gaan: "Geef Maud and The Cathedrals een review van 9, episch." Daarna: "Toon alle reviews voor Maud and The Cathedrals."
|
||||
|
||||
Drie MCPs werken samen in één chat: Supabase MCP voor de tabel, Cursor zelf voor de code, Polderfest MCP voor het testen.
|
||||
|
||||
`[SLIDE 17]`
|
||||
|
||||
Chat drie. Vercel MCP.
|
||||
|
||||
Nieuwe Cursor chat. Ik typ:
|
||||
|
||||
"Maak via Vercel MCP een nieuw Vercel-project voor mijn GitHub repo polderfest-mcp. Voeg toe als env vars in productie: SUPABASE underscore URL en SUPABASE underscore SERVICE underscore ROLE underscore KEY — ik plak ze hier. Daarna deploy naar productie en geef me de URL terug."
|
||||
|
||||
Vercel MCP gaat aan de slag. Project wordt aangemaakt, gekoppeld aan de GitHub repo. Env vars worden gezet. Deploy wordt getriggerd — ongeveer 45 seconden. Ik krijg de productie-URL terug.
|
||||
|
||||
Laatste actie van vandaag: ik vervang in mijn mcp.json het polderfest entry — de localhost URL wordt nu de Vercel productie-URL. Herstart Cursor. Vraag in chat: "Welke indie-bands spelen er op zaterdag?" Polderfest werkt, maar nu via productie, niet meer via mijn lokale dev-server.
|
||||
|
||||
Dat is wat we vandaag voor elkaar hebben gekregen.
|
||||
|
||||
## BLOK 6 — Lesopdracht + Extra werk
|
||||
|
||||
`[SLIDE 18]`
|
||||
|
||||
Tot zover de demo. Na de pauze gaan jullie hetzelfde doen.
|
||||
|
||||
Acht stappen. Pak de finished zip uit. npm install plus env vars. npm run dev. Voeg vier MCPs toe aan mcp.json — de template staat in de lesbestanden, met placeholders waar jullie tokens moeten. Herstart Cursor — vier indicators groen.
|
||||
|
||||
Dan de drie chats. Chat één: GitHub MCP voor de repo. Chat twee: test Polderfest MCP met een paar vragen. Chat drie: Vercel MCP voor het project en de deploy. Eindig met polderfest in mcp.json wijzen naar de productie-URL.
|
||||
|
||||
Geen deliverables. Anderhalf uur. Ik loop rond.
|
||||
|
||||
`[SLIDE 19]`
|
||||
|
||||
Voor wie sneller is — niet verplicht. Doe wat ik in chat twee deed, voor je eigen feature.
|
||||
|
||||
Het patroon is hetzelfde. Vraag Supabase MCP om een nieuwe tabel met seed-data. Vraag Cursor om de bijbehorende tools in route.ts. Push naar GitHub — Vercel deploy't automatisch want het project is al gekoppeld. Test in chat.
|
||||
|
||||
Een paar ideeën: reviews zoals de demo. Een wishlist voor favoriete bands. Stage ratings. Of notes per band. Of iets totaal anders. Wat zou jij willen kunnen vragen aan je Polderfest data?
|
||||
|
||||
Geen deliverables.
|
||||
|
||||
`[SLIDE 20]`
|
||||
|
||||
Volgende week Les 16. RAG en embeddings. We bouwen een PDF Q&A app from scratch. Embeddings met pgvector — dat is een Postgres-extensie voor vector search. Context-injectie naar de LLM.
|
||||
|
||||
Voor de volgende les heb je drie dingen nodig. Polderfest MCP staat live op Vercel — dat doen jullie vandaag. Vier MCPs gekoppeld in Cursor — github, supabase, vercel, polderfest. En je Supabase project blijft draaien, want we breiden het uit met pgvector.
|
||||
|
||||
Tot volgende week.
|
||||
187
Les15-MCP/Les15-Docenttekst-Autocue.pdf
Normal file
187
Les15-MCP/Les15-Docenttekst-Autocue.pdf
Normal file
@@ -0,0 +1,187 @@
|
||||
%PDF-1.4
|
||||
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Autocue) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Count 6 /Kids [ 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2061
|
||||
>>
|
||||
stream
|
||||
Gb"/'?$Fae&:Mm.R$XB_&hB+r.5h]"#81]1%0[Xd*Z>NSV6MRuV,a@W;tA;mhRb;[8k;iR+ZpA"=J,2i1\^=]fHD1Ua*^Apd??""5'+ebBt>G-ZTO\JdaRI;?[GQFB&Hk/`18l0j9F2f[lqgkZ/l2XOlqoc)V_$D9@dXLs/8WbT[VS_$##dDKM0TmRrF@<_'rIHpiPr87s#q"cB[C3V<E<r\5kAbq>A&2R]^?#:cn7eTA(?k]djRuZg)8*Nj3/3gY0*t[f9o*K4c:+Ph/S1SesBh_hV-cX#4ai/-U)P&&Zhi1Qf[>bj-h?Z6E:M($B,?4+&r$<=f'k4+2SKP^[u253QemX?5%m#OOPRXJfLH%s(LoS5_Ha9I32d,*<Xf3VecZa?)^(`fi,0">2$%i/dMa2g1YeY2t[qrmI^C^Hd7dPq1`Q$G9[OrjUU$*^LPHd:tL==qOtSMK&(TfPY&0^2;nC6q_k'ORg(,30/!Z`L>GC1"L,7>fR@%3&VTTG&j^3h"s(L7EMo]\7_;82=NA_2=]54r<TVu47<(i9Ur)fN`9;NO4l..<X+(Fi&HB[Um<RTP\HaX\m^IVP>qhmYOeb[q75er+),J^d/"q;<QZU&@=?7i05pbKn;Tr]0TW9.b\nWkoj,cJ4RX2R[kYB=@Q/-'oP$_j8)?0kFPpCmF>1:dkTK)sj'3fILCV^_7VaM`CJFPXkhq?q6X5!b7$$,!6M_/"Z8n8A`bD#$)dO:LZHcGR"=q%F]Xisn+oRq2?g9Yb_NlS@:se5Nii8`P(;MQ9895h!AV`^$5`.'L/@gi_1'RoK=^#6hXj/"RgK@g8o<@49p!'L^n_URQ).)3u("964jU0sp?h.Y1%<&b8=EM8E,%9=0IUL`8[dA%]/Fk+PaHG]MMARZT;o8.3:=mLViu26g0_DB0FJe=?onO)#(,C_jB9DWfpWFc8r^JI0Nio\/Rp9Tgn+PITY$$EFa!.&-SVXC*h&D9Z+'TN>i[_9)2^Y0CU=hbd<b-/kQpl_:!j$%D&a?R9&j<8)h!"I*`G!,NbSOtkQ[i`(6ZT0BMTX;un?r<mo/M^r-(uJ2V4gSGW[/Ln:K9ofIG-M/fr%/oR*,YXB`P@8D^k=WTuM$pj@/6+XSX#@n7P]3DsZ=2p-%SCogI[faCf!IOtkjKoiVg;+\4=UPt7r%a"s_oQ#I><'sK^AM[[3LE%]';9SgS?f>8<d)b\7#'XKCZ0=Y8rA2%J(SYP6!BJEe7+SPGTj?#A,b-Fuhg45G+pjlqTB&Lg8TT_<JVrM?ea>'c@&k;2uk\F3JA$g_7&-)G'P[q&$3MJ97hJ`LNkE+Af-)8T\p/rX?qrD]\PJa;=j^-8ufjG91kF*i9H?.DRjq?.[$koi)&$O3g<YfIak-E2tXAN&S'P$UVH1>coTo1"<JZh5Nr<7P\IGmgHp*ZakJ&Pnb`O@DL1'<g!@$P/fI!Sb9a2'W/r]@WblbRFNCZjL`G!(*f43b3XK5p:iFP#1C5[edc6t)\5;0FUS=]Qf1/UDF7WX6]RCkmV&AZkfHV0bf\,:P0)d6!HH46UEr*FRpV3`5T\!!@mC-VGu'6)er@CC+,hSBD>MA$]a7hV`pE7>P!0::OIX=WGd]BRTfKFQ^hd+@@YfdF6t!VY?W^=OWEhP&c!F0+r[M?2'9+jmJ3_-eQ[=E,3%rgr-M?9WZ;*\WCS8!sjQtV._I.9He**e8>nB&XpW\JHn/)i/r.n'&/'JdO/HFa0dH*(6&bZo5E&8rEWK;Zo0/X_04HkBFDn4cB(eG*g4`%luS'Vd]75ee4B`P[e@'Q-s0*&/UBF:HRM(nA//i][%Q+O!29@/Q62g5<R%Oo#OSdF,aad(4&Jk8B0A^0eAc3s.jl-"IY]H#(%bDdn;],(@%]i?D5s\c@i`RuTe<e[Q,F\@++V6L5ijsem/_EMe(!dAP:Y5.KFU=1WTs#;[-*o8e-U&3,>2'9o@4$(0phQ%,F564,<?2B'q0(dWKXXt9V7n7bFRY[/8J30F#rAVn9_(eetEJ^euh;H,5`\B%R3S'[eJE#NqUO4HZ$I_e)%hdf9't~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2090
|
||||
>>
|
||||
stream
|
||||
Gatm<?$Fae&:N_CbZeeBUKXt`,eShaO3g^$0Q9c[+4ekB?L)+Oe]KN?ot@\pQ&hA4V1I\1OL7/+GON4$RukYb$N8;\+F65LT3m<e7PnIM3";`A^SD\;-\"8c]/OA"0KY+d69:!9_=?K/MKS\RLh>&^oT4[HnT]'e;/e:Q5@i4jfJDm3b\1<*6q$"=KSY#+Vp@$RrB_*EM4u6kX;pJnhO\$O8Fn^`J!0TAiG2d1j6<*SW;XFPFP6qi:cN@Ynj3<NE-ZI&JgQ!HGF[@FnW5_oW!sHt%<PNG5d/l@S%0eCiCE97R4)V[8i5"KlZd"6HKf*SQqf:Ei`pQhC>,mtkVCOs8qkElYeWa+[prVt3FhW+=2e*cQmej63k*a>QJfm9fE?\@<:*T/3DH+@#hMbT.@l?%]C![(Xk=n)h5@TA%8au#pHb#n(Rc"?JIc=S`2"+T42CTVfqLB?9]:R(ptk&Z2t.R-m(rG'F=V,V*D:X.:7qVi'TKr4PeTm%O%SJr1bmEN!da$lY;d$U77k)_a0EW5OONLulPBpk3,<>Im"ir-eKd)=T6$P`;iU]-V\^TSN)4Y,QNG+oSG;S'cack%-W;Et+QP$Z!?;b_.5u6iTZ\LgN!Jlj\egZR-<7)$Un.``3hnR]<p,\t-i='_#*kJ"KUTd#Nusr8SC,>!a3tE("5lM>-taD"#gP6Gg'Z@@aSkU=r+KqB#AE^J3lhE;WD-qu+iPfI"?/Q0[*U6JXR^m*36h[`60&ocoie<Q4BkjJX\N*bJRT#9CiRU94MJ\J6>4n)W+QTHZ],ajHD>7?m5THpf:\fQWJ[_!>+:[MY:fgfVe75s/8TN&<,X6dhP@aL6e5UaH<^UF8IkE0V;/,P3>D`47:!47s1`cBZ_hnrj"tq8?['\>JpUL5L)lt6(rQt$$rckK#'U2TZ>trd&T-Hb^qar)Q.(]i:3S`<SC$n=&7,!6c+u@VLU0gges;oA"^n&E3IHBVKJ+4P=_@`XqhmSnZHX@,\12OTMUTY`Pj?J\XKuOE\F10BZYf,%2"m(/-pn>*HH6EZfAYZg*DVC_aDZEjNE=hRm$bp$S$O(UhDI$'E6`F(JfN\Cer\l5eQ@p`b1'a%OIW=#Irl6qpo$F)gHmgAHf@HaBWo.$G?`.^kB^R%r@1td=7uZ*RpNEV%S0)q8sloLRjt9*`1Ue;<3ki;ZEJB4cPLL^%0`Q(J$NY.9N@e<&&DuN(f`\H+mlY]-UXcQXgt4!'*=WGPgUbaCs(E%>7a>9d")Fuo!1,Xaij.`2N<^,^5It^PoQ/Hs4WGl^]Fim.bAMg:7WTa;?JoNH5JITQ1V[3hCs5:<:5>,aU`D@/PY$M[:gIXXkMHE8F-T'B>7,Q"(XH<PTjXcjk+Zl`Aa@9=S.s37LWD38iV94JCN6RN5c.!h<S\#NYf:0)j\G[58-MaD(;qip!&`_hi]d]V_CRE(\2`d_GtKf(/S!r06PQ(R:8Q+EYW43]$r#V9Imjrl!YgJK"t:=&W',4ZjTa`THPY.PV.-DfEkD'!KCp&#^83K4R7M8ocT3:jnar7UkUnC*=f5G%\/kjUNl#f&C:kuZ>\<YAK,=c.H\jR-tEco"Vi'5B>a9?ZS2N,nF?J=&/:/"?o"8n$4?Z;R*POE^<9f$danCh.KZ=/*l7s"7ajH0r#[(&%1lE^&e0]h/Km\sf5SL42o&-p-nIa*1>KHY9fF#nkk0=P21i62R<8$AeeM#Bg].^^hB<V.g+,\>NSM=5jPXIG[3,*QU/-&M0P>t@6S'_"GKLGcZoe)-6Z>+U5DN.![PRkYgm&(CY$`@VpD475Ou(f^-B2F]&i(fG]n]t.^(E`GW"*(EUf129;m3s+q,pRJ,r]64aq3ODe&JLPd"$7Ic*hKj_HUl'V89t9*CqcYnaqr],Uo/=dV<YTQ1pjGg,gQ/KGfAaFSl+Sn"6(",o?&ke?Z.HjBJ^K$&#KB?ODqT.TcfA+fU1U7gn[.JA@1-!n5hH&Qju8+_Q#":ccXN%h2-[!@,@6&`Q(mEXuBBK$hX#=K+Hk"P+I"b,TM_YPN**2e-&j;js20+R@/%:5ElJ#(c^.\_FF%N6C[>m__aprW1QS>R:~>endstream
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2272
|
||||
>>
|
||||
stream
|
||||
GatU4gNM>[&q/)-bj&,QQGeZFXOqsE`D,.!M-I%J>!4kV&31pX5m.RI;WFtL*q0fsEI-ie2t6snIIA%5VA[,.n2K/:]i`[+5K"6`[+\?hL_dA/V"<-#^RiC"b%22;c]YW%T$:$*e!&GdqsZX=W;U`3'*sF"\T<S\[mF`0:_ENqGKl/XoM0I%[`i^":TJ8S#Bk5Xf1i*mWb0tkVi9^XA_'f95HgO(;el(!gTQqW@P4X#G0?j4`u@pRrg$Sh>5a5Jrme7M5a??sZCp?.P!$W+PsMPJ5i+@lOE4fHFC)2s8=/D*f![S*Cfl]n;=T7'K7j;q`Enq?f@uJRFZ!k(G/n1Dh]d*Ui;8)h/^J$=U:5A$Gd"N)e#\0&WmW%E\b8Iu:2Qg:mY9rT7b*2\DpfRh=Ig6CNOJOk<3G91LNC]mA^a,S?sM6.?hFqo0bR[iTjfLaY9_[i379i5rQt!$SVlX=ltE^:dEBP[emTa!+U\kNmWmh#>3daqkA''6kI#*tDN)&_YK4t68QXKYZ45E76H8GX11Vn?V90L\7QmO1.;SPbQW]Xu8`Po50d\6h,>=Wb2=\H5QPZjNl0NmEoZS(X&W-\=A<B#HBIG_\qis.uhG!/3r$7TcZ;&pVa%s]Tib)Ak"\qHMk`VjS;u"GtlM50]V$8PMA>%Lf7.7,c:m$:,V5J,&?C*[RI`2OM[_f@sG]ncbZ_]EE$*#iD\fNuIXYH!hHOX!5^'ruQ16jcH3GNm-^SdF,oUn\?\u=p!CCnLd`YboqaK1[DJssBaV(N<_AX]'J:4&"7-&QE]\HZdOoRcg,4K29FGXSJHB;7f,=?[SeT#!cJ5Gda*VU*`@ilOS@,[.6$>-GJIVZ>G6Nu^fC^PjX`Vmea]CX7Y!LfRrRXQSkBHd[&pCXn<7)G+$]B0pMic<!o*ID8UT/SW6I,g-(t)LY/S9[4QBG7hO9gHcuBCuZ\!Eu9R+a=rdp:1A#*&9i5OgMoaVghBan%2-Hg1`cK7qHgXAcj;lUPHs<@bB\CpMe;KqM<eZtfcAUtaO.h-V`+&3'2B;&3uXFYSJLEBSrhiJ9'32ZocY727.c\Z>':\;.,]Nb1%pFJ]jnCA)Y%/rHUJ??kDcrGf21'J&0XZV0sOi>/)PiOaETUp,8]sTBG[$3J-rUtqOuD5(lN.4E'L3Qc_$1qr@gXc-&DaC5lu7.p<md+2NO?S_^)Kq==EMWYI-_h=r3h5r[U@Bg4K&a2b3"eLoH=?Z2jI<n1C%,Gl@%^*>HL>U[_ZeKblb)`j@4!VM)st7g*7S=K%`3Wlp"gKI@"+0DF/lQbeo<pE(9H!hD9jDK`oj#JLt&<'*=sgerU9<7IXb+1+uf1^BGS2(_e31G(TB[BucpN$s<:!*H/A95TFUjj&Il!f+R=g1)fKM)T`c8l$MWg$_^?J1,R#ekX#8T@2jWIb]K`(p^Bs=M3mRV1?Q?(="!X':djcnGUgo*<Eu?n?e>CQb]u-\l2Okqq7UgcS)K!6aS9Y.3[>pRR0dY$&m_j42MA!;"MZs7(db`6#sOV(5SI'jOrOGUA=%iIKkSRk482%f,`;&^p+hN>-D:<^AA3;#u5r>%`P(?DeQF]gp,mtGUsoPA'&3U#Y"J;D[#F9$+&J,ScL"fJ:NI42H"(eF/3QK8jB&kP2u8YNVOU[Q-.iB!sH%LT!U?FZIJPR8M_"]Z/lGP91MC"b4lO.+<-<j"U;ma?+"0G/#N#8m:t?`'Zij23^+Jtf[$(Zc@pX,18VJX[g+s5?DoQAaAIt#)DMGfS<"GeeuuTg3!At&(K`-C98ep<%.[sjZJ.qYkJm=%Cj'SD&%O6tmmZ,$-^F,Bf`i\!Qg#R))%r_H_R[?F>EcR?[4iq0j3%=A7nk.7D_55/dG>7d4/)?dLYoI`!FKT0*nTdJ-0P1Fb@/-/fjB)ko/2n!CP5<5<$ZK$BJ=7XhDKuKj&&b:&P;f^K/+"rlZOMm6=iQ7.5Oi@#3Q,,Rg_.P_9.De53b,MLi^oMdk[9=T93nN-<Q=\lt<gO)8QdPf>',2"/V@PXRe1bA3BOn6,.eW"9&+$,q$f%dMb9',u+PtA%_W3e"@hW;Vh^r!2/hb#sk"PI7-<BoZrfk.lZ*,SR7aO\dU$S2g[0p]ItBZQ.i\]J#h9--N]<ZVmHGB@-GqkV'aD/PJAh@YUl_uIaK3VAA,f>&Nc$\k`u02@JC>1BirhYh_=cX"@Nd3mcZZ7QhbgYJc.Te><NS^Z>0<E.rYL_:Rh$T...qIk7D0W/o__f'<15@/u<qK6hKF]:SP?m~>endstream
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2142
|
||||
>>
|
||||
stream
|
||||
GatU4bBDW0%,Le2E4i1!A$R<OYj>ZU\7n^'7h[1Oe6S#(L`5k>m*&:`o^rBB'MUZp9dbB1R2c]e$fL"S!uTC/i;Q2+^rnb9p.#*K#kK>F,C]jf)-kFIr]:IUQ'9ReS;68.S.-kFF?CDfF*-.IU&NH]&T&/fqpGt"^#u'FPBT,T/3oS>&](b&+tF77r^Fn-+sR,n6L2C:q$kPtAc$u+pE:U_UHUjEdt:DC'c%.5eHVfM9-1'Q-_lqq=Gm5mBG^uc*"4K.KbcN!8f->^l!+M[_eY:,b\.g#\A?6QNi5a)DJJ*6p!nI)-TQ%[QQoLl1$].J`R]gF/-T`B-PO"k=4hAHCtO0Ue<$e6A;.[DHhkK6,t;o(1$o"gAGVfANUPkJII\^29\>IbrLrR/AKf;*A'h6]kZbr2DWHf9S&X_CZ;m_eLr-oUdDNT<oGQKpms=`SVqIP@=-T5)HPdY3I_8]o.MX.[=:69$>foC7WE:C1kG!d_08=57EX6'Xp=;R"5)BSlL&O@!I[7DVBo9kY6nt@#oV![SOG0q`bC$g!jd+`mnXIVC%&%W/8Ja,s;(r^)]#r[g:K8hZ_P!7Tf$FV?*JS4^NG[LSE3Z)/2cZ/@fS)qjP.#7h^pQ\RjXG9pS.E3j;H`b0@!ia`_-e*$`8H%M/s_bX[a%EKaW2-Om-+$M]'dZg6aB6V.2#bh+9&&Wj;t%,V36P/[FDl9QqWk^Lf-.'nu/A0/6WHSQ,T\g8>_>GA:lQ]XE&Sb\@"7b;$pb^"%$FMRaRk2UQN_l)l9*$153,(%>e+fd(eXF,coE(G2a*"rBu^R7QID20TLGopV)k%27]c)9"K^MY0i%!h"32050Q$+f`o>fG5N)]P].DMS[T`+%1k1(N^R;G#`7WP/u9(1FDYEM!FcTk#tu$o>.o%(pLT>(3;&'%adD*O\6sD"[L3q68U*-3K0Pk9L?A]aq2iA*_e2lb'0;eP&D$?`SA"q1`cWQ52d`^?i"\0K^Z'WXPYRR5g-"crk\P4YWuiOt;%KrONPfBG]%9sN.S*QW@DdJtWHlB2KSBUBgn7Df*YAC?bf)MBj>#!K>kKMrd9tDC\HdG05EB%N6qOQKo?\8N)lWG[AtiTB`rZ2UX=BSVq:=rP";eY4WJ/sPr`Lh?*^JhI4Z&I>+-JSP#Jp3aUb^W=$.UM"AfC=k:IihgRA&0AU20CbWm^Q,A'8a$Dp@)2K/;V$a.@rG`qOW]cCU4K1k/Aa"E?=8MD"6I3':@P?Vj7[FTX)#EWY@:gQqG3Anjt#4e94Dfr.^7*gg*FPAO>t8A'/_e!%dLCYr,(H[7Wt;nalM2:!K:P9PFolp<6AZQ`#R:n:_<3RN6:m:S5RV;$-de],rl.tpV+RYutlTHZ.,dS;<.h"h"*mT%2d\+7%!.h@Ea+)g8QFmaJ=]2+7AF4fc/2XMcsR2i@]*LU6rYrAHUd]A**dWFVsb6]KbHh"ImiNsCVLMN#u3>bMgkH;4Qh[5Kh>qN*G-DN_/bQ2ZEr]Q_DY)V"1F:j3[$56gElqi8]Ooj\*GIolT*L4.hdt\GKl&D(";KAIg,$Of_4TO\IK9%"#=H9%\X";*(YX%H=82Hk_9oXt#lT!>eLo&.B)",>ES(L!J::7`+3/5a$B@L8!9)L$p*A'Vc_fcJ9ddD)fEP0>LV$.qEH)J?kg:+Jj&:U]kaQSdg[>toE4fUR@R=:kqbspE#<JtR3A49Uc'raa`)l?f9AND:Y.k/>)87Ppk,R=]sG<DZ,<O(Qd7,1;hKg4%CY<2+&**AD)m89EXfO;41#L6eNZY"#/=*'LF0dITWY'W,iG`MKBJ/K^tQnb!%RRN8k,>Vmc4,o_te*$?=9V+$?mRsY%),Z[l3sfLgf3OU\/u'.1$D8k@Ha%!3J4GeNr0n*)3d==f2_o.o&04n3T%P$?R+E"8S@L.ZW%j\]p_A<-047MfSfH08jSf4eQ4s&GE0"2TR(8J1%Mp"TJm0^78$5L];'lInJb9^N7/qDZS5keVkmk_/!O/f+ck-P+c,Um?]pVguj47e^hQ52-;ImZW3O-6$hGRse2%Q8jRnrG+&GERj7]c=2"lrjY,BtSVCcln0JJnca"AI0kCr5WnkGEC-%X;YtbP:d9?4*`uWjV!kR:$<CgAgUG#TWdR^aO[?~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2219
|
||||
>>
|
||||
stream
|
||||
GatU4gN)%<%"6H'@/SBKXi+D-/788#EGX_l;&VAoPNaoP=HO0&'$4^O9=aTEpL$QiZKk2OfU'N%QODa0huKeY*TDG(s,Ae.B/BD[q1&Ymbn`,Q"XBI1Bek&+c]^Z$EZ>;De*qHEa'fgT*$((/*ZbP0eNYs;JkZI:^4R)fAbDpUoqo%VdgsS5>=s.cQ$$_]>nq+W8u=N`=1Fa+A[[V.VG&@<3GifrS78O`G-p>37iVAs3&2ArH,HK-M9_;+kr%:;67^U`JS$F$rCB_TRWD)t;p#8cWKbXB-+KV+b*L%VIC]tgcBts\)>mK]?supqA52/FE+M;<etf$r$V)[@KC/ZRkBD;<JC4/an.?M^&<th.=WE)rd1SWm%)ZRq2p;=g,+*&@]V:BdFCimfm6]\$3%C2cdD64Q"hdc59Ic*;[UcsGDTT[L/R+/N4'CER^7gP/l*NrckV?<G#7Y.pBP*>07*a'"Ou<O6I:hg'TE@GL-]\8kdV;RFP&ApT&O<>$3b9Cg7g&S34_FHPjdh]Z$S?[K8?dLf,j=X3ftu7Q^LThE:H7g[K2F!;N9VgHGGG*%HMZoI6A+=53uI6`PbSKB]F]D.^bh_YCa5/QU?rTLiTT$Urhd6;iGB2Q>JA>:VrI'dQB/W)B,EgGqV[?2CL9$4E(T=;(S@.crPG!<8$:-`VH5dkd-bI&7h$/eMO#Ib/1LXiJbD(@.$G9q[:tK(;770N@_9.-S7[lRO/qkbkLUBIEpQXm`\RM_DCR^Ad%fpK]D&K]mbE-_Fuj=hm+m.Sq6SWYLgcDq;++T&[]TDF>bpKqjga#i,_cu[MN"A9`"g>$9"G`-;)j"m+n+CYFW%b:*gEWr!kN+ZE+<_N+!mF#ENTOZf;9rUNOLS-AL='A^*,eU"\:Q@+l9NsJJs,df7]^F8]!BgEAK;S5mO.3&X==R5b!+,Y,u"V@]Dl!^Vf<Z0TAgg$,L6[3s@N=*:tAqQH1n3@s#OC8b@`h/e]CrVlB0*<C3rZWcgKnQC[tRLP[ug'<-(fE*qV'dE&@(1TY8QF!omr?@lrffkh6'o;*IcnQKoC5&e)F>baITkA[c'&ubs35h-QjX+4?SP$_F64l,F1U?>'d-N%\+>ALu$p$"_>Ca9O=Yc1d+<+ro.QbWW$muWLu,K0-c1t?8%8t=`"Tb[q;/q,I!550/'>>@5eM:'^Z0H?(5`1_r&?mOJZ&<Flj0aJ[+:C`/XnGRGW?iLLA>R(i:l<@(e#OlR+p,*n:*_a+$"e'VAFS$5@rcM9;'R\SFfY-=m2g)Qm%\7H]4SfCPpEqEMkgk8!_YBKSW5EPr=0\bJNA;m/W_-2h'oJiU[UR]7BuBG9MdrA.l#VCqK`i9G^g'1*@<nPf?"/\c>?t(%+]`lNTp2c673SOnD_5;Nl:88qq:dan#G[]ha!0Ds<8LYX.gatqGFY?k5Z7N!>S!<gE]HW%.M4hnTVtA\,$t-:'Zu'`%9h%blmaG?/;$bL$E_.AXa>-G/^]2A3J2_mgC<*1U_C^2"7TmX).]i=aooZ\SC:5hBA9So58r)n`%R?Pqok+IpW%&87`L,`JF*-F+3L's`Tl.UFO?,/gSod?msHcTb7]&_)Y0FN]r0sNH?IECK_bn%^Dh=3FSpr0HGSJ(<26,9*,GrF>\9%n_?=.Eei1b$5iaL8B,>JUj2[)!Ba54V]@H2a@DA/ah1.t+c8Yo/Oag/Chi<4UO_RiO-4<eDoEFmJVB+7N[?ruBeJ)+of[=eb>Hi.5jUNCB*`WCtKi6&r3PiQ#<opc<qp#3K8*Y7+DN7;[P9g:oa3(CK>>9BQIEX8jkB]"@=Q&](*3%,nLZke=>h5*O;g9A%VF]uEp`=K_j\1DM5E;B:1O"&C"qdf@2;!q?!H!nArZ';de%LM5oi(1PWI<49/@8pn/_M2(DiG_:g28-.DkQ?o/t8'C6>[.\!Rg:hUO]KfKS/?\N]b/>gBH[CW^?sY`'p!g7sIn\*MT+Iad]+=a@@_hVP9>^_,`%\f"0"Fr7?<mi4ButX-hu=f+'JD^S],&;(XgG*.3`.4Y-lt@0])sCMG]/%6]gFHI#knHbP:[Ukr_EWd?<b'Fi%_]fU]eZHh-nj1%kH#@uM\Q?$>6=-q7tGOO;8n'6D+oR9tGe5.#+qp?$mBj]\=DIJ%#!Vs4l1p.'[^ELVk`,aPoJ'j!D3?&7CRai]7/%],TggNKUk>F\<Ti'9$VUIKuF$`\%"1rfM#Q~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 386
|
||||
>>
|
||||
stream
|
||||
GasbTb>,r/&4Q?hMRtDf$?6?"CoPPaU`uP9fJrO\Fm@\sG1,8\qOgBX$T-Geo$!W7\cOmI39BfSGcpQ*+s74l)i-7";8$F9-!uM=FH28q4=b"FPC/fnTCS0.\==9n+5\=P&QeBFU\8>6IYJo+UQomAMD.$=OQD7kJ1];b$-I$Yg2[5F.G\N:3hsc7-q(_mQO$I2#bqWVl`_SJ-A0X=QpC2VE]i"J`b;qNeY9XqBTQ7daeY1KIq/[=Zu]MC7R:8&HQQA;M:(OsmHYI[mpn!PV&2rH;KPtX)&DqEp#FJQcF]Cnn[bq_]%sS+0;U"biZN*VJP^^AMY1g8U=U!(^:S"8;<P9XBm0EFj+cm[=oKMpbF>A10)cc<+PQd"Lu]L!<g=g~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 22
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000132 00000 n
|
||||
0000000239 00000 n
|
||||
0000000351 00000 n
|
||||
0000000456 00000 n
|
||||
0000000533 00000 n
|
||||
0000000738 00000 n
|
||||
0000000943 00000 n
|
||||
0000001058 00000 n
|
||||
0000001263 00000 n
|
||||
0000001469 00000 n
|
||||
0000001675 00000 n
|
||||
0000001881 00000 n
|
||||
0000001951 00000 n
|
||||
0000002243 00000 n
|
||||
0000002336 00000 n
|
||||
0000004489 00000 n
|
||||
0000006671 00000 n
|
||||
0000009035 00000 n
|
||||
0000011269 00000 n
|
||||
0000013580 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<34b5a86e4dd3638aeb863340f86aca3d><34b5a86e4dd3638aeb863340f86aca3d>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 14 0 R
|
||||
/Root 13 0 R
|
||||
/Size 22
|
||||
>>
|
||||
startxref
|
||||
14057
|
||||
%%EOF
|
||||
213
Les15-MCP/Les15-Docenttekst.md
Normal file
213
Les15-MCP/Les15-Docenttekst.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Les 15 — Docenttekst (normaal)
|
||||
|
||||
**Fysieke les, 3 uur. Voor uitgebreide voorbereiding + context per slide.**
|
||||
**Voor letterlijk voorleesbaar script: zie `Les15-Docenttekst-Autocue.md`.**
|
||||
|
||||
---
|
||||
|
||||
## Lesvorm en flow
|
||||
|
||||
| Onderdeel | Indeling |
|
||||
|-----------|----------|
|
||||
| Theorie + 4 live demos | ±1 uur klassikaal |
|
||||
| Pauze | 15 minuten |
|
||||
| Hands-on (Polderfest deploy via 3 chats) | ±1 uur basis |
|
||||
| Extra werk (eigen feature toevoegen) | ±30 min als tijd over |
|
||||
|
||||
**Focus:** koppelen + gebruiken. Climax: drie chats die GitHub, Polderfest+Supabase, en Vercel MCP samen aan het werk zetten.
|
||||
|
||||
---
|
||||
|
||||
## Voorbereiding — een week vooraf
|
||||
|
||||
**Studenten checklist (stuur via Slack):**
|
||||
- Supabase project draait nog (uit Les 11/12)
|
||||
- Cursor + Pro Student actief
|
||||
- Node ≥ 18
|
||||
- Personal Access Tokens **vooraf** aangemaakt:
|
||||
- GitHub PAT (fine-grained)
|
||||
- Supabase Personal Access Token (account-level)
|
||||
- Vercel account (geen token vooraf — OAuth bij eerste gebruik)
|
||||
|
||||
**Jouw eigen voorbereiding:**
|
||||
- `~/.cursor/mcp.json` met VIER servers vooraf werkend (github + supabase + vercel + polderfest)
|
||||
- Vercel OAuth-flow vooraf doorlopen (anders kost dat live tijd)
|
||||
- Polderfest MCP finished `npm install` + `.env.local` gedaan
|
||||
- Lokaal `npm run dev` getest — `localhost:3000` werkt
|
||||
- Eén git commit gedaan in polderfest-mcp folder (klaar voor Chat 1)
|
||||
- Backup productie-URL op Vercel klaar als demo-fallback
|
||||
|
||||
---
|
||||
|
||||
## Voorbereiding — dag van de les
|
||||
|
||||
**30 minuten vooraf:**
|
||||
- Cursor open met 4 MCP-indicators groen
|
||||
- Twee terminals: één in polderfest-mcp folder met `npm run dev`, één leeg
|
||||
- Browser tabs: github.com (logged in), Supabase dashboard, Vercel dashboard
|
||||
- Slides in fullscreen
|
||||
|
||||
**Backup scenario:**
|
||||
- GitHub PAT verlopen → tweede PAT klaar
|
||||
- Vercel MCP OAuth weigert → val terug op `vercel link` + `vercel --prod` manueel
|
||||
- MCP-indicator komt niet → herstart Cursor LIVE (laat zien hoe)
|
||||
- Een chat raakt verstrikt → ALTIJD nieuwe chat openen, niet doorgaan in vervuilde context
|
||||
|
||||
---
|
||||
|
||||
## Per slide — extra context bij voorbereiding
|
||||
|
||||
### Slide 1 — Title
|
||||
**Doel:** verwachtingen zetten.
|
||||
|
||||
Vandaag is anders dan voorgaande lessen: minder code, meer koppelen. Climax in drie chats.
|
||||
|
||||
### Slide 2 — Vandaag
|
||||
**Doel:** roadmap zetten — drie blokken + climax expliciet noemen.
|
||||
|
||||
Maak vooraf duidelijk dat de drie chats het hoofdmoment zijn — daar werken we naartoe.
|
||||
|
||||
### Slide 3-5 — Wat is MCP
|
||||
**Doel:** basis-terminologie + waarom.
|
||||
|
||||
Niet te diep. Drie rollen + drie primitives noemen, doorgaan.
|
||||
|
||||
### Slide 6 — Ecosysteem
|
||||
**Doel:** context dat het breed is.
|
||||
|
||||
Niet alle servers doornemen. Een paar herkenbare voor de klas (GitHub, Slack).
|
||||
|
||||
### Slide 7 — Vier MCPs vandaag
|
||||
**Doel:** zet het demo-blok op.
|
||||
|
||||
Belangrijk: noem dat alle vier via HTTP gaan, met dezelfde patroon. Vercel doet OAuth ipv Bearer — uitleggen dat dat veiliger is.
|
||||
|
||||
### Slide 8 — De mcp.json
|
||||
**Doel:** de complete config in één oogopslag.
|
||||
|
||||
Echt de hele JSON tonen. Studenten krijgen 'm als template in lesbestanden. Wijs op:
|
||||
- `type: http` — moderne syntax
|
||||
- `headers.Authorization` voor Bearer-based
|
||||
- `vercel` heeft GEEN header — OAuth doet de rest
|
||||
|
||||
### Slide 9 — Tokens regelen
|
||||
**Doel:** student weet hoe hij elk token krijgt.
|
||||
|
||||
**Belangrijkste valkuilen:**
|
||||
- GitHub PAT: fine-grained, niet classic. Beperkte permissies geven.
|
||||
- Supabase: ACCOUNT-level token, NIET project keys. `project_ref` uit project URL halen.
|
||||
- Vercel: geen pre-token. OAuth bij eerste gebruik.
|
||||
|
||||
### Slide 10 — Live koppelen
|
||||
**Doel:** student ziet dat het werkt.
|
||||
|
||||
Live doorlopen: open `~/.cursor/mcp.json`, plak, fill tokens, save, Cmd+Q, opnieuw open, indicator groen. Voor Vercel: klik op "needs login", OAuth flow, terug naar Cursor.
|
||||
|
||||
### Slide 11 — Polderfest MCP
|
||||
**Doel:** student weet wat er in de zip zit.
|
||||
|
||||
File-tree doorlopen. Benadrukken: één bestand doet het echte werk (`route.ts`). Rest is gewone Next.js.
|
||||
|
||||
### Slide 12 — Code-tour
|
||||
**Doel:** student snapt `server.tool()` patroon.
|
||||
|
||||
Eén tool (searchBands) is genoeg. Andere drie volgen hetzelfde patroon.
|
||||
|
||||
### Slide 13 — Lokaal draaien
|
||||
**Doel:** student kan dit straks zelf.
|
||||
|
||||
Live: commando's draaien, polderfest in mcp.json toevoegen, herstart Cursor, vraag stellen.
|
||||
|
||||
### Slide 14 — De drie chats workflow
|
||||
**Doel:** climax aankondigen.
|
||||
|
||||
Maak expliciet dat het DRIE aparte chats zijn — niet één lange chat. Reden: elke chat heeft één focus, mcp.json kan dan beter de juiste tools aanroepen.
|
||||
|
||||
### Slide 15 — Chat 1: GitHub MCP
|
||||
**Doel:** student ziet GitHub MCP echt aan het werk.
|
||||
|
||||
**Demo-aanwijzingen:**
|
||||
- Nieuwe chat (Cmd+L, of via "+" knop)
|
||||
- Plak de exacte prompt van de slide
|
||||
- Wacht tot Cursor tool-call `create_repository` doet
|
||||
- Toon github.com om te bevestigen
|
||||
- Cursor draait daarna `git remote add` + `git push` automatisch in terminal
|
||||
|
||||
**Backup:** als GitHub MCP traag is — gewoon `gh repo create` zelf doen, maar wel commentaar geven dat MCP dit ook had gekund.
|
||||
|
||||
### Slide 16 — Chat 2: Polderfest features
|
||||
**Doel:** zien dat Supabase MCP + Cursor + Polderfest MCP samenwerken in één chat.
|
||||
|
||||
**Demo-aanwijzingen:**
|
||||
- VOLLEDIG nieuwe chat (Cmd+L) — niet vorige chat hergebruiken
|
||||
- Plak prompt — Cursor zal eerst Supabase MCP aanroepen (vraagt bevestiging)
|
||||
- Daarna genereert Cursor de tools in route.ts
|
||||
- Accept all → save → `git add . && git commit -m "feat: reviews"` + push
|
||||
- Test in DEZELFDE chat met de twee review-vragen — die gaan via Polderfest MCP
|
||||
|
||||
**Belangrijke uitleg na demo:** drie verschillende MCPs samen aan het werk in één chat. Cursor kiest de juiste.
|
||||
|
||||
### Slide 17 — Chat 3: Vercel MCP
|
||||
**Doel:** zien dat Vercel MCP een complete deploy doet.
|
||||
|
||||
**Demo-aanwijzingen:**
|
||||
- Nieuwe chat
|
||||
- Plak prompt met env vars (vooraf in een notitie klaar — niet live tikken want geheim)
|
||||
- Vercel MCP roept create_project, link_repo, set_env_vars, deploy aan
|
||||
- Wacht op productie-URL (~45s)
|
||||
- Open URL in browser om te bevestigen
|
||||
- Update polderfest in mcp.json naar productie-URL → herstart Cursor → vraag in chat
|
||||
|
||||
**Belangrijke afsluiting:** "Drie chats, vier MCPs, geen terminal-commando's zelf getypt voor github, supabase of vercel."
|
||||
|
||||
### Slide 18 — Lesopdracht
|
||||
**Doel:** overgang naar zelf doen.
|
||||
|
||||
Lees de acht stappen voor. Geen deliverables. Wijs op: een mcp.json template staat in lesbestanden — invullen met eigen tokens.
|
||||
|
||||
### Slide 19 — Extra werk
|
||||
**Doel:** snel-werkers bezig houden.
|
||||
|
||||
Hetzelfde patroon als Chat 2 uit jouw demo. Studenten kunnen letterlijk de demo kopiëren met een eigen feature-idee.
|
||||
|
||||
### Slide 20 — Vooruitblik
|
||||
**Doel:** voorbereiding Les 16.
|
||||
|
||||
---
|
||||
|
||||
## Veelvoorkomende vragen + antwoorden
|
||||
|
||||
**"Werkt OAuth Vercel altijd?"**
|
||||
Bij eerste login wel. Als token verloopt verschijnt indicator rood — opnieuw klikken op "needs login".
|
||||
|
||||
**"Moet Polderfest in dezelfde mcp.json staan als de andere drie?"**
|
||||
Ja — zo hebben we 4 indicators tegelijk groen.
|
||||
|
||||
**"Wat als Supabase MCP read-only is — kan hij dan toch tabellen maken?"**
|
||||
Nee, dan zal hij dat weigeren. Zet `read_only=true` op `false` als je migrations wilt doen via MCP. Voor demo: dat is wat we doen.
|
||||
|
||||
**"Kan dit ook in Claude Desktop?"**
|
||||
Ja — `~/Library/Application Support/Claude/claude_desktop_config.json` met dezelfde structuur.
|
||||
|
||||
**"Wat is het verschil tussen Bearer + OAuth?"**
|
||||
Bearer = vooraf token in config. OAuth = browser-flow bij eerste gebruik, token wordt opgeslagen. Vercel doet OAuth om veiligheidsredenen.
|
||||
|
||||
---
|
||||
|
||||
## Foutmodi tijdens de les
|
||||
|
||||
| Probleem | Snelle fix |
|
||||
|----------|-----------|
|
||||
| MCP-indicator komt niet | `Cmd+Q` Cursor en opnieuw openen |
|
||||
| mcp.json werkt niet | Valideer JSON op jsonlint.com — één komma fout = ALLE servers vallen uit |
|
||||
| GitHub MCP geeft 401 | PAT verlopen of geen rechten |
|
||||
| Supabase MCP geeft 403 | Token is project-key — heb account-level nodig, OF read_only blokkeert write |
|
||||
| Vercel "Needs login" blijft hangen | Browser pop-up geblokkeerd? Manueel naar mcp.vercel.com |
|
||||
| Vercel MCP kan geen project maken | Vercel account permissies — check je teamselect |
|
||||
| Productie-URL geeft 500 | Env vars niet (goed) gezet — `vercel logs --follow` |
|
||||
|
||||
---
|
||||
|
||||
## Vooruitblik Les 16
|
||||
|
||||
Volgende les: RAG met embeddings. PDF Q&A app from scratch. Studenten moeten Polderfest MCP live hebben op Vercel + vier MCP-servers gekoppeld in Cursor.
|
||||
162
Les15-MCP/Les15-Docenttekst.pdf
Normal file
162
Les15-MCP/Les15-Docenttekst.pdf
Normal file
@@ -0,0 +1,162 @@
|
||||
%PDF-1.4
|
||||
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Docenttekst) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Count 5 /Kids [ 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1796
|
||||
>>
|
||||
stream
|
||||
Gb!;d94-FC&ARfck30agd-<3##plbsD.mlKq6H=+[VSP$C0-/Nd*LQO4`'-p#$P1A+Bt;*f?X;,Af6tXZ0Hh'!IX+Kksks%K)Q%C@lViMKT#iOT-p_6oIRlfXcS*PGt-_\%]uMXL?2RPj+:\EVB&iJKXUl2@XLL)fi!b6rJX-H^]H+)WW[Dr;$(fI?s,\k+sP6r_O4@WkiAJZ3)9cthEZPtrO5suhs&jgO;F@$$JgoGKk4eU(01`#[;a#uIZ#q&55pWMc4Em<n%a[`/.B$#U4>!r@iQ8O`!?`s0LYk"aiDb^8E:-H'[3f^R1b3NWEHd9'%)9m5qB9?nP#&R/CJ&iX&u6TEl`ORg=VNbOj)>nr(O>+@(-Qc=3U'6H)^PP<$Eg>aPop&`XuPK"2f:E=*ndCJR^8<rdfK(d"VVa0M9=O?L;"$&aiO**[Nr#lRW^>J/LWa#1+TW"iBIT1"B>m'7.8]N6q5!c\B\kZcKTsjQcqEi(J0Ipf>Mhm\N\#.M^Rjm*Rmb41b#8O0OKQdl!Jp&R(3-KT6]ul+[dX8hmtJDH=-H=9d`5W<'&eoHu$oMU3t8O<t?g(c*<Wd%FN'YWTpIQGG-6;C41lo%bfZE;aOn'eb'r"5Fhn_NHIl9831*SeO&Nn2&C#fb6@hc':iALEe(sM/f,E+0\9q+[IsD9&@IX?%O#E/$jt8-@:.!C#9Qn`LSJ<ggVu_nX,8X<ZkHiKn0lH&4fS<'HAaeg86bHGUc!*dhp")Mm^Mb]=u_NoYb-'@+gc6Q6/(E"2h%0,inq'l,7Q"[`t"P'qFnJL$L3[G@H5@#);;>Rd:+ga7kl&:V^Ro2gMh8XYV](@nL(nJk"rZR:1!KH(&:AmL3PFRapOd9V4Kpl>nt5#HM_0?C,_.gP##P*o139@1'c@?`d.lBCpfI*%%WLIJTpo4$NdI*R?I-D]eJ)'eBZ_$`c5\b#25.OYjgAXQp69*MK_%h:$$$rqd$^9*K'0WYN$EAPD3AA4@5kAnb/nDQ._%kUa;?Gr-JQ3I;V4/Uq&c9ha8k_U\J_(c*8sb2&?lR.'LodUn6W(a7P#Yetp!)h)NG%d&J4@RNacE1SK!"`7J!3Xr#K+a2#0"!%p6j!2J(3OXZ4L;\hGk@Gg9e*4_L+eXZ&i/pWNIkV)b5I-i-NaXG@O#$&\&MKV/B1&i`fc)?i]V0^9@(#=6SCOaCD2'VFZX",iKs[C2:Z6^9Tp3n?"L`?O]3RdnFeg>m*@(7I1US;+!jI<DF2LW$UGfli,&m4h:\d%DEpSbn::L"+\lp`m:"D+_C$.sLYrmn3RB'YO$Tp8+OgQiUm#u7&8pt1]ff%F3;H8J&!F47'D_tQj@$jWeRMJe0gL]`UIW&k0\\Zb<l;pj>6JWL8Rllb1UWe!m*W7W`bC2"JG@Am+VaaWcQd[LS)cI*q/\oK,@Cu>UnJrkje-=*>?Zd$;H*$a&;%q+50$#sA]<J49UC6[)PB\e0?U;)7@_E-1St<Js:W3+b6-SZMf$gCiITk&]>h-`^SfnSIru.ogC8RcG*XJt`3Pft+9I6<DOQ9=01u/M8mM<3]q8,Zk`4(TD(fRR6T@LGA3pfi/@=4u6MDD*gGd5/&V0uUBk2s7t;%j;=1YXb2Xee''ea'B/(B`E'3=qoM,dNAHFVmqGXW^8s^^<Q]=%^#+b'?kG,+R,@$LDo%e.4CnQ#f+>`22nlH[gFX">OhmlIoEi180bbH6^S=+g!`<N&'-H4>4)9Nml3_97hY%S<!C^(0h*Z4s5<!ReB+NN>4XVm+2J&qXgcglcj_d-[u6>>aX+~>endstream
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1725
|
||||
>>
|
||||
stream
|
||||
Gb!Sl?$G!^&:Mm.R(#dJi*p2Ta]lKjD,8l_-_0ma(L[hD6+4Fh9[`k8NarD@K\"7c\sj0)!o)/I]eakEj?n]/^AFJ^!%1-Yo@=.2JDgY/cjO9,!U%A6^\@ZFS\<+&#/S*1W#pA+Y7o?*FLgEaX))^b6H?2>Ccifg$M>WM&-`$:#@9@WF9_hkK<G/V/E5Pk7Q0>7B!>OhfsF!7qu$U6?Gl8.]>5;0XZ#G??I/@UIr-j;^UGc^5@",n;HbQ3,o2T;Tt<L[?FG-Anq7m[1h93fZ$#p\](,7\EOj]`K(UlLn((<crS\20(fWLKR*\>WlLoW&;ZJJDPTiOR6#G>$;SpAcF\k&EhRr5.QJ=.9*)op'MY"f`j_jst[5A+sp(P)jcL8Yd[F_`oi^Cm-9ct92-F>OGVUa<lA.GA_+gVSU_JODAJubn^A#9i9VPdqG<8)r-W"KfF:O\&:DZo&g3*Zc]U:*?CWJ&WT_C%+tbG.4q@&Y&AB2(74[IkcR:099P9$dk[9Q>?be\ultlG_`^<i#`Sk$9j!bMsbi;$eUNM\HRAR1](c8'/<O!esYs7CU?30W&0cVCh@t`#:f]=(Vdua>6fXq7+Y:Yi8br<!5*9DdI?,GVOQHH#U:\mWQ3A1>TH)B17M&?FMgVW/`Qq\Uf@m$XfU$dY8?BM[Fk%rp.*0=N1d)E=237`6lD:QHsVm,+4TUMQrSSRB)>,ZPR"lHng$KiQM@/h-dpneGq.(2>F/8.V$]QF4t54E@Tt0aMQl;Bs-+%IcPXhW+WelqM)4qIm\;<UO2'3lVp\BF\)a;gB%JP+QrnFWn>a1)"VO1P>bsjaAZD]D^\Ypq8'XZIVm9g^Y>M-FF_E4_FN4KAOC><rd+T`YeL-\2%-!RZSRHtBL=J0ar?X?%J`enFGIkDiAIj8+$O)s=[DqM'e*W1E"2F]Ke0VeL#^#S(ni-MCFkKrRRu6^-8NOU[>S_;TI`s%M/Xqhc>IJUVM`t+X.'qI7Q[J$W$tt2e\HbZg,4^ZZ8?4UK"fq[LWW`?fo/*TKVX>1DbIn[S'Qf,]G]7jpuPJ),[#03Z3d<A_'SmLN9DTO_):4CW==]hS><\@X/n?h0E%P%JdS]eDNHOeo[ZQL?oKOSNg]=DHaQT*aF4J&W`'ANLcW/FS)saSg\%csBj)Vn&)i7nOa.UF_VjO'I^5YWLcUQm#A9>f-&$:]k3+?2cai"UlKRU?CY`'P-FWa/N]E#4`m,6s^MAIhs4W9:&FQ!lA^.K2DaSqH@h)4X9a!!18,nZ'7f382RWGC66q6,j+)LCBg?b9>ll%[6/lcb';YR>n;lYTSRR.2&8u%"ck(`Dd)=0$KQ';,E`bB1b)_,\9`4IAe%Vl,Eb[oP/.lBm-;"4dM8@3h1F]O$E'q$9-4E=$T_T8uC\"Lr*K6P5l-.jJY\RUm=mnc[`]TO-Y-'>l'1QZ^d*bKXWLD(r]Pll9q`-u9EOj%tN:8XHH._2d9:U>TEUKpAT>XuRI6CsFu[?fRnb#VPr46PJ.]6PjP\A!1(1Y[>Z%],uFA>oaURXioT]KE7@WF*!_9W8\W%p,AfJWYZ2kUTT.<'?)L)+BI[n;j(@7?_2Eh2L^_p?YJ6OiAS[Xltmcr*!#-2`U^P3/b]<fnU[L`l,EYnEs5.S*qXE_:4imK67uV3H^u?S"RM4L>Dj,#0(;%3^mXBDU1q*<;_ao^.>.,;h6M&[5bP\l/Un<@m/bdh@E.]]ta~>endstream
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1762
|
||||
>>
|
||||
stream
|
||||
Gb!#\gN)%,&:O:Sm.dDL,La<hroRG5bW(WH.-ZY%3Y8ir+Ar=MJ<JrJIX;kV,rq]2;lScr\p5P&=I_O$p4*Y*oPjAFIh$E]K[Vji3%bUei3]R$%'SW)GkC`m;dS/F=Im1\GG+94<j-ALBH6tek<I/ZBJkF5ceqjDpV8@M=Dl1H[b>,74ko;7@.c\S=3fd+"/?Fi1o7gQ1(U?=Ek:(.0V#uKG;K7hH>M1%*38Hd4_M0%FhV=Ndl1SnX6#XC)+%had`4>>IC#1C9G&WjhHjLDd(_+s#p\'h_M>Hk:E^:J+[D-fF=")N>Ocjk'o$nt!JZn=+L(gT)j+0]82!KoF3J@sIM((+7$oB[-H$abSNs:Zl1?#$Z5YIk0t]+B`e+V'8nS/O)O<q89Ei:tcN)u2[##59a_.<UC4t1&4N(p>E#kC;A'C*tQAUnr=Kjd-2utee\a5dA*P;&Uo_*Saa7%8V:SZos5P\Nds2`3!HSukZKI;!W8k'mg',"'^nmeO2pFSAF'P0\JdDqd+GGlY&"X^k17ceL%@"$eU&_;5dYb/7dNL@WaS6]BIpi8/QM.p%2_9=7]]Xo(K.`DgE%2"%f(6n/la[N!&oe2/c$LopTf;$*.\qVYlT0_h_"`[>PI*Dh75e3f09dQCl5ZsrUND3]R1^+ZS=Cs[qDK_oZ[X7_!'p'Wc-1I?V&0d76ob=XdZ%&&@hOC5H(o\Vr3M_P4+a\(8[jrN+h.IRrDa"_!`Zc_>"7ohBnNWq;T1L?il.f(:R1dX8(7L?)/8C>=`+=(i-7H=<-="cIf,pJ=0hFjCbSOpo:BKr?/`^YrNDKu"Pckc.XCr<e4JDjqS0/"C@M/1l\66+O&rU<71s.A&KJ<cLH&-SSZO]eWVkDJ?LoK3+W3sP?MqdD>C%ZonQh#%*,VG5Xi/)FnpH59R*D3_#DQU,aA^h[aRjVD!"'V)sOg%)_;.MXEo%K+L,qp[A=:u=FaH.JA.B.rn5)^E71mB35BPFlfkKtdf4rMSTkrMJ"+PbnG-DK)EX*85i7En"D_hAAT!+rN+&C+'m<@VFVY#Mbp8`nRO6Gt+cY;/.M=pjdf1KoS%p%NH3>Q*Q]TRdE%nu\!FIsOkC:nq)\1)#54"_S-A(%Fk9Z=Y;XY74Y&0qJs3H\C,;*BM_Wfg8*F=A/`N`p<7.YI.<8s(f=n7`A-B6>=Oc"U:b`d!58FB@6@5!Vg+fVppR\e-ZFq$6),R%9bV"au:q$Z0$qCnH2JM;SAR(TWoY/O7AEGBG7rD*]g:&,,pnTj8:@T$C4"JVWBo^OkZq7SpMa7O85=%!Fn\HW,m^$OE'$_Ea"i`8lFP&('39g1%ud$P06YiY(3@5Yn\teI$PIsj_'1)\=3S#EKQCH^aMF<lABN&1\0\fM=<=O>e>PE<H:,Vdj_6ERk?VB\1s(716_fmCdVA:%enmLBpV/)2WP:>4>#+Kr2tHeP'lF:2mMF,eKD8CPr6?t^,tKGH'Z1_\N$8dQ[Eu&M$%lqdJ]T9?@VEBFLUp`LSF3tl/RaX^*W5<%HE5IaU_0T_&46-5)(HtC"\+\5PMQI0=T!H>'oZ,QL)@8:V:2O\ITh%S8?5tjj$emEd;,Ds2U[l4^IHIbZ']?X%&?^d#$*4\?kS)I#1NASbQ-W3$!"Q<@ZPOh-]NgaI_W;P2h.UlY/8cW:9sQ.I1JK:ocPBl'Ju<Z"L)["Qq,O1d36"<_5(6*@0khm@"2E-,Zo.*KXlJ>.Kl/>0NDsb+BT9Us0peKZ-%@N53iUN/Y#(~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1786
|
||||
>>
|
||||
stream
|
||||
Gb!;d?$"^h'Rf_Z\Dr@/1e'bQI/0m!:=:$63&O9>3m;gaF:eo/Y.@e&j+mX-hCJ,-@k^Xp[kY<GQ<&A,gj@=0].VUsjVGUd$,E#O]RK0ulToFlDVtZElb[6%jKB5@EG*11)Ju\F6IYOr<kT9q34_cch:8je(A&4Xc@]K=ot[^"iuE"-D'Gpk&]8=ST"fs8Un"^"V#hjdi177Do)\c4E<Xo!&V80!ms.W`E3?U#`?9Xj-lR`S<5[(UYdjAI>Du&`C/ei-#[q,$0ar2fTX+JM_AT@_;"&%qKYeK<LXLe*;](FSm[3,dFI)Y4*$=G<!$mPIYQ9U@qYUNkhbA2/Xan5;?j[7j>7=`t3gLq@gF--P0rDInF1BY<Lh;=.Kb5mp2jbTs<\rBnWSl/lFYq+mBtBS.J?c]2;_\h$(8/?mMqhjsclX3Xns)l[giG;N)C?3krOU0:$APpl6]eE/2N_8M\-pG_=hk3**DCQT#W7s4>H&fGk;t&eC!$bJNQpak'pm+sehAdNm]iMjbuK]3Y8Pj+nQRlc:!pBkbPl+pICInb)5Q^^Fk:K@)ftDe_nGs:cQBaSbKd<[5Y92_=QBJYQ&?o8ea:+&$]ZiBQt[e?`UtsS#R;]Cdkt@<,&7d.caWiF>YeQ&<@k#/)P*[I6*@M,Dab(j-G*@/<&d?W8ql$'bZKlV$qN-/.jHO@rl[H4poW]]@m/G-_!$j_.R=]Fd*Ue>&e*20X#5_gZ.8'nU18Bt9aeVfo<!*pf5;aQ*tkkdL-qbqM`>$$OPo!^?G1>SkS@5j^1R04;-SI+HAVtqE8X0)WU^,c?egS'Xd[$SC\g%KZ^JaWG.ON]Y9;)rn\-hB@hueXgj7`eW2)J@IpR<UFtQF)8P!X,i9#iR`E.n1&Q=_c)+oR<BF$CCmPU<43'Zir+EbHm4a%!=Tct^/;hSE0(aC0SK[a,4HXg_;hAHXAE%L#d<ESHU<q\eE'@Kbdg^G9<KhMD>-*K#@Pqe]f1*[PuhC@hKn(8t_\R@i&F81]+807%>ppZt(>!$$LGC,Bn*2M>HH'H<^O\e\\rKStl+k@iN"OCj8r;&?UMtSsT]DZUErdF/^p;k_oi/ool!m;7W)9dcUfE>l)`/(tI<N!3hQ3]^7Gr)`!:9gU\@HE^omp%86/U!(.g$9H'!;!S63Hdi7Z0AhCW$XL2B=)P6^#AA2).[E!#e(bmm;0m:,0Q&7R(eC8R.c7`=%AVg.JM@gIrqYBrX%lChXpd`-L$AFqnPC^QUB:d0RB=a!M'4IlI,:lFoVJgs7%P7`ds5S9a!%))5b\)e&5gpnD1H8g%:8L#9hYfqT088Pt)n\@1h>[Dg_"Ur^MjPQokkdZgHUrg0o'?OV+A1W0tNVS<:<Yh,lLj<$JKNUD59.5@rs898*"Sb1fM)K,j>>+.TH\9t&hmkn0C3&f9a'nYY]]O)AuAV0I2=dqNK_j9L@c4M&/P"@>Me.#W(fG'/=tnUfI<7]*8AXh*gBAo,<lFe]:==U@&t%_Ui_L<(C[Ld?uf"(1+7,7Qag>2Y9&(<M+^^M(8.W'rWULh0No$T#Q?]2Z,,h/V+R=]]:S8^0"PA>S_AZemCAJ,-Q2U@@<+8)^n;T);8j>V1uXaCu(P<^ndoH$E0^h8]2bs#jPg21d"ef'k+=T>[&9NkFD'M9b;'L<"j\b9:$2fJHY:3Qm_u+-V"i"n>;@eZ#?]NGk+#VK^h^*gO_?AX21<A6\*ja3H]Qa<Kf>WQ8e-K301XFSFujB9E_NTF=D4=aqibLB(5Q3I]>RF!Mi'i8e6HqoMV~>endstream
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1796
|
||||
>>
|
||||
stream
|
||||
GatU3D/\-!&H88.1!57b]bUjf?qa?rQ)%,M]sLKQ/sT1oWl6$KKsfOA?GeWBm^M*?g,LpCJ1bht"3eqE0DiuGK`PJ^r>Xo"B+m88E5D3TP(FW4!DG-)mH!L",I`pH4XV4r@i%U@kX<<S5^N(1j+Z><(kUd<"+TZg1\T'(W9f=V\t,<%-98X+^l=9\cT::G+H!"[C*@XL&qm)j)&D]]=J8ggH%O!kR*9"0Wr_g[Xt>1@nR)IG%pk7%SDl9KXRh!_mWe-&b+NtV("GXgVpehqi!*2?!gq>^T"I7q^272X?4Y?V*"Z,H&VSAQ!@nioS;IZ\>S-BN.NrB&bs,2jj7>S>0+o<^/JTZ_'=d_J&]h99=Kg;\obN-4N4pk%\3N`f`r*)_5kMtX.FEX,W8U@+"BC>E5mn"UTh).$H!EE%8et6;`X9j\7GgsV8/<@G*4KJB4_l7SP;'o='MXH-\5,HA`N1d]_<23IPf2RC86p.8UqV%<SXHo0>h:9kZ3oR.q%Y;g8RE7m47K6N^7\#rE0/FPbBO5[9i20',\L)N/KoU3Hst"_QtTKB(Zc!L7d2BBpkod@lM>:%j8PNfs%9=1J$Mc8n5(@+&:PBOEQtIj4B"pp=]DWb6:2qk:NC&6Fl7ua'GW<g0+C5]n;;*6m'%gleR9:Xl+Hs\R7asn.2Sl9c3:8JX576ld'A%pUE4%,hig\s<D7g*3A!m4\r-XY@\G9<;X'Cbr(1^Chic"&[oaCpcYR\&j=dDlF[r+!]#;1_eKK2ESobU[2Dpn2`uY;(>uak@1Lph-d@t3OD7n@(]E1Ht/8a'rZJ,T;db?:>._2QUoi!#80]hmY?S/<i3)qhC]P&muLk;U(AVQI(bj;DK1,6/`(mR,?jaq,$j@Q203;]p<njCG._P'QQG(IK&'6rf8\WF5EA\u7p3u4.'"BIT]%`9'8Ijg,ikg;mdj>p-pNhB1NM'pbZaf)c$11@Oa`Yf<)..lKWIB=4eq^m/Is'^0mmPR,\)V#n@_:C;iYj4FnPMJg]B>n[6FsGZgFpRNWQFh@;jA[8a)ti!Jo7G=,#Jmn`^Ffqi6d#G^:`*=![9j`iO165,edg)Bs*hW0&u1$?]&&4:eNV<CkF^_G<Y[(<iGY%a\sjA?h$EiM``B\jc7?NA*9+HX]O?C>FPc!Tor!j_`Y^L`(YX`1/6f9jonT\q'dflL5t]1Qf]2VpngtE$(k.&_ZW"-k48]`@_9b5ja7\ooeBZeW1"?gDgjN:/eZ7.jZW*2:6)i6bW))E@hOU@]D2PtdkLY1]4apVk8]./8PY04Uf)5c$D_a[Q%BqgXFeS[6QUHN2d7X*65$^E4c!`(Im;o4>]IL(((Xh9@.7h@U`ud5u5;L(<"F8"ECfdPUlIO6`6gddnZ<rrQb?Gh*C\Y]&)@]liP[3XAeu:+o/p+hBM9]YZlpeDbD-EBnBFhTpc*Y$6&lm5XMIX8A`A<GhAr"dTnd5P'hh>jY54T#>ArcgS^Ke5?V2;G^9V_Kn=Ao#o&"?K"\%PIEEr1fSgmPVU:6Q\sM[K=Z<!o(\)lClQGYbO=OlJonkL2ig7T&*Ui/EHpahX6e8'VZH*TcJY<+k%p\@*&`leqBSJJ4'"Fa$PL0+&_Dj?FhB*-;6XIR^)tJdsg6irUj4>].'$[X7WPH11cq<QO")$Je78fuHCIcnFA$krZM@@6VXUiC-d-E:r>p5$Jl;?VZ(Q<gJgs,-NJnrAU<.E33j1/%"!`BiC_[`ZpN.AAOW,HZ/eS6;6#il#U$sko:Re?aF$m$X&cD'"bJRc@3:`^sRYG>7/g~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 19
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000122 00000 n
|
||||
0000000229 00000 n
|
||||
0000000341 00000 n
|
||||
0000000446 00000 n
|
||||
0000000523 00000 n
|
||||
0000000728 00000 n
|
||||
0000000933 00000 n
|
||||
0000001138 00000 n
|
||||
0000001343 00000 n
|
||||
0000001549 00000 n
|
||||
0000001619 00000 n
|
||||
0000001915 00000 n
|
||||
0000002000 00000 n
|
||||
0000003888 00000 n
|
||||
0000005705 00000 n
|
||||
0000007559 00000 n
|
||||
0000009437 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<b8b153d24f81055630bb0ab884390705><b8b153d24f81055630bb0ab884390705>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 12 0 R
|
||||
/Root 11 0 R
|
||||
/Size 19
|
||||
>>
|
||||
startxref
|
||||
11325
|
||||
%%EOF
|
||||
147
Les15-MCP/Les15-Huiswerk.md
Normal file
147
Les15-MCP/Les15-Huiswerk.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Les 15 — Huiswerk
|
||||
## Voeg een eigen tool toe aan je Polderfest MCP server
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Werkvorm:** take-home (±1–2 uur)
|
||||
**Deadline:** vóór Les 16
|
||||
|
||||
---
|
||||
|
||||
## Doel
|
||||
|
||||
Je breidt je Polderfest MCP server uit met minimaal één eigen tool. Daarmee laat je zien dat je het patroon van `server.tool()` zelf kunt toepassen — en je geeft Cursor een nieuwe mogelijkheid die er niet eerder was.
|
||||
|
||||
---
|
||||
|
||||
## Wat je gaat doen
|
||||
|
||||
### Stap 1 — Bedenk een tool
|
||||
|
||||
Kies één van de voorbeelden hieronder, of bedenk er zelf één.
|
||||
|
||||
#### Voorbeeld A — `findBandsByVibe`
|
||||
|
||||
Input: een vibe-omschrijving (string, bv. "donker en stoer" of "vrolijk zonnig").
|
||||
Output: 5 bands die bij die vibe passen.
|
||||
|
||||
Implementatie-hint: gebruik `searchBands` onderliggend met een keyword-mapping, of vraag de AI om de vibe te interpreteren als genre.
|
||||
|
||||
#### Voorbeeld B — `comparePerformances`
|
||||
|
||||
Input: twee dagen (`day1`, `day2`).
|
||||
Output: vergelijking — hoeveel bands per stage, populairste genres, schema-conflicten.
|
||||
|
||||
#### Voorbeeld C — `recommendForGenres`
|
||||
|
||||
Input: een lijst favoriete genres (string array).
|
||||
Output: aanbevolen bands die in die genres vallen, gegroepeerd per dag.
|
||||
|
||||
#### Voorbeeld D — `getBandDetails`
|
||||
|
||||
Input: bandnaam (string).
|
||||
Output: alle info over die band — stage, dag, tijd, genre, eventueel duurtijd-berekening.
|
||||
|
||||
#### Voorbeeld E — `searchByTimeSlot`
|
||||
|
||||
Input: dag + tijdsblok (`day`, `from`, `to`).
|
||||
Output: welke bands spelen er in dat blok.
|
||||
|
||||
### Stap 2 — Implementeer
|
||||
|
||||
Open `app/api/mcp/route.ts` in je Polderfest MCP folder. Voeg je tool toe onder de bestaande tools.
|
||||
|
||||
**Template:**
|
||||
|
||||
```typescript
|
||||
server.tool(
|
||||
"naamVanTool",
|
||||
"Korte beschrijving — wat doet hij. Goede beschrijvingen helpen de AI te beslissen wanneer hij de tool aanroept.",
|
||||
{
|
||||
// Zod input-schema
|
||||
arg1: z.string(),
|
||||
arg2: z.enum(["optie1", "optie2"]).optional(),
|
||||
},
|
||||
async ({ arg1, arg2 }) => {
|
||||
// Jouw logica — meestal een Supabase-query
|
||||
const { data } = await supabase.from("bands")./* ... */;
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: JSON.stringify(data, null, 2) },
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Stap 3 — Lokaal testen
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
In Cursor: stel een vraag die je nieuwe tool zou moeten aanroepen. Check dat:
|
||||
- De MCP-indicator je tool laat zien
|
||||
- Cursor de tool aanroept (zie tool-call output in chat)
|
||||
- De output zinvol is
|
||||
|
||||
### Stap 4 — Pushen + deployen
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: <jouw tool-naam>"
|
||||
git push
|
||||
```
|
||||
|
||||
**Vercel deploy't automatisch** zodra je naar `main` pusht. Wacht ±45 seconden.
|
||||
|
||||
### Stap 5 — Test op productie
|
||||
|
||||
In Cursor (mcp.json wijst al naar productie-URL), stel dezelfde vraag. Check dat het ook live werkt.
|
||||
|
||||
---
|
||||
|
||||
## Op te leveren (via Teams)
|
||||
|
||||
1. **GitHub repo URL** met je nieuwe tool
|
||||
2. **Korte beschrijving** (3–5 zinnen):
|
||||
- Welke tool heb je gebouwd?
|
||||
- Welke probleem lost hij op?
|
||||
- Met welke prompt heb je hem in Cursor getest?
|
||||
3. **Twee screenshots:**
|
||||
- Cursor chat waar je tool aangeroepen wordt (zichtbaar in tool-call output)
|
||||
- De output die Cursor met je tool gaf
|
||||
|
||||
**Deadline:** vóór Les 16 (volgende week).
|
||||
|
||||
---
|
||||
|
||||
## Beoordeling
|
||||
|
||||
| Criterium | Punten |
|
||||
|-----------|--------|
|
||||
| Tool is gedefinieerd met juiste `server.tool()` signature | 25% |
|
||||
| Input-schema gebruikt Zod correct (geen `any`) | 15% |
|
||||
| Tool returnt valide MCP-output (`{ content: [...] }`) | 15% |
|
||||
| Tool werkt lokaal én op productie | 25% |
|
||||
| Beschrijving + screenshots compleet | 20% |
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
- **Hou de tool focused.** Eén ding goed doen is beter dan vijf dingen half.
|
||||
- **Schrijf een goede description.** De AI leest die om te beslissen wanneer hij hem aanroept. Zwakke beschrijving = tool wordt niet gebruikt.
|
||||
- **Test met meerdere prompts.** Sommige tools worden alleen aangeroepen bij heel specifieke woorden — probeer variaties.
|
||||
- **Gebruik Cursor om Cursor te bouwen.** Vraag in een aparte Cursor chat: *"Schrijf een MCP tool voor X gebaseerd op de patronen in @file:app/api/mcp/route.ts"*. Meta!
|
||||
|
||||
---
|
||||
|
||||
## Vooruitblik
|
||||
|
||||
In Les 16 (RAG + Embeddings) ga je verder met je Supabase project, dus zorg dat:
|
||||
- Je MCP server live blijft staan
|
||||
- Je huiswerk-tool werkt (we komen erop terug)
|
||||
- Je `.cursor/mcp.json` correct wijst naar productie
|
||||
|
||||
Veel succes!
|
||||
118
Les15-MCP/Les15-Huiswerk.pdf
Normal file
118
Les15-MCP/Les15-Huiswerk.pdf
Normal file
@@ -0,0 +1,118 @@
|
||||
%PDF-1.4
|
||||
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 12 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 10 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616091046+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616091046+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Huiswerk) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 5 0 R 6 0 R 7 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1647
|
||||
>>
|
||||
stream
|
||||
Gau0C?$G!l&:O;VR,]RLJrDmE]\.;Q\4u;]KK:bKA3kiM,OMM3PrR'mn*EXPCiIaX)e0#@*UV)-HfmaK%-cm-lQQG@T#0T9(L.KJ95jt\JE]RK2a.'4b\\mUc\X"*`.a<i3TG&qd!`:fG`aol6?)kmd%>R@9RQF:.QO7eh:>:'du7OVi/Wd)cT:9<_ue^*AtCr5%j`keW7Db=I=UQ8`r"Zc3Ym9.FSm6g9a!rqU/B8U]Jd]TM$Y:35b?>&9:=33JM@09#bPWGUKV-#iQo!*TKlMN3dl\&+7DYn,Ri]r,7]ZV)pQ_tj*8h`8gl$pIUde.cKoifY&4JG7DT@k1#-/jWQ[.4)B-N\7_QDr736N4*,b;uh>JpQO9l\\Pr\"l>2`q5\A]8YWKJ_mgd/8(%TY>PR.Y&tjLR`rj?ZqKfu4_l]o?ok`@2UJ\5>-T,N$-K=HPul0_IbF*-a-[+rjB^K@e9*7Ug[)UsS#npgdUd1]%Z:c>XJVJdG+0/a)OVc@8?O.j@a'W&c?;L(*=k"j\#Y@HN?JH1-[CJK)U.L'T5<G-c+WQK%c<MJgb.<`4hBl%UNAMUNTW@iK$`,3_%>q-1`$&00_]R(kW=o,FQ"=I5gR8tMOf"o5N>O#Dt1`2/3tV(/:Hfd8\E)ha0gR.?%*MG=X98"aA?OsV+_mh3mbf\<XIl+i-7EdW?V]dT(PijK&pQ-#@2f#0fo9,+;A$R1)X]3BW/rqY>rd!?4="Wpt;T/Ff+g#@C=0oEBXer\B)OPi\Q4"VNH]Oe\*E:4<48\:1Y;+upF8ZCj.Ug4!ON_0Z<YRba\o-QLu$`1PC".t/-rW$>T$^eOBpAHm7\>iF']&Y'LAg/k1+7K"F^RgJ+V^g-r\%9d*!-]sa;STIn<TJ'@1TJEO5?(UdWMUG7d?d&@.gO@0.Fs.hR22#dlJ)Ab?7BS$#/3YPG]H![E8[nKG1.#bjs2jGk5<&CDNE7&cCiJ+%[d.PB^^o;BjXA'fkj21aRggV3HY]^.(09AWoWIjFbQg6atdp4;lk@ESks-ZD;d:LXmpBU<BS?)Sm/-!PY*Zr(]dt!K$<H%dpm.rM?-_[OKPqq9+)(t.-q"h+8uE=>rV=Ya=Ls25>e\`I`oY2M_5V^qQHTjQD!)2O,04g)meCB>%73h53J\j5TX'Rc`QmE8KFQYmoJ6+^ai!?$`8n[F&I4U.>0nG1+uSG'ob.02+>8+OYR%q2HWMT9_h#!;Qr+Z)-,^66Q;"Z1*_Lnn"om*7b`JO0DM.H#9c]l_333%7]g]h='Ih<.E*A&H(\;`Ng3<&hHbin_L==#="L30*TdYTMAj$>_j2V^8f-!jd8*%+VEm@2D$n5kC<"^TLqAl?onXR*"CkSmmh94<2*u=46(/,)<aN)8")ao/d.K]rLU^]Dh$;('ThF08R(\k6BO`fb;h8,I+?(>!G"4bP9:UV2>m#:LRNJFi_p>=3>lYDQQO6[<[4W%rn>jO/R&=tQ]JuIb9hQe=doKY$YC:=B?"9'R7?/^ELh'gfdjDRW*;r]+HtV=-C3nS\Md;oEh;O8$rA66Q3?C?q;#@q:3PWcRq5'7N7RnPDE.7-kH>.ji@%Y)G\UEPIrSFpO3LNdiMX.Z&6[q7_L#['gXS4R!VasoFh)qP%r4q!8qb1\p~>endstream
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1825
|
||||
>>
|
||||
stream
|
||||
Gb!#\D,]1Y&H;*)Z/[LF.3*Q0GM8kAVG3L9D3Dn5AfBdK+WjfUDb)6SaRJ*!-eXZ=Z"7U'e7L8r[(Eg9cQ3KCITc=__uN"ri:&/t`aNlhbTnN/?G\@Z_>/U5.;_grO*_9dl4Qid#PeRW;<HO?g8djUO$e@W8-UE.KNktS5s-hALUaMM[VO:+FK&kgQV<ar6oL_cFh0*kMC'/sCYUV#i>GB:W`cZb9hil8$g37K'/^[b.k%EGL^&etY1n4r>@Fnp.i?W6R0XlQ?'kJk2=567Qjlgad$OTecDfT9J6.#c&pNSW>[Ftq,9CLQ#5fVKZX=E`W0$gAk!Z%[jH%;7RDWQR'-!ddUn1`Z!=TVbiccT7+R<ur4"GAX@UTsPXM:UBP&Mt1@$ZT>7@9Tscaa,o6GYLD$>/7e^p@!8Dc4[[AhqNuJhMS#%^hBH/.,Xn&4n'moPeVUH1&h.:Hd49=lnh0)W:)rr_B@&-<[;X:b*EA*XNV&!'4^:60+s1#d-^lE\tfYq;XZbR2AP_H!L_]99;^_qkF\>1fL)Y_gSKPq!b%C-MfVp7't,Aga/P%Y>i1&,A.L[%UJ<DMT[7FJ+T_GCqnC.Y5L(;HJIglC(Y&^B%OXJdddI6oJ?tESIi&jKb=tgN:5LV'??i/#Nqr-0=M>`$nRT#ci?A#'FI%?<%%e!n-Hsd#Se%HRH,Vhd@J0bO\CZVHLPka#U-ErptbY'YX]OrBc,)a\Q!.3oGMRo`&(bd2f+b68g`g/5&=l>*25B&*ELBp:m[S4Jt\A--qjhn3J&&c?i@6'[IE8*+)0d0+Xs1"+#jJ(0A*-jCr:6S4+6JS0FZqXS@:'$4oXKoA%Ctd4/Up#Wi>r:=8&-<E()_P's=&OZ5EE?T3ppH\RMF4A^014Nl>'We#J[nhC=Y=;5`O!oLrXn.SNU*ged!R2:*qHKjOT?n7M/BYTa!bLA8EEC=k,4iP!5d]JM$kpU&nQ>1NcX=#]tI(.2DC+B\8J!OE^=@hs$^s#,NN[A*?4;MG'p!`-tO8Fn(A@+Ur_OUH.9dnmnd65%H36@q['\4TDM&\jDsLa5bB65>&!1Du_Ic?$?I<@iKrX+BBgl:S,/.j$X;n[1pqK@?3#F2tY<(j0-sY:n#0f)!U12C`Dp@PR#Nc<b^8jj+QgJ+EFY9W$J"<LR^l<Gm]tir2rfV3GqPU(:mD]@WV?X7J`?-C%TS$r@4jBNqlf&;V`dZrB"Mi"uL29IPD'c0>NjqS"[iRWB2j$a_rGf!]n2W@o1FlPAW4DpN%OSmRi'$qp(o$q1r.Qb0V<S/1D(pXNKTAJ(7`HD=>A/tj7L9rHRr<^_oOc&1rCbsTslRhaWaU[;@ugrODm$)]b<M<P_q*@:s7';TW,Q\--0-PDn!Hp?6C@o[1<D";h\q^eH&AXG?':r5rXUriEaBj"M+>Ci>[K$cZJ#DaLF;LX6fQR[uK$LoB=mFrE.#a&YdM[EdDaC4nu=M9_`R&mQ+r=snX,+)fbiAST.;+MZWV[,UN;ue^8VL=#)[-D+ji/e(U#$INmb5dSOrMhR?L+;p#D!\cROHFgNin6#UUD>hp<UO[&3V_pdQZtisOaS^_A9<FZ]7TbeCu^os/P+21$5X=-A:CD!n0c[T"4sQ3\=EW?lP78(/`(@bmV8MBD=jl&T7nMHB6e<@rb)lNFM*Ef/a1X,*1g*n@GH.ji5V+6-aq]rGke,ZEi`lN4'(.C;#OQ=j-_Gk!COP$1,0",jp8F"Mnb(QHj=0MBC#Eo<7]:d7g>9TD/$(HAE&[%#L^r'\k*OT]5Nl1haa(.1-/X"Cbp@<oANXi,Eb`Mf\dber!WA-aT_~>endstream
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1736
|
||||
>>
|
||||
stream
|
||||
Gb!SlD/\/e&H88.E@W`YaVMml?7Z6s[^PM5(+mlk7Q5GG#?miBg6/CsP`9h<J)=+dD)Hc8dNRfi`GlN]mW]92H1#>QICs7l1W]Zh%,p1n$C&AG!n8S]$t$iWiQ3/h['^</'#lK"0YlF?d.d&j]\Tc0lflO66bi?1[[rP1op\GW$e`<+J9XrKM]%W/F'l$_\m?8W+\//@jg/8lDaXh_;]*efGif(?`G6.*UElKhU7GE5I.)&M5BZs`aQ<OrXi?b?3Fp97\<\pIjnF+3@U]k8f=fUY/M&GSWT>Gg,fVMOpm-O`i+]$IdP)D:(qZ@&gDc&R=$db]ZP'p#"#*@@_WCM`T*BpU'Bh,T2`qV;6@_Y7&;@Yqem.&4Os'VYP+sUmjI0R9ENWRN=f!n3]U0&.6n&IF.\0`[ofgOZg5_^=(7;Eh:O&1VWbbhkdHQ.K9g=Ln%7m<tOXEYVXmh_KajGtPfrAMc=>pu$*PMik8[,O'[hPEGZ4K`aDH>JD`Q/EMpX8P'(n`&?a[0Q;;5=9$5g2`rqt[R$*/fX9LOi[b:4j[$CP1C1=h*=paK:ac\3:K3Maq/=e%I89'?s&"DAB[#chTd"&LC9g7rXLI["-793d'&s#$J4KL;Z@pKc]#sDkDp)UQ,TG*e+(OW*a^-Vcn,n:!)X96\tc%gd^q-C2&Bf<Si;?A'"S0`4d=smGCls'h-1<VU!-c@ePH.a3YDe)JhI6R)hT$U!n`aPr'9<=6P@b`:7Qk)kDSCdt6=HMdpO=rNkGJh3K3,nX[XW!\&M7&o<.>QSI'VS6Pc];Fm&',&o(j`>/Hdq"#08g(h'h8bY^a#+N95^e>(C$_Bqcj6NU]c<E^Tp&;ht%UOJg^C`e]LVJlSDd=FjN`9*c?ODd<AnoV:"sYdo"cbNZ0M&7_b%(4TSdrOTf*5"R,-0gAJS^'Z]%n(lb=`hYm#ahu+4flbE^K7.?aCD4UPO3:GW(A*Nrg1AIFnPV4[1(j/D$-32^EBmAoP.bMLEeuiF'IpWh%I&63l@r"#')G%6di'LU&bWFc_;S-fQ-N%Ktk/qWo]_*P:(PWe.VFW=tOr7\jRqd628;3-B@%<?d,r+_OYT`]27Y89k4D3LeM&GiP6*PPpIoK'36uYV=i5$]U2+E`Fo2!`#7jaoYF,K#uG5<GWn5o?-)0J;c;/`1h9;JK5,^8RpPs`eso[[m'#c'*4,7DO@a54tQMW^/fpD\BF.*RXJo44k+D^f$HeRq@?\NhQE:3(@VeS:p(PpXsj[j3]3=BBWkC,b@snLVT-LGM_(p`7V=1lf?fB`9IUT7OeRKn;DjMp(s81,HFgdlE%O)MYrMq6%3Kss^+5MqS!mGRa]NlVDls.A[1k^fotBL8lAc<2MC$kt4_;73a!I6=l7r@OB8up0cijIX^UeIHVB.7l]cfm<DRCsa2AGYu6tYGAQfSB(j[Gc*(E+6s+P&Y9,CmgGrmgB`GS\4C$tF3o=('6V)glO)G`TsHdZ-+D5<cff55cbrSW5]`MTs9%h;d\N`1Q.$OZFL?306kF47=_8L9+-7=&m?_kcTkW3'hs#Bk?4t_Fd@=j,S=>l?AB%b9mW-W@QB0n9e@LHL5)JR9Uj:1+ZlB`hHKR3Y?l;T+,1Kf/U:Pg%*p9VT_nU.:B<!A0"9NM6,=ZoF!bZmiP0c/k*ZO>K-/6MFE\3SXb_%R8LqhU9+n>90'o@;RfLkG0Sc3FG@*ZrbR$jGFp?VKXiOKE:OO&_)U$~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 14
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000112 00000 n
|
||||
0000000219 00000 n
|
||||
0000000331 00000 n
|
||||
0000000436 00000 n
|
||||
0000000641 00000 n
|
||||
0000000846 00000 n
|
||||
0000001051 00000 n
|
||||
0000001120 00000 n
|
||||
0000001412 00000 n
|
||||
0000001484 00000 n
|
||||
0000003223 00000 n
|
||||
0000005140 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<ac8544ee605d4b361a9cf9d19fa393f9><ac8544ee605d4b361a9cf9d19fa393f9>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 9 0 R
|
||||
/Root 8 0 R
|
||||
/Size 14
|
||||
>>
|
||||
startxref
|
||||
6968
|
||||
%%EOF
|
||||
264
Les15-MCP/Les15-Lesopdracht.md
Normal file
264
Les15-MCP/Les15-Lesopdracht.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Les 15 — Lesopdracht
|
||||
## Polderfest live via drie chats — GitHub + Polderfest + Vercel MCP
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Werkvorm:** in-class na de pauze (±1 uur basis + ±30 min extra)
|
||||
**Geen deliverables.** Gewoon doen tot het werkt.
|
||||
|
||||
---
|
||||
|
||||
## Doel
|
||||
|
||||
Je doorloopt de hele flow die Tim in de demo deed. Vier MCPs koppelen, lokaal draaien, en daarna drie chats waarmee je via Cursor + GitHub MCP + Vercel MCP je Polderfest live krijgt — zonder zelf `git`, `gh` of `vercel` commando's te tikken.
|
||||
|
||||
**Extra werk:** breid je Polderfest MCP uit met een eigen feature via Cursor + Supabase MCP.
|
||||
|
||||
---
|
||||
|
||||
## Wat je nodig hebt
|
||||
|
||||
- **Supabase project** met bands-tabel (uit Les 11/12)
|
||||
- `SUPABASE_URL` + `SUPABASE_SERVICE_ROLE_KEY` (project Settings → API)
|
||||
- **Supabase Personal Access Token** (`sbp_...`) + `project_ref`
|
||||
- **GitHub Personal Access Token** (fine-grained, repo permissies)
|
||||
- **Vercel account** (geen token vooraf — OAuth)
|
||||
- **Cursor** ingelogd
|
||||
- **Node ≥ 18**
|
||||
|
||||
---
|
||||
|
||||
## Basis — ±1 uur
|
||||
|
||||
### Stap 1 — Finished zip uitpakken + dev-server
|
||||
|
||||
```bash
|
||||
cd ~/web
|
||||
unzip ~/Downloads/polderfest-mcp-finished.zip
|
||||
cd polderfest-mcp-finished
|
||||
|
||||
npm install
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
Vul in `.env.local`:
|
||||
|
||||
```
|
||||
SUPABASE_URL=https://xxxxx.supabase.co
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
|
||||
```
|
||||
|
||||
(Beide uit Supabase → Settings → API.)
|
||||
|
||||
```bash
|
||||
npm run dev # → localhost:3000
|
||||
```
|
||||
|
||||
### Stap 2 — `~/.cursor/mcp.json` met vier MCPs
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.cursor
|
||||
nano ~/.cursor/mcp.json
|
||||
```
|
||||
|
||||
Plak (zie ook template in `lesbestanden/cursor-mcp-config/`):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"mcpServers": {
|
||||
"supabase": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.supabase.com/mcp?project_ref=<JOUW_REF>&read_only=false",
|
||||
"headers": { "Authorization": "Bearer sbp_..." }
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": { "Authorization": "Bearer ghp_..." }
|
||||
},
|
||||
"vercel": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.vercel.com"
|
||||
},
|
||||
"polderfest": {
|
||||
"type": "http",
|
||||
"url": "http://localhost:3000/api/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Tip:** zet `read_only=false` voor Supabase als je in Chat 2 een tabel wilt aanmaken. Voor pure read-only demo's kan `true`.
|
||||
|
||||
### Stap 3 — Cursor herstart + check
|
||||
|
||||
`Cmd+Q`, opnieuw openen.
|
||||
|
||||
In een chat onderaan de MCP-indicator: **4 servers groen** (github, supabase, vercel, polderfest).
|
||||
|
||||
Voor Vercel: klik "Needs login" → OAuth-flow in browser → autoriseer.
|
||||
|
||||
### Stap 4 — Eerst commit lokaal
|
||||
|
||||
Een eerste commit klaarzetten voor Chat 1:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "polderfest mcp server"
|
||||
git branch -M main
|
||||
```
|
||||
|
||||
(Géén `gh repo create` — dat doet Chat 1 zo via MCP.)
|
||||
|
||||
---
|
||||
|
||||
## Chat 1 — GitHub MCP: repo + remote
|
||||
|
||||
Open een **nieuwe** Cursor chat (`Cmd+L`). Plak:
|
||||
|
||||
```
|
||||
Maak een nieuwe public GitHub repo "polderfest-mcp" aan via GitHub MCP.
|
||||
Voeg toe als remote `origin` aan deze folder.
|
||||
Push de main branch.
|
||||
```
|
||||
|
||||
**Wat je ziet gebeuren:**
|
||||
- GitHub MCP roept `create_repository` aan → URL terug
|
||||
- Cursor draait `git remote add origin <url>` in terminal
|
||||
- Cursor draait `git push -u origin main`
|
||||
|
||||
✅ **Check:** open github.com → repo staat er met je code.
|
||||
|
||||
---
|
||||
|
||||
## Chat 2 — Polderfest + Supabase MCP: nieuwe feature
|
||||
|
||||
Open een **nieuwe** chat. Plak:
|
||||
|
||||
```
|
||||
Ik wil een review-systeem voor de Polderfest MCP.
|
||||
|
||||
Stap 1: vraag Supabase MCP om een tabel band_reviews te maken
|
||||
in mijn Polderfest project:
|
||||
- id (uuid, pk, default uuid_generate_v4())
|
||||
- band_id (uuid, FK naar bands.id)
|
||||
- score (smallint, check tussen 1 en 10)
|
||||
- comment (text)
|
||||
- created_at (timestamp default now())
|
||||
|
||||
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
|
||||
- addReview(bandName, score, comment) — zoekt band op naam, insert in band_reviews
|
||||
- getReviews(bandName) — geeft reviews voor één band
|
||||
|
||||
Gebruik dezelfde patroon als bestaande tools (Zod, content-array).
|
||||
```
|
||||
|
||||
Supabase MCP draait migration → bevestigen.
|
||||
Cursor genereert tools in route.ts → Accept all → save.
|
||||
|
||||
**Commit + push:**
|
||||
|
||||
```bash
|
||||
git add . && git commit -m "feat: review tools"
|
||||
git push
|
||||
```
|
||||
|
||||
**Test in dezelfde chat:**
|
||||
|
||||
```
|
||||
Geef Maud and The Cathedrals een review: 9, episch concert.
|
||||
```
|
||||
|
||||
```
|
||||
Toon alle reviews voor Maud and The Cathedrals.
|
||||
```
|
||||
|
||||
✅ **Check:** beide vragen geven antwoord, en in Supabase Table Editor staat een nieuwe rij in `band_reviews`.
|
||||
|
||||
---
|
||||
|
||||
## Chat 3 — Vercel MCP: project + deploy
|
||||
|
||||
Open een **nieuwe** chat. Plak:
|
||||
|
||||
```
|
||||
Maak via Vercel MCP een nieuw Vercel-project aan voor mijn
|
||||
GitHub repo `polderfest-mcp`.
|
||||
|
||||
Voeg toe als env vars (production):
|
||||
- SUPABASE_URL = <plak hier je URL>
|
||||
- SUPABASE_SERVICE_ROLE_KEY = <plak hier je key>
|
||||
|
||||
Daarna: deploy naar productie en geef me de URL terug.
|
||||
```
|
||||
|
||||
Vercel MCP doet:
|
||||
- `create_project` met GitHub-koppeling
|
||||
- env vars toevoegen
|
||||
- deploy triggeren (±45s)
|
||||
- productie-URL terug
|
||||
|
||||
**Test in browser:** open `https://<productie-url>/api/mcp` → MCP-handshake response.
|
||||
|
||||
---
|
||||
|
||||
## Stap 5 — Cursor naar productie
|
||||
|
||||
Update `~/.cursor/mcp.json`: vervang de polderfest URL:
|
||||
|
||||
```jsonc
|
||||
"polderfest": {
|
||||
"type": "http",
|
||||
"url": "https://polderfest-mcp-<scope>.vercel.app/api/mcp"
|
||||
}
|
||||
```
|
||||
|
||||
`Cmd+Q` Cursor, opnieuw openen.
|
||||
|
||||
Stop `npm run dev` lokaal. Stel opnieuw een van de test-vragen in chat — werkt nu zonder lokale dev-server. Alles op productie.
|
||||
|
||||
✅ **Klaar als:** je vragen aan Polderfest werken met `npm run dev` gestopt.
|
||||
|
||||
---
|
||||
|
||||
## Extra werk — Polderfest uitbreiden
|
||||
|
||||
**Voor wie sneller is.** Doe Chat 2 nog een keer, voor een eigen feature.
|
||||
|
||||
### Stappen (zelfde patroon als de demo)
|
||||
|
||||
1. Bedenk een feature die nieuwe data nodig heeft
|
||||
2. **Supabase MCP** via Cursor → maak nieuwe tabel + seed-data
|
||||
3. **Cursor** → genereer MCP tool(s) in `route.ts`
|
||||
4. Push naar GitHub — Vercel deploy't automatisch (je project is al gekoppeld via Chat 3)
|
||||
5. Test in Cursor chat
|
||||
|
||||
### Ideeën
|
||||
|
||||
| Feature | Tabel | Tools |
|
||||
|---------|-------|-------|
|
||||
| Reviews (demo) | `band_reviews` | `addReview` + `getReviews` |
|
||||
| Wishlist | `wishlist` (user_email, band_id) | `addToWishlist` + `getMyWishlist` |
|
||||
| Stage ratings | `stage_ratings` (stage, score) | `rateStage` + `getStageRatings` |
|
||||
| Notes per band | `band_notes` (band_id, note) | `addNote` + `getNotes` |
|
||||
| Eigen idee | — | — |
|
||||
|
||||
**Geen deliverables.** Werkt het — top.
|
||||
|
||||
---
|
||||
|
||||
## Vastgelopen?
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| `npm install` faalt | Check Node ≥ 18, `npm cache clean --force` |
|
||||
| Supabase 401 in app | Service-role key opnieuw kopiëren (let op witregels) |
|
||||
| MCP-indicator komt niet | Cursor VOLLEDIG sluiten (Cmd+Q) en opnieuw openen |
|
||||
| mcp.json syntax error | jsonlint.com — één komma fout = ALLE servers vallen uit |
|
||||
| Vercel "Needs login" werkt niet | Browser pop-up geblokkeerd? Ga manueel naar `mcp.vercel.com` |
|
||||
| Supabase MCP weigert migration | `read_only=false` in mcp.json zetten |
|
||||
| Productie-URL geeft 500 | Env vars niet (goed) gezet in Vercel project |
|
||||
| GitHub MCP geeft 403 | PAT scope te beperkt — geef Contents + Issues + PR write-rechten |
|
||||
|
||||
Tim loopt rond — vragen gewoon stellen.
|
||||
187
Les15-MCP/Les15-Lesopdracht.pdf
Normal file
187
Les15-MCP/Les15-Lesopdracht.pdf
Normal file
@@ -0,0 +1,187 @@
|
||||
%PDF-1.4
|
||||
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 15 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesopdracht) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Count 6 /Kids [ 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1725
|
||||
>>
|
||||
stream
|
||||
GauHK>BALZ&:WeDm+\3M'MH17NOH4=3B9gfcudPA1DsU/E3LqG2PcOlrq<ZR^/,c4UmXfq?nAF'^\r=W>#EZ96]5k9!dHe\P7J?UKBWH95n'-P_.5V%0qbA=`JEV2Q\*I5ig?0clYr:HMqNgk7!CKDAO'L+rB\"7TMA[[Z!\KD#mSmV-<O\aGU>2=-Hm=5!Ir;2:V!sW?bl:<VCr[70DIO1Jnd\4/5q*Z-t&I&^r'%7g-M)B1A"#H&6TKOYSFAO3;@21^g$=kgVNn,jI\>OGQiH2Y:<%s=!I<*#lhWN\DK4Qq/;r+L/4M#0KohDJPoDp#qQ&)-!K<o2hd58T#LU=`i#SOQ3CDDI!@<Q,*YYEiZ(S,9%!,j#t<bWnZo4ToD-ocI$.-bGq1=2pm.fZ6e,'(?)6eg9V\bF+<GeOcSC<=4TPlHgkhJmfl4q^8n#%t@EoH719!_s,#Jo(pmJ%-p7c'aUC].0;c--^4;8[tR/JFNV*lJ`P.AeF-$$LaO?%SO-o=O9KCu.1BplBE;iBVJ']K"P9Y,04pZuJgfg--[a56rTOD-+SP,f_,6muFTb$4?iSYnc,;,ZGgLa7EUT>4GL;O+a0XC[QfI/FmL\`Q#m)W3_Smj\U5Vt$o@lCdRfitc8O+lHs63:Ab>-;t=k=YZin?^_$nEp\H_VnpV5h\Fk9N,]?q7(B2D6*GmR%.\T>?[%3)/XZ[@ltY8jQoYQ8aXH/R0E90;(Q\mf[>5od9876V"jXWeW[B:rMmVm&eNCg>D6]s_g]TW*R-^h@7'OVuL#DbRD2$qhPAmT_U"2?E%EBa*mT@.K@2Nl6nC3Wrc?&)L/H&t5\N91D),.(fY>]Mg]N._pN&@-)A$0se3C'S;364PuH)G`hGt)Y_(Ssl`8Z>&r@unid3`ns'DhQ`ap*/F9bg($ho/RT9CoGdT3+qI^A[<2dfGm.`T8eBD<kn\263"]*N8KXK-eI7iSooF1eZ%J1r,VB$lL`onqWmt1jSGLjJ"=!R@EBp&MEJom0WUEU/bbWH[8rVM0YTM0b-1cbT'7^%<jqua^FfY,%l*MBA'Yg5Aks`@EX,]^1S2jTCI1([0HT'+k`h$5VdaY!2=lqla\u<BCd!rhng&Q1B8[lJULhU<iP1+H,SC`P7KI[G,!25Z](:;S<43ji7<su9c]aj.]0FPp92RNT8.BC?(#b),Z$nmnjK#'$ZY/'cfq3--]0l6VkI\ZuHg)VnmMUGR9\$p#UAM:8[]j[C[.IC/?>YPKl/;Bcf3.;U?7bX+e(\"Z>H7q!g0)'?mMJCTeJF;DRic7HI0=Y)Z2O#:e0qhH$3Z\U:N3mqn0*U?Ck\4^]-'t!>JaB=M-Hi1[\HNlfSSGqNp8OM9TprFKPK#E-oB6j8I-mh(gmneij]BD^p('qF^1g^$O;E]WJ/TYiR"-W75RB<pIC`G`Z*E;/uBPnEAJ'u`cP\UNbq(Ygbh`u)F6N<Wg]8n5`+kfHdc/n9P\Hif^(VX%pFUn>i0eTr:C2o_;Zb]WBO=OW.S<m$+$c#'DKB.&slI-J`RN^E1Gq12=u:kF`K"pHU0]s`3bA8kW1;]Cgn.F?RP=(D&c19B.$:,^5jHk)(j"$+=MG=Y@/&:\"*jtO.PAmFN#Y<Kp$m6L;@DdD&9)/7,8WbS;6'Ch:8jIQN.WfQAD.q9(;7O]S+_n^L)C"V)NP_oKK]<+2%>SD*iBOD0I*Sl[+!PRo[5=i.#H0LS+~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1830
|
||||
>>
|
||||
stream
|
||||
Gb!ktD/\/e&H8h>EU_?"Bk*U-7(i`HL*T<FG28V,DMnN"&m;)K`Nl:3EjBlPIf#.gob,<%S76.:bVh#7jrNpPdG7/GT_N-p\$lK"*^]le"3+b/"_J;JL9KNWW.!%FHDS`?l#6lG*':o&3K5`8*0W[:[AiS587l&().4".E8H/BAlohRD9&gB!mRsK'!.u>Rh^iT64IZeBC'YuVc@em:)k_db>jgK-=P-*:hcoa=tcm--tbYRU/gGc`3SdMca6_Blb,E?f#@hPW,+'I.rX]W:Rc.KV\i)"`o,q:f"oJ'9o6QD2GTUtG)4$R2h@`2gpAUL/tk@OHZjLY1cW0nHiE;LSn2nfd-q"6?6igqmat5[nY//`?GZ-q4UTBu/U`9#o62i_:+sn"^H0('p%4;4q=3Qor/4&NBkVk"'b2)D&e6!?_P-`=:9k4=L>">:(bHl->jdW`a3pSN=SWgCefI)jA,j*EZ'5W;rQBp.(s"9"_cch\aeQ0YA@-R(`U-Fc-8>r*+2GP7RqUDN%E!t0e>euqbjf!?*%r&6nX`c:<9a8%BPrDTN0fX-6C>i0!;N]lhCUn8ltpRdKcosA?ll/tJPIY274P^G,U--SY$S>X]a)&0`scilCd#kp(c[I6\)?iG54MZF)C>?Y(R(mV$85g:1V!-i!l(:rV<n6rJeDteinFS%IofU9Mi"&k08:s9],/i@%3rBPr\c#/SQFMDYQ\o5H)f%fUT-ZT/8uCZl@/:M7*u&k\VpVLPMh3m4XYqc2j!9+VO8AuACrPOm%KD%0++WVXM7UH];*&lG,007-Bni&gEW!<]W`YpcCm.f9,>[\D&/$Z$`Y(ue7qf'[@3es10X/>[?2s.:+q.of>G,Z"sufP,$t`)_*efEE\3K^Er^'_f\]:p@ki^58LJZmjXNf#<3dpCI>jG%7o>MrIZ$>KUp`<#?R7ueXU'r5WZ(MAW@!(SPNYVbfgR*_LHiM#rV$3/q<@LC^/(Od\VS,\j5JJsd/:>s.4*UYN99PL<3ZoOFH0DT4A,NIDWXH_1?quQ$!-_@*+'gKlG2S<2dj'g"?mLL#+o*N@[9T=A(MK=ApuXFNS>,_9VW_>?^M*G3$R!1*ls!RqkPpdMdeo,Z8*I!<ojP&gh+j,"$N.B(hde#a"iY1"3iA`/QuB._+2J._%)$nJhU+??Q*TJa1BVknTMtik,8Q0*nH+S:DX45QX*lU*TTMpc"kqk.)8eUYue<E0.`i@@peN8+c*+%p-%9-o=%kn!hG3=9+Z39=?>WQg(q?VU&_<rRm$fC4l'D2lOM%:#,Ad,PHgWdCX1^T+<eRa"^^--=pP2+QIqMIU+K4JE8V:+k\<q<1r:!.08IUdl@E@mTk`X"o`.r`"L#5n]n`n<$.^#K3WZ<NW7'bc7GuL6]qG.t\XaeciVc[\[3m`"h!kP>S7>J"<0.6k46"aH5UkX!NBJ7,'!N8%4D\RbX:Ak?c6!QfL2ci=;dsp6.e.@TC$<SBRj85&+Z$mgkS';%*CWPJ"N<C2askl_gs(rE9:;\S#&rT-)7XRoF;H\Xn4q*EX$i8PkUP+GV&E,fbNO]q(2aDE=gF3Wj:8kLF!&Y9_JXnf?T>.N`F7jRB7VbW_OJ)-#1()S95i5'qj?4sk^LgWDbJ$jEq8SAAg[3pMF1aRDX],C%-A$hE4V5F>D_s00MeIA1)_;^:,%Tn,BZ?=q;K9dgal&-8ePD2WbXk'cmY8-OUU^:C"+_<i]`SPAKQf`]ao6k*UW7R*p(YB5OQ:N*/X.TN=TD/p^?<`0'+@t;_f,]cK"pEH*)C5D]2#&l9Q77d1$<>nS6hoL=K(r9K"*qg->MaZk<~>endstream
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1956
|
||||
>>
|
||||
stream
|
||||
Gb!#\gN)%,&:N/3m,U`5/=6G+IVS2*dnZ(W8_eKdUDX:\+dj*a!C=9df678P,YPSI9]K0Z,nZL"`U;E3"XmLJVWK\W!.MM4PB.(q_;uZ86QEU`#HodN?UhCCkqV1O/>V4YN!-<Fi&p?QeCH:_s",$3PY$3Z-nn"'%##W8JQ=K(Uj.`%;oX30/S(\#.^CU&O;:L7dj)B8J]jD6O:V-CAIRBKL-bG*J9G1jeL]'\Ik>[P>-$l:/lU'WSPTZ?<^e1jm$]GtVu$qnZr[;IC-r97B]r;1^&uOgNE,eq!J`?FgI'%ONpl#00GrNlOV:iCim?`hZl.p:bH_!<QCo01NJ::onYt\47T@tTP*#K'l>D=QZlRY#d'o[Hi?96#Y96s5[K^0kTIUp`K/"f=oX;4"LXcetUXAaJrXsXYhbD0ec><h]Lb'1G(gMupekDJ:N_;Lh%s1dk$]J;Q-Yb5&4(d,'6/]%W$BJ[>O3_7u4m)Qh5Gprl,_HnEOiB`5"[Y,*8uRDJ$2NS#H/3ZlDC0WM?,P_dA3@sm0X_?tR$Jc0^?gAo"6opJCWQS:l1iNQG,bJe_._8gD?c+;f-!Lb5U6M7U@%?dN190m0b<81W&>mYY(0NhiVhF5R3"L74Y0'E^sMeGrN>=+rAop$juY1,]?)!)mcG4ekEJ59b*oDL%5_,#8^u)Z9Nuer/FaI(AOX5P7kJm)B<^SbDuWiVB'DJ'3a_$+W,K..HreDtaX5H%:WN4IF-aeKhVFe[4X+j-o&UO`ammcfeG\j(a`U0?WQ!GVaUO'FnC9=Y)n(BF$i1X`9`7?)9RVgV#dX_@ccf'%4S,09V85G2G:;c[RRA/G%3X8YA:d5?!]\M#Ae!*MKVeYr_u`m]J>ZBTFnf4_A<GrFNTaL"$()[&HfV?9eO<bLj&Eop/T$VuI-"`m>1i6"RG$`Jr$Ni.NI\VD$"kDnU6[!Q)!S3TWO/`mV.Ftbi:U;=gT#&kYR`"Nh*t2>mltK\9o2MW5^QcA;T\tEOG,+5(5Y!2IAK50-*!&6[oc+T^MS)\=jC:@hXIrYIfK0QP`YRt_3G2>XQOY3Vs?VB[1"!IcBE,&l-6_4(0q\4q#^:2Le[)i/BJt_9[Oa(k_nojMGe*aV!e/b<Ld`45!64,fDuq3n1`<dcJ;2Rj:?e3rjI,r/Y[D9>R,YM[p`OgFZgsO\fD90_pF\.a@8K_-4D=l74cOZ?.\eo^@YgPK4*p>s-8Is2&4HV:m\nJ:Nacl]Fmp>e8@Toj9anr-`hjH_8>YG!U8%qaTW,BRf@l-[:gBggj1n/)J6&YL9hZ5g=Hbu/a-D\7FOKn'i!Rp%Do24f/$Jb'['!*$r"mc^F\>%8PN`l==)sM;LQOUZcJTq16r2]!8Lqn:sUjNeZp87jYC).hcO9h%-T.AOM;PM#R=%CD4'<=Oi!(?nPs(/KtNBr[M8NYCRc/A>)t#Ej<e5@:FV2@&ZO5B;HQ1NheT<a5S@Qu?e7ks%-@$s4Z)>EW^+$u/OV4ehR-G1'J'$>K@DNqe0gQ#Krnml_DUlHD^":KReDUanQp4$9?cA.&Q_DT'I^'*Ga]ti3l[s`=p7o-'mc-sril)&%;eL<LR*cW0oK;/C6\Ktm@0%`A@i?B?9?i)2"?hk'r`Ir)"=Wn?'lG;:[o@oYi>ieHQ6Ou:UF^p@#Q8K9Jq5K"I@O@(=]4c`[^8/H'4%`?ButO9MVg.5ESC:"B!\jColMu9Q[E,d/\8LehR&eBB$E-_f=kLktsh6GD0-K'3X,@FI9CE8FfADIJ:!to`M?_-<OKQ`e%qZTi8K5@]lBI+Ht["'nRM)oOH"l%N03'9\.>6*)Y-E;G"uq/;i5QAS+!uW3crOFSo2PCH(>^h)+(gnum9bhKL@SbUm*3Ia``(#2-BV;jJsgK9P:69'_mIiM$r1!Rfd((d\cpWm*"J[N2X`QiCP0l%9%LoAOa@^=k&W@.Xtr1.nS~>endstream
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1741
|
||||
>>
|
||||
stream
|
||||
Gb"/'D/\/e&H;*)EU\b#m:ofMog0O%JMqSY@4h<g\%htG.3gRU&m<X.fg.Q5^O8UTU6PF)l%Z8]('h9i,4CQdo%[_RYjb)^pL"<sEXJRC'I\Uu"]1]@0RXCoC%&MPX]N)m\3BL5GhaU^Z#&3NNsUH`Im3=p&o.'5L*c+q5cp#@X`8<C/sfEAha+\#(?<Fh]nGV`D<O-sFr5:S<F'<KKNclL.C>_aidPH!l.KGXk+LLbA^C'4,(qUQ=kp>\'5hVT[\0*Y0!T69lUjRO-Kl>8c&$@!%]r\f17(i&HDn2Jma[5!%iS$4?1a'c8j"7XO0)HNe_1@V<pNG<2oaH]^H##8)>"]XM^K?7rS"&YoSS9.V84@M:2M@_54KM%/hfC(F$3<sDD9*naY3i!Nuikd#r]=5/tmcgA3#BLXWqrtAX^V-^QZh]R[_VmYtk=Ym-i)FIJ$To2H<0P;$*Hf?,[VRJma-^hkAu,+LZ-8!sQ_J\(!C;U"Grr<pmCRL=a#JnAE`j?<_$&V0+q?.<Q='UJ_ot7]o(>8I3e!JeM9><1(k=!2YjiON-3C"dMMFYeXF8!gT,5=H,;3^o[I+Xa`'0dkjca`j)Pm;SY=fo6pErSfs)/U2Js0ND"u6\*;LZpf.4Hl#W(M2nM,MXL3t%1">1e!sU+\'jQ`:/KV^<M@^*F\X[0Wc.*`iAPujpnNs=i7,u8N.[0&$:b\0t(kP<rQ$E(uPd7A_22TiJ)FG0Dip,[m3m+jiEt$H!*Oskta`7)q3%(kJFL63T[sbH*Xs)2(T'ZO:Rn>%lf7.!1ko,3hg(.5F@K]*W0Or=Z:FZlS$u\ntN?QKo9,6Y.*NCYc^DqIOp70^5D/;D#iPF7WK;o$#4,'.hDl$:J3.Z:Y]Xe34F'B4!\"3O?*[l.:kJHV<"Jm/ZCi-(N:8u!2A3;OYN>Ufti.d@rXYQrbp!=hu;26g)S.FIIGm3M)G"Sh&T3=3j8kmH="#A'`1-&#R0R2BXZ,iu^&sY\`c4Qt['L$/D*3/o<kPfE<A=ptP4g'iU?3mG$lsJl<TQ3dXCS+K^8$\n_)I(<%ehSmd2S`7EX.YF"AG17qfW'pj_S$<N#"(BKq!Q,E(T3J'Nuq]Gp[B`OWeNs<Ek#)!ZKI".+]N]VYq9gjP7O:75+.phGl)f-p<2OK/_*qfb7W91,'Z%EpOe]U,0)QQ5_?Jl"@bZ\[n$Rd$5_p$6)jG*r93Y.gbK=dS-%Q9$D#<I;g!Wg3Gr\o3HiH5<BY5'^aNi"?]>2'\/up`k,^B(H$c+CPW4m7C2E8B>.tAXK,cn`3icJ&$Wi[pKM=N;YL4t#EKn]AqLXYp4PYKl>PCb)ktl3L27BY[%\/;W%sOVrf"K@-G?1,%o=<#KULo,rF/eO@Zc@hrp4*DFWcK&B(`HZ31A-+RKP0*-FR9!m@(TU:q2T]kVq6XF\+sL%[D^!;]L>t8`HX5>jF`T=U(P\?XhD-X^(j3$\9g1dim_pum'0[6/'Ybc/56Io#L:C--]W,UHV#FsSltPY=lX3F1N57RnRRn$34*U5rGd=dlZCpm-XZe=Ka;0m8bm-iK$#%'$Z]CG\%a@#`irM>=se;RJQf4LLX@-0UqjgkR.UnB8_51.7aUQY8QS\sca+(EoOtqZG[Mec8X^U9,`T`WQ!(5k#A0-)Rac7seK;[NpRjnSrUNUYE*$E@D#`bU3KN%/b2[*7DYKXmEql^A&07@+F[\'Abd%EZmdWs&T/R)fH@H%)7HZ?~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1935
|
||||
>>
|
||||
stream
|
||||
Gb"/'D0+Dj&H88.Z-t?rYJJ3K$g)&=3c1>JHInK?@F?gQF;0'&#@k<M$r)6MX^pH'^mLq.D\k`rc*$^&dH]CjgX)^^D>o92J2]i?q9B26_/KgkCUl.s!2m`0mmhP4kqgL)Y^m73a0u,RkE6p89oaB[(4"0H>Pl-^kCDC;UKO\pPnWAObbGt]T#:&pSB_sni1uIFON/KL1r`urT9s)&)8"kr)TNY?mD@:mS$:P]3Z*2[QqJ_\W5,L]G1(1:Z:H.U`>6`ebW&BL\5\jF?kH=>E:!W4F4FPe5+u2#$K74"(bsTs--DX-["+bmNDQ'p&mVQB6Z[BkPRE]k]M9*`BlM.hLl,$JGQ88'E7gcKU>b2qYI[qAFjc7l=1;7)Yn\;Z,b.Nc8cHaG;)\aq%@ieG(C;[>9]R$7kjBG@K8?.]YK53:9_;W6EICV=Z.m&#C4ktF(@t\dotYU"ShhK]#*/[+?LG/`CDP1K"-&,%Paq_LP++=ogUsuA,$*;.is=*90\cRFUupKC]7TfM#RSZk+]"4e7m`_M>Pu/L#U_YO)5L_N6?\)S-Z5Wkod.V(\&QKh+V0Dh0=XNI?.-1W\OiQ#e**7A$4Mo\MJ%%j@FY+0adC@N`g%5^eZks3<J5EJHJXJ"o.*LP^t(44bh".m[:!TNaqd8#4=hE=QEbm(lt4u,T%10\^:-"!]nJP'hDfk#O90-.2QGITLhOtk!7APdESB"mT"l%[X;YLM'`Ch4;KfiJ=#2unS1feZ?h58rj2q*t(b]?)jbf=L\G(Q6R--E,f:;HcMD3#Q8\e[uNYlmrH[?TNT7er^!63g70iepm`RTSIU@XG/6pWU>8r.$-a[a["*nsY/.11cpb4i-[SqJuq!7of_:=OQO:Ee6plmu1e("]:4?_IlW1*XpTEVl4,?77Pk.aGp\W=ri#9$0(0.M>ks-_bE(8@an^N(bh-c5rYdi(7&@%UeC<U^VD.U(+<OR''f)VY\`*&R<Y1ZAe9:66&"We!rO9d<Bp)@JN%9I]c=H_R>&(&3uSf3!njEG8aGq<]7%gbJkHF<+XHD2EiXpJ_rM-T06Xg2eX4um9c&KIr$q>X_iYcG>5?tGus-o2:C+#CnE+gK0a?;e1/&aemWubH%`*(,s>D_Mp:<FfE8;>R3=2%&`YXEF1>3\lpP!e?1ToL-;35SYa#TWU2i"]&JJ+[qj`'?Cub.FcDO7(Wtng&os1m#eJ)!YJ&=:-P@H:Sj/?[32:?4R!]h$rM+b&pDtaA(_<W%I._6r4P/JrC1p!e`?+K%Mj]gh5aNaB4jIq[f$Fq(-]Wn!\^bu-uliZ2K*FJ408Pja;)'p]U;@DL,a7,K8a&/`YpDEO^<kQrg7'W>n6u*o%_&O<h;c?1bh;<C58opPG>(Grre3`ph]l-GTK7GtXGcW!sNX]$]jF^@``S$C8_6TB6q41(?b60GeV47trbO:X$2)S5^kg]kJ/m-D@)&lus;npMZWS?7s=2(,PH&>W!9YK<a/ID"NbO.H=:cka:psmsaMW\`+M1/@mW]`oc?$bN(o`'E(C/%Uh;Gb`uI.smSY&=bSpF]*eVG]%m8RHouqrjpkECu+T,V\@CBoprYkpsN4.>BYR,cH^)/+U+4kZn![*WGA3:q2WcdB"K%V[-^_4G@Zs/>tmP8O[b4jn<Lrc;AfAJp-73EY4)ZPp/M]iGJOPMosgWf436Nkm<nROhieR''Thc9j1``)X8`^E>le>=&`5G`*DP%XGt*>\LL(P5(E?HqY'k*j@Kq6K>TfFU_\Y:mQW^JaX$I3ZYjfG6@PCRrW?!>We"mh;n6YP,YjU>MKu0q7#S2)WF"2H_jHl;p_-sI=t$$-jfkDV+cqA(J+BPlO(Y)`0?kXPP;n!!H?Rrd=#n8>.@)eDA0U=T).fr97*P;@T'-oZEGC-T!bi.nqIh&gAkp%2s!GFpdD,;`A+]~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 626
|
||||
>>
|
||||
stream
|
||||
GasIdbAu&c']&X:c8$sb@EJ(U_#cZo^m$"<L/hk'a13o7[U`np)>JO=7qh-D`7IfLgiFROLVn>ULO7DKD_etQbJ=@f%Cfp]fe'?_n#+2RA/MR"'e)We8sY.EFFJ+sM+GQ?":Gi,8-N*6oES(S.Hd$eO9oPkOsZP(#t*?M3mQS/81^?bYV_<%.)=<pj<Tqq]PdClOJ-+amLL%jWF!&[/3#uUp);-j7^$R$joX3Og#Wm7O:b-^9TSC5D6:FQ;WT.55@BO<3RZA2_g#S"gU6i,mg(#=(-#,jP-J]#dc@jbl:`N<LS9+o9rn-BX%cn^W_/s6>.3h*_Jc`G:@LYjs)0B7]dmnfo.S9kPXHIKd.bjST:E+8*rF`7:_d-gcUpptq[:I(6MII,,u[X"f'"\#p/EC'^i`T/GIuDPM_PV(#A:=Z*>bn8FG4T=QE[*LbR^%u&FmPg/$3%mCkigOhfV$G?PZ9S9P%pUW;-@1O*WO.E7`ZN4@qs!VI%2t$ohAUL=<C[D`efpE1H7V@057dNB?V#?k98p`)0MiFekQ%Wd5]tn@sK[Fr8PFW/bO=N5"^Gb2Zk?6CqagK3ITl_J$=u40UYh8]ipMT\VkTQKjnQHW4mT.D.mH<<F9~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 22
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000132 00000 n
|
||||
0000000239 00000 n
|
||||
0000000351 00000 n
|
||||
0000000456 00000 n
|
||||
0000000533 00000 n
|
||||
0000000738 00000 n
|
||||
0000000943 00000 n
|
||||
0000001026 00000 n
|
||||
0000001231 00000 n
|
||||
0000001437 00000 n
|
||||
0000001643 00000 n
|
||||
0000001849 00000 n
|
||||
0000001919 00000 n
|
||||
0000002215 00000 n
|
||||
0000002308 00000 n
|
||||
0000004125 00000 n
|
||||
0000006047 00000 n
|
||||
0000008095 00000 n
|
||||
0000009928 00000 n
|
||||
0000011955 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<91a34edc5f1ac0d3fb629e0a3e03ed80><91a34edc5f1ac0d3fb629e0a3e03ed80>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 14 0 R
|
||||
/Root 13 0 R
|
||||
/Size 22
|
||||
>>
|
||||
startxref
|
||||
12672
|
||||
%%EOF
|
||||
325
Les15-MCP/Les15-Lesstof.md
Normal file
325
Les15-MCP/Les15-Lesstof.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# Les 15 — Lesstof
|
||||
## MCP — Model Context Protocol
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Vorige les:** Les 14 — Cursor + Vercel
|
||||
**Volgende les:** Les 16 — RAG + Embeddings
|
||||
|
||||
---
|
||||
|
||||
## Doel van deze les
|
||||
|
||||
Aan het einde van deze les heb je:
|
||||
|
||||
- Begrip van wat MCP is en welk probleem het oplost
|
||||
- Vier MCPs gekoppeld in Cursor: **GitHub, Supabase, Vercel, Polderfest**
|
||||
- De Polderfest MCP server lokaal gedraaid + via drie chats naar productie:
|
||||
- **Chat 1:** GitHub MCP — repo aanmaken + remote toevoegen
|
||||
- **Chat 2:** Polderfest + Supabase MCP — nieuwe feature bouwen
|
||||
- **Chat 3:** Vercel MCP — project + deploy
|
||||
- Productie-URL gekoppeld in `mcp.json`
|
||||
- Optioneel: eigen feature toegevoegd via Cursor + Supabase MCP
|
||||
|
||||
---
|
||||
|
||||
## 1. Wat is MCP
|
||||
|
||||
**Model Context Protocol** = open standaard voor hoe AI-clients (Cursor, Claude Desktop) verbinding maken met externe tools en data.
|
||||
|
||||
### Het probleem dat het oplost
|
||||
|
||||
Voor MCP had elke AI-client eigen plug-in/extension-systeem:
|
||||
- Cursor: extension API
|
||||
- Claude Desktop: plugins
|
||||
- ChatGPT: GPTs + Custom Actions
|
||||
- Windsurf, Zed: eigen API's
|
||||
|
||||
Eén feature × vier clients = vier integraties.
|
||||
|
||||
### De oplossing
|
||||
|
||||
**Eén protocol. Eén server. Vele clients.** Anthropic introduceerde MCP in november 2024. Open spec.
|
||||
|
||||
**Analogie:** USB-C voor AI — één stekker, elke laptop.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architectuur + primitives
|
||||
|
||||
Drie rollen:
|
||||
|
||||
| Rol | Voorbeeld | Wat het doet |
|
||||
|-----|-----------|--------------|
|
||||
| **Host** | Cursor, Claude Desktop | de app waarin je werkt |
|
||||
| **Client** | MCP library | praat met server (zit in host) |
|
||||
| **Server** | GitHub MCP, jouw Polderfest | biedt tools/resources/prompts |
|
||||
|
||||
Drie primitives:
|
||||
|
||||
| Primitive | Wat |
|
||||
|-----------|-----|
|
||||
| **Tools** | functies (de AI doet iets) |
|
||||
| **Resources** | read-only data (de AI leest iets) |
|
||||
| **Prompts** | templates / persona's |
|
||||
|
||||
Vandaag focus op **tools**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Transport
|
||||
|
||||
In Cursor 3 is HTTP/SSE de moderne manier:
|
||||
|
||||
| Transport | Wanneer |
|
||||
|-----------|---------|
|
||||
| **HTTP (remote)** | Server is een webservice (Vercel, mcp.supabase.com, jouw eigen) |
|
||||
| **HTTP (lokaal)** | `npm run dev` op `localhost:3000` |
|
||||
| **stdio** | Oud — Cursor start lokaal proces. Vermijden waar mogelijk. |
|
||||
|
||||
Alle MCPs van vandaag werken via HTTP — dat is wat we gaan zien.
|
||||
|
||||
---
|
||||
|
||||
## 4. Vier MCPs vandaag
|
||||
|
||||
| MCP | URL | Auth |
|
||||
|-----|-----|------|
|
||||
| **GitHub** | `https://api.githubcopilot.com/mcp/` | Bearer (`ghp_...` PAT) |
|
||||
| **Supabase** | `https://mcp.supabase.com/mcp?project_ref=<id>&read_only=...` | Bearer (`sbp_...` PAT) |
|
||||
| **Vercel** | `https://mcp.vercel.com` | OAuth (browser-flow) |
|
||||
| **Polderfest** (eigen) | `http://localhost:3000/api/mcp` | geen |
|
||||
|
||||
---
|
||||
|
||||
## 5. De `mcp.json` config
|
||||
|
||||
Plaats: `~/.cursor/mcp.json` (globaal — werkt overal) of `.cursor/mcp.json` in een project (alleen daar).
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"mcpServers": {
|
||||
"supabase": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.supabase.com/mcp?project_ref=<JOUW_REF>&read_only=false",
|
||||
"headers": { "Authorization": "Bearer sbp_..." }
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": { "Authorization": "Bearer ghp_..." }
|
||||
},
|
||||
"vercel": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.vercel.com"
|
||||
},
|
||||
"polderfest": {
|
||||
"type": "http",
|
||||
"url": "http://localhost:3000/api/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Patroon:** `type: http` + `url` + optioneel `headers.Authorization` met Bearer.
|
||||
|
||||
**Vercel** heeft geen header — die doet OAuth bij eerste gebruik via een "Needs login" prompt in Cursor.
|
||||
|
||||
---
|
||||
|
||||
## 6. Tokens regelen
|
||||
|
||||
### GitHub PAT (fine-grained)
|
||||
|
||||
1. github.com → **Settings** → **Developer settings** → **Personal access tokens** → **Fine-grained**
|
||||
2. **Repository access:** kies welke repos je MCP mag zien (niet "All" als het niet hoeft)
|
||||
3. **Permissions:** Contents (read+write), Issues (read+write), Pull requests (read+write)
|
||||
4. Genereer → kopieer `ghp_...` (éénmalig zichtbaar)
|
||||
|
||||
### Supabase Personal Access Token
|
||||
|
||||
1. supabase.com → **Account** → **Access Tokens** → **Generate new token**
|
||||
2. Naam: bv. `cursor-mcp`
|
||||
3. Kopieer `sbp_...` (éénmalig zichtbaar)
|
||||
|
||||
> ⚠️ **Dit is NIET** de project anon key of service role key. Dit is een **account-level** token — geeft toegang tot ALLE projecten in je account.
|
||||
|
||||
**`project_ref`** vind je in je Supabase project URL: `https://supabase.com/dashboard/project/XXXXXX` — die `XXXXXX` is je ref.
|
||||
|
||||
### Vercel OAuth
|
||||
|
||||
Geen token vooraf nodig. Bij eerste gebruik in Cursor verschijnt een "Needs login" knop in de MCP-indicator. Klik → autoriseer in browser → klaar.
|
||||
|
||||
---
|
||||
|
||||
## 7. De drie chats workflow
|
||||
|
||||
De climax van de les. Drie aparte Cursor-chats, elk met één focus.
|
||||
|
||||
### Chat 1 — GitHub MCP: repo + remote
|
||||
|
||||
```
|
||||
Maak een nieuwe public GitHub repo "polderfest-mcp" aan via GitHub MCP.
|
||||
Voeg toe als remote `origin` aan deze folder.
|
||||
Push de main branch.
|
||||
```
|
||||
|
||||
GitHub MCP roept `create_repository` aan. Cursor draait daarna `git remote add` + `git push` in terminal. Geen `gh repo create` zelf typen.
|
||||
|
||||
### Chat 2 — Polderfest + Supabase MCP: feature bouwen
|
||||
|
||||
```
|
||||
Ik wil een review-systeem.
|
||||
|
||||
Stap 1: vraag Supabase MCP om een tabel band_reviews
|
||||
(id, band_id FK naar bands, score 1-10, comment, created_at)
|
||||
|
||||
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
|
||||
- addReview(bandName, score, comment)
|
||||
- getReviews(bandName)
|
||||
```
|
||||
|
||||
Drie MCPs samen aan het werk:
|
||||
- **Supabase MCP** maakt de tabel
|
||||
- **Cursor** schrijft de tool-code
|
||||
- **Polderfest MCP** voert de tools uit (na push + auto-deploy)
|
||||
|
||||
### Chat 3 — Vercel MCP: project + deploy
|
||||
|
||||
```
|
||||
Maak via Vercel MCP een nieuw project voor mijn GitHub repo polderfest-mcp.
|
||||
Voeg env vars toe (production): SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
|
||||
Deploy naar productie.
|
||||
```
|
||||
|
||||
Vercel MCP doet: `create_project` → link GitHub → set env vars → deploy. Productie-URL terug.
|
||||
|
||||
**Tot slot:** vervang polderfest entry in `mcp.json` naar de productie-URL. Werkt vanaf dan zonder lokale dev-server.
|
||||
|
||||
---
|
||||
|
||||
## 8. Polderfest MCP — anatomie
|
||||
|
||||
`polderfest-mcp-finished.zip` is een complete Next.js-app:
|
||||
|
||||
```
|
||||
polderfest-mcp/
|
||||
├── app/
|
||||
│ ├── api/mcp/route.ts ← MCP server (het echte werk)
|
||||
│ ├── lib/supabase.ts ← Supabase client + capitalizeDay helper
|
||||
│ ├── page.tsx ← landingspagina
|
||||
│ └── layout.tsx
|
||||
├── package.json ← mcp-handler + @modelcontextprotocol/sdk + zod
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Vier tools
|
||||
|
||||
| Tool | Wat |
|
||||
|------|-----|
|
||||
| `searchBands` | zoek bands op query / genre / dag |
|
||||
| `getStageSchedule` | schema voor 1 festivaldag |
|
||||
| `festivalStats` | totalen + verdeling per genre / dag / stage |
|
||||
| `getWeather` | Open-Meteo forecast voor festivaldag |
|
||||
|
||||
### Tool-anatomie
|
||||
|
||||
```typescript
|
||||
server.tool(
|
||||
"searchBands",
|
||||
"Zoek bands op query, genre, of dag.", // ← description voor de AI
|
||||
{
|
||||
query: z.string().optional(),
|
||||
genre: z.string().optional(),
|
||||
day: z.enum(["vrijdag", "zaterdag", "zondag"]).optional(),
|
||||
},
|
||||
async ({ query, genre, day }) => {
|
||||
let q = supabase.from("bands").select("*").limit(50);
|
||||
if (query) q = q.ilike("name", `%${query}%`);
|
||||
if (genre) q = q.ilike("genre", `%${genre}%`);
|
||||
if (day) q = q.eq("day", capitalizeDay(day));
|
||||
const { data } = await q;
|
||||
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
**Bekend uit Les 12/13:** Zod schemas. **Nieuw:** `server.tool()` signature + return met `content: [...]`.
|
||||
|
||||
### Bouwstenen
|
||||
|
||||
| Package | Wat |
|
||||
|---------|-----|
|
||||
| `@modelcontextprotocol/sdk` | officiële MCP SDK van Anthropic |
|
||||
| `mcp-handler` | Vercel-adapter voor Next.js |
|
||||
| `zod` | input schemas |
|
||||
|
||||
---
|
||||
|
||||
## 9. Lokaal draaien
|
||||
|
||||
```bash
|
||||
unzip polderfest-mcp-finished.zip
|
||||
cd polderfest-mcp-finished
|
||||
npm install
|
||||
cp .env.example .env.local
|
||||
# vul SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY (uit Les 11/12)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
- Landingspagina: `http://localhost:3000`
|
||||
- MCP endpoint: `http://localhost:3000/api/mcp`
|
||||
|
||||
Polderfest entry zit al in je `mcp.json` (lokaal). Herstart Cursor → 4 indicators groen.
|
||||
|
||||
---
|
||||
|
||||
## 10. Bekende MCP servers
|
||||
|
||||
Inspiratie:
|
||||
|
||||
- **GitHub** — issues, PRs, repos, commits, code-search
|
||||
- **Supabase** — DB queries, schema, migrations
|
||||
- **Vercel** — projects, deployments, env vars
|
||||
- **Filesystem** — lees/schrijf folders
|
||||
- **Slack** — kanaal-search, posts
|
||||
- **Linear / Jira** — tickets vanuit IDE
|
||||
- **Notion** — pages + databases
|
||||
- **Brave Search** — web search
|
||||
- **PostgreSQL** — direct SQL
|
||||
|
||||
Zoek op `cursor.com/mcp` of `github.com/modelcontextprotocol/servers`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Veiligheid
|
||||
|
||||
- Tokens niet committen — `~/.cursor/mcp.json` staat al buiten projecten
|
||||
- Supabase: `read_only=true` voor demo's met productie-data
|
||||
- GitHub PAT: fine-grained met minimale repo-rechten
|
||||
- Vercel: OAuth + autoriseer alleen vertrouwde IDE
|
||||
|
||||
---
|
||||
|
||||
## 12. Verder lezen
|
||||
|
||||
- **MCP spec** — `modelcontextprotocol.io`
|
||||
- **Cursor MCP docs** — `cursor.com/docs/mcp`
|
||||
- **Supabase MCP** — `supabase.com/docs/guides/getting-started/mcp`
|
||||
- **Vercel MCP** — `vercel.com/docs/agent-resources/vercel-mcp`
|
||||
- **GitHub MCP** — `github.com/github/github-mcp-server`
|
||||
|
||||
---
|
||||
|
||||
## Samenvatting
|
||||
|
||||
Vandaag heb je:
|
||||
|
||||
- MCP gezien als de open standaard voor AI-clients
|
||||
- Vier MCPs gekoppeld via HTTP-config in `~/.cursor/mcp.json`
|
||||
- De drie-chats workflow doorlopen: GitHub repo → Polderfest feature → Vercel deploy
|
||||
- Je eigen Polderfest MCP live gekregen op Vercel
|
||||
- Drie MCPs samen zien werken in één chat (Cursor + Supabase + Polderfest)
|
||||
|
||||
Volgende les: RAG + Embeddings — AI antwoorden op basis van eigen documenten.
|
||||
206
Les15-MCP/Les15-Lesstof.pdf
Normal file
206
Les15-MCP/Les15-Lesstof.pdf
Normal file
@@ -0,0 +1,206 @@
|
||||
%PDF-1.4
|
||||
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R /F5 9 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/BaseFont /ZapfDingbats /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 16 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 16 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Author (NOVI Hogeschool Utrecht) /CreationDate (D:20260616185725+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260616185725+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Les 15 Lesstof) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Count 7 /Kids [ 5 0 R 6 0 R 8 0 R 10 0 R 11 0 R 12 0 R 13 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1567
|
||||
>>
|
||||
stream
|
||||
Gb!#\9iKe;&A@ZcqR4ZNMFWh@RAq6caX0uL5SAiOD/gFdNBLClBP5=[ZG1j8,YO8$f[u90_-kUpo=Eh$^O@qj0"kBW*i/k7@sIec"6RJq(7=Rc0YW(hj+Dgkf]pbtFb50+@*j*"-gYu(qEMRX\:NE/Hf0-Wf,M6X$p=CjC3i-\Z5P'?ECpcpYg>_H"[M5P26\.W\8EDPDf+S&$Q.d;peM:sJR8'S^q,Qh#<a$A]f-(B^V8[+&\]2iQ/TE?0d\T;RDEeGY*:auP0(Vh#%#pfAe!,hV8TeG.Y,,HfPt!MC'pMI5!LJMMVT_aG@5ZE4OdPJK/&nT&eg\2Ql0#^==r%5bS(\qaJS>NhHsT]_co,i*R1BRi3Q8tc.Yfq9-pG@$0q4&`THtB4/7MX;Sce\8*:dj7!uDo9;\)qgK"a*'gE2c^`g?c-Q(SPoF=R,4X]mXc#qDA#PAO%Xc"u,fe%IR1&bBfo5K)jTYC\<Dt?#]?fG(ePi^:1'%@=!TWmc?Ja)e0mdOas!52bTJBs\[GDjWjH3;H0Fack/_u2RdCX/A/gou6WA!A$UHAmQZ$+:X;V`7[#0<"]h%G8Z8)N]t5R7".<,XG*3&nRY*D)R,8$+'%U-<u,0=0/c[h5^tdPtb2p'^%6t0o82,X&_>s[GDj2hC^-Fn7e$V$.jD)AWInKPtYXd\%%\99)dK\q15qT"e9!GQ(e78W[3dla>rDid1e+V781:u'HCR.&>C%$)D]U'/Wk8i)6p$'6KmkupRX]=KseHDm[Y4?8dSU(?-<;"HD2k4-#fHPHqmsf8B7\d#Y*fO,,t2M(4*ggYu"jlL'>'26DHjWlX'?P8g\NI(#]q==X\r[Un_d#GgE=I("Ql^nCgR;7Wb1^ec-XZQBi@Z+MVj:g3lG9F<@E@=&h1$J,X\tY)k.\]S_P&b+1UG[r(^mMVmI$mkQggAr+P8=TiKr\q5q/A_YRO+aTDN>:]YQ:`hT@(nWP'o>4n1a7B)?qM(no/EX_a.UAM&pVkd>p'(uiI@^'IJ)>(2=(EA.?#=g%e'K48-.+`:BO,nJ#kHpmDE9)g7kdR1K)F=?lFE&QbmU1a1a*5sNjcC=V>d@CgjLuW1:q0S.BH1d%<m!:]WitT3\*f$s6n3$l5,-g(8S^A3,5cfpq%B?3Y;-jg\nfXpt@K;eS)s4+OAW3FB-e8ZBu\km%remdo>eVqE$hD@Ch*eF]l%)pFs@s@b0qg>\3_G?=8##rgOA!I8a<bS<IFJ7CN"@lds@VI?UBsr8qaA!q`?]K1?;dA.$5qCA6<7IeWC(KeuLWeEmkGNH$h-W!NE:B=2`fYX/<Zipllbl`hO1n&?S>Za:66PpX6q%gD4p<,qI4H3[ki_;M3t.m9F;19);I^BN<J36WFeHn-uCeIZe0FeX7'Ele&&=:sT#5@2LV9Z^O-QE7ql+f`'EaQ/pJ\\rSV<4ZAeGMobDLH.i9d-7gU-T0+m,,`u?=Yo!^T2(G;jWGV9QX88*]?8gbI#^%@Mi!VdSE+4I:g*"8T"itBKPkf.VgOlm3\XO\9Gk(mMhQ:d\Itkq@bLT_#nHq:Bn.3b~>endstream
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1522
|
||||
>>
|
||||
stream
|
||||
Gb!SkgMYb*&:O:SbgUH5,daM^K'b2=oTCi<BUX1X0*)..U2;rSW)Ak>p?Qq$MsV@iV'\GL)T=Gt*6oN6B?kHE=SU8.mj&']4bDWS$IjgX'W=Z0(k%(HUVYO_D32J+r5amH"HH8EcZ4"GrgVP:s/O`PNR5(=[#iisXkKYWGk+<1%SA&Scd#n5BE=lp`m&.D2Qf0WETQZ!o'O6cJ2j7a7-HIJb`D:aiO[)82l%]-UCb^e(DMYh9=/E)"qoSc+=-OUd!&ml]ijENSTaPX@ur1MFrL_!$11d#HjD;;Y*0fmCdhnC6%+]K*CVRLMj.u#GnTS$4*El*&(rf/s-SO$f+:TX[oQTG]OaVW3_[m..RR:CclEN6jCP:tWD;D-@:d[A%5/d0bWG#HCD"n`**/"IOhlXeMQ;L7IXT!GW#R.9B3jR:lp$igOGL/Nl<=LgAjNR:L+pRX(0iH%K"I6863FN_@mo`k*9%`6X)G$r*RnifH_IXc0d+;2=NGLdf.r2nRdrgc?Seh9Ns1S%BIT-SW-BIY2;uM.=#/$CZ":aNgY_d0$j[;0gVL2$K@"`hO8uWE%cHLJJ9\;5$Rac+3.=KX[OU"Wq\6nk-lSr3PjNo"Fk2)@8P$!P.^W=KU5+\P477QC)7aX[ZTi#;f-c^H7U,0I/5[7PD!Q&j5c80Od=N0:5?Qt_+"B\\_0'b-L7/afBsY@f;Zg*k`Fgr!^-Tt">Ph&Q<`4^jW!VI$c0Y%&@l7'RKPB5n&9<fJQa,Ft<jd3R5WhZVK@T+=d2MLs:PI>A[AgL7HGi:jZC%m79]`'$5ito1C6OE#H@>Q-$Rb"C;/TJ>!O8^K3_\;)CV\>tIC(66edS>j)CBX0)%M4[@TegdV0c%6d3Ht%,YoL;W*:?'+:>UsNNS&G[r2;r09rKs)8<FNE7I=cS/ifg]Q!7"UnbelAc,D8IDUsJ%g^]OnPj7P8/$/XXD[7aHo]OBFtttb!\rYIkR0n?8kpP#jJ42F2J*+?':CcGS;S923jl+6Q,j3MP"np"U+B)+Po42fdM-F?-S.!]PB4dMXQ*JckZGo49)#<DiQap/C6H6V9PLG^@TSkocNGp+WE@(c1jF_l]V"^^/0;l.;FCce^#OeF6#!SKD[lL#!l)P0`SAcH=L-/`.Xa-4<8?q?0W5N.`!AGcacR=PXZ4NAikOM"[>-fsThRko4gG5RQ58spF`d/l:ls7t1)oKo-gW8[(mkE$[ah?$qZX&gSb>_1,J3gVXMaJt-&l)G4)8rnr*/6s2]k-Lpau'2eFTT;?:_QFN36<i`m2!;Mt>nhjat^0SsSJobXE_`OgQ@tgotQ"XmNd`9N@;*CnBMt6K%]LFBAiRV/7KbhpC%Sh%a;g`F#Q=$U2u4MGIUX+lY_91KVMAF%(J1D#-l'lTGsB*d_RR\S_`P-I_?&/?<oJJ%m0"P=Z/C2HFjP%I%\taAqCTG<K9l_P>@iB2CHROltaW@rfLamkq,jY]i:A,G8.TFThVaZm/C'!W&C7:en:"M!rds!/(:P!)%R^~>endstream
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1764
|
||||
>>
|
||||
stream
|
||||
Gau0D=``=U&:W67f[3&Vc2e"iG\NUiSAXTZ3/QA'BZ?`Z8>8fj$45CLFOK('"V6a(,a2)9dl&Lk078+u$n,F5ruWinSr_h)PQBeuRK=-j^uu;R2<oVTZ'*Po`::Ca^uZ'rCatMW*u"']=4fbhO'4^Pi`kOMdTI)lZF(SU7LN23JU']gcP6k)!\YjI>_GajRAo6lKIWBZZQg;m_W[+:i#Q-.N,X0u0]JBFUL8Y"0FIA6;596Bni*,Ui%,?>SIHaoF*q_f1kYgg8[qAKPoa!BPn]LJ!K78i=A=AiL,%jDgrT+[K=[@_&<Dl;9fHUk8rOCE14a#:/nLtY5nM)H:gl&f#_I/G7+sB#I#$>q)MOOi7[`^!W:,Fecn.gbJ0<XeFWVA^iOZu!I'6k7HbuP4Z!J%a.u503"a-:b?:>P$ena($KJM<,7BP0<)UCDOiS8J]U9t0j'bjYtM0k2kT%A3\CLOU$(%u<X9T3?0[[-jaDX*9kDr-sI67gA]`2rL"T;M^E=IBAQ!:u&n)0^VcprRPNX:>/G3+j20n-HFhS^RZ40bcgm'OrKU&##!7Eeq7BXN6p6QGFifA-Z.Q5bdt@(V05%V;^de[9iR![.#kHC9)FL<>nrog$gt^l,k%[WeFUN\I@Qj<)0(aptOQ<r],K.lL^tr;Ai`0X!I]BC`:6*I[b@MM4hRrC7h8W&iAgEXRobod?d*g.PXCUQU**"a<o)O6:`34hQMHT*jIFIedB."=N"H<0Aif^lH^Y;?VT5p_&ST8ke<'b2TH)H`BBduQ4YRi$.UegQ*/n^gaZQ:R!['WAtWf.N^.ZHpoYjQ"3k(;?!:I^a&E@Vg];)''B[qV?_DfTlU'(>gI&\/`us0P?QRc^b8/_>]5HHpM%H9!l`Dg?N38Pj9XertG)=Vdp>*HeM.=*QNnraX@hf4<>\/4p;+aR0)*D(fYUnd;SC_4Sie:m[i/[,hoI\SCh%5(LeK_PG8Cq'ia?*=QBFYK!ld-4,<2jE<dD53oHis0U$tL<QO#Mk,i%7oCBp)35G_<_(9l_a72^BrML*B@(bRG'<+-jV4>hC`<0'-'MbR?C``Po?/&P9K/B\Zg%5>7eES:/gZ9-g((X69nnUECY6qJW1Ba<4Ni.-[5[BX-'!<-j[&;dFQXRP%W8Z`EHrf\S*0n"!r/![Dh6P71>LPArb!mC(?"0Fd]V:j;Y_=1q_:(\rlM%k*(^OoAoLJ[:OPNfVX;O\]V(J@CI,d8!&FV_j)&?Gd>C5HZ?,b+[s&E9B?6Z_6u.d+,Q@1(3<uDI6jc16\9Bf!ILs[uU8Q6Srqs8gA=[pR'^\S^8s\<",%sK+ClG.%+YYZ@@AG0BKU#e^BC/&#S@qP>!9IBF2OPAf9ep=:_UuZk7XHh27Ek5pD).L`Fe\3ErQ<Hb!*YUfI9%6u93p?UZ`fj'R0[1$=Z;/g^GkiX?U=%),,3_8_*Q_FiEEM;peXaGYaK6bYBY=<-d$9g%\\"<a`I5^YeH0VB1YQ=978!mM+<BUS&m+YZjpj-qmREd[K^Ia,0`gg!$I`A)u>$(l&gh#l(k0G5B[g9:@cQAJ;?]7#$,?/Z(H?sNI*EoPsH:1A%mbkrcF+HB.nKl]E!h]$CumbP%#`*@VmL@h0%'Z.HNS.pZ)mue];&f(<:Ur-/t/j62IZ>>MXk&L4olRej2<f'"f+`FsX!W#3U)ZbtIM7b:+d=W7Q0&<88(iK$2RikfA@<,(#1:DH%pCn-0%pd5eeGB,ep\Ttc<5*$+6oBSQs)Ln#jo~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2029
|
||||
>>
|
||||
stream
|
||||
Gau`T=``=U&:W67f^VXIPf/.Zn,iR<,Yc]\S2?"ip;&Np6p_M<?6LbscEH[<4Cq,KRSG?cU&Zh>hJ\aT@,RO>:Ho&.5(i)M$C&AC!Rr;/bg,2/D"MR4CJ*>)NlO6hNlMI8%1CM@m726.$kk&.4Hbe!^B?b.ZV^c^Sr1@)*Q#A-E=q0H,E?W@KuIc6cNl=tDJ=a\/M'sDC0)Hr*NN[ZZ]7+rp[Ke@U7*<U24>bEioK.=`nNsW:jj6c:ENX,k7^BUO(8k3Ca#D/EO;'WN7s;I=)OBHK7&Mf)Hr1k`sFA=$&^/U6b<e!)UB>UZY\Q$C8kM+9-g?N>UYA1XR>)!NWES]?bXHa]4%b>`_#_<AljD#eNVL;XPP#u)O:pMnG^\Ufo6YT#UEr+HDR\Z8XdFu?:2;S6K[%L8%5;X)r)Z71QaF1n/&g2DN]#QafUL+'$><UB.b$B@4C<4e-***0J6;7'Zgso8pGN.rj#B(WA2Ap,h.35ErmBX&`;L<kJs/m*S)L:j7JN-('g<6*'CCc?flA`Y[SP\Wh<+PAtM#MR#@^1h&i97cPeZ&mc.;MB#WGF>1E2_:ZH.3MUag(Gqc?r:]JkHJP>I;+I%l'>II&c0g&MeP8?TI%6\:rbF27+^#8%\9'FQWQL^`0]VQ:%l\,-ZF'hE+qm0#-\qah6l:^OHE%#%FjF43DYY(m3Rc\&[8K*7T!meb79[::!-)T?Xhh5b[Wu%8<89<GC%=]+!-)FApPP7rZ"[jeKfa4aD1fLc4CfBr]M[pI0Gj(&@bmD%BOB_*iCL<\AAiiN*:i,W?=J/QLq`IYu=B,lEZ]9^[mh&FOA&P_o>P[qG!Fg@BTH*DSf<Tsp'tb.IE6+S.jBQE<.]lqY":7ASam#us=hlZ7f8D'5h*Xueg/I$&r2kV18"g4:Z1o9H6:D;,=eI6:&!;\gK3q&pU:!#c)6G'J-!Zfq<W<@V;L_X#a.:5A[sk8@?D'&u>M&K4Yl&>o#L'D./Cc;*Vg+'I;VnO=ANGKa;l`<b5s9uiGO;uk#hp\72XYo'O"Anb4)2)<N@\tGKABS_dMu^uL>1VE%?3U;qR0*XV$>DMRQXCSL+d5-gKmV(C=)AkX)8N/-uh99h)7<+7OQC"0$?<qN)&"$HtQ1JeK:n_fUo-X]kr6W_:-dd#d51h08Q37r6,2j,d,7VL8"-!+k@PmP#-%>M#3Ze'`(NXFW=2_1^[d-O@;0drTjXiB';,e]nlF3mXR][Sn*lIC5H<%4!N$[?:RGT+n-6:"<m*3Af\t]3-o8+GOTIPMX?0nYu5]X$]R_%nQ5V,E%(/dZ(&CQnCTMabf=EaB'#\Tq7.39]MqWW6A3SK6"<9'8H\'?/d.1Y!J4c)#/$7HX*0d7JcVXE#7kA/P@67A\j8dIEL;Q<_!BkPe$3"T#uK;0@.[_o$Ri27RG7me9\8O>qEI4c<h[fo2E0"gmI[,=BRs?*E/L3aR+TO:.Pe&N2#.)JLf0_0-?o;77&`gq9?F0$;sI@NXTn0Q6D8SMU/8I3FNf\=ek\aYcU8fqf+B(==i2kN1_/Vf)p7!4\Aj(i`chC_3leTZ9H7S`SBCAXAT?#&W6_<]J=aRmbAFeV0p'CY"+5"'Tl2QHi3H7X\6TDFq9h+!Ae/i8%YN6qGSn5s[uaudlSZ-<_G;m2l%BtQJ0T+dPOc)`YM`<C(QWJNA_G!V?%0DrHhabgASA59h49c9]dX#d#%.Sg?P[&qcU='n72AW1hPYY1.4$F$ZNjn77"Nc6mAu5N1A/>:ASl:"kZu4$d<pPPDHS\F3f\P,%bEDpD%/^W##itd-:._:$;7aU<]FUVAA15hMBD3r$IgqZZL@bMbe`?NRp8$Sgmb[MlQ0'SUr9-pdoP%]@FOPpTjH?SP<gFLEJ>lAn(!\E<!NoWpj%TPZ6![meVj_[i^6`f?7dWWZ%+0M)B9i8dF+Y#keFF0f/F4o']C6SN@eOj_o0M2`tC7,@&$^tJ<N61;0mYc-?&htK3G?`SNf:@R+2aE5H!hMhu%H`'Is'!BEt;^!#FGHD?~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1674
|
||||
>>
|
||||
stream
|
||||
Gb!#[D,]1K&H;*)YohG<U0+^;bC$n8jEc#%=,s(12o?%K*0HUJQEd!fnEY#SG="Vg42[QjE@e;:ofTk?>u@ir6d+q;"n6C:UYdLs#1I@\S8ajEL@Nd)[:h/V1#`?f^`UE<^@4/.ZO!sQi)/_+_KGtbg"]1N%\,3h`rfFHh"QX:J`t*_M[S95m"W$CQ&nLPDYsAEdE$uA46F+B:hm=7<APtXc1$<S\Js$bm0_PZ<7O.M(.D?"pY,&"fq#7lZFe)ops?1OT&67+IYQFj\pfWDRLbQ[dd9:rZ>C7PZ]2j3h#Rg!AKEf@nK#VG@KhBo.@78q,b1hb<&#B-567Sn80YJ@#&0rQ<*6q*lO6hA9`cE3ifEaIA1H,kl?K23CR#^R<LAqN0YN&%lTJ8dZH55d0<Lg[2$]C_?mDut$E`5qJ@9(UY_[CAn`gB,?VfOmHTN*PIQdW<Dn=!2q"3q!pZ'HG0;$:OT>pmPYhIlld17^doc5;UB$_AnrV?K[N(m*TZ$G(_@A>bX/M6"oN-*fn:K[R^bn4SYl:e8<#YJmP:R1Dlb`j[.A^,;p[S\nq`-FLBfc"*Lf@pq?h`r/-bJm&uSt)jUF3:h%cUrKiS2A_*NU$q:Xlg3dhG(?:mA$),50n$]cVn+7*p_hL&VD$fD.GEj`@=2*Z9*l*8'WeSI$<mWOchJ1Au^b-"-(+,b*KOrA0S3c-53o@$/0o304h4'aFfo7%!,[-b'o!JL)XAWY+]:%J1F#<E,-'J0qZ'CS17*n^bJo]%lt'@%8_'m$mJ`_9VA^Q>"k#a0]iJs.J,)fLKEef?>hRqpP-#tB+M>hn2a19I%b055"ZnW+Nhm''^A[:X$XY[l?e)b)Tjm"3kS/3r\*Ae17p8GBtq;W((foF:0V&'6S"jR"mq*p0C;,2?J6"';eC6^?F[XEgEk?jb5g18o&r?EG0kOXGSR=J6Xt`o8_OXNn-rbB*)MW*4"eREZ!6lb`R*\%B.YGK)6B64(Q&oPVhAuF@P['B[$]ZOAIU2M$^6sq"U=rb9K<omT#U&(#]Ls>$XeshX&6T+jd"&*W'>oW;JUe:N\EJ'\IXh@6H7EdqB567U#K$_:P3h=@f/l+!-B^R#4-QtY+G\7b@DA7o,P/,M0p3]GDYMXi[#QomkAPe\u/WZmie;p?FViiR6(4\"Ic'i5S@S^,&cDKQ(Fs7e4G4f!UFj$6QM6Yfb32ule.?nVU%RrY[&I<]/4*`LE"WR=Z@G:rGXkGX$2-+7Eu59I4Cpj,HuURD@CdE)">skAb@T@:(+2aHuF"I^"P+1=l8ZHL*S3`P2-ko+grP^Jprt>14co-KENp!&eW49]q4;kK6k*'$,%b]Phr\q2BAbWl#%;+;\#j#3(5lPLBuo]#riH!d;rI_,jeL20h1I/fkF*Dl#2Y6R>$QC\2Ws\(@);$=tS^UaFd7HG-.u5*l2)-#^:s(m<lA]PBG(EF&bm@gsqT")n>RTXjC7RBB:PTP1A6KK'<lANJsL6<gE,>,Ddb*6NHF$KuRkZ%HJIL*l'lMR1fV@GRl!Ik6Agfj$A=i*)CC/IH@Cb>`!r%<AH\VN`F!)c;Z4`qCK<!Xk@$1iVsAT?S(-f=;0g)N`D)6(mWaCT74/'$f@D4ZKe.tT441P]6%\7_#-YM7>L=n=YM4:"b&=[Qs_$(3CeVm+&TU@li~>endstream
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1979
|
||||
>>
|
||||
stream
|
||||
Gau`TGuNHe'`['_if2^:Qk\,`X49WdSVDg)8SCGAUsm8MA1Oj#JLh+!L&f,8n'gRmM4j73>k5&S^$k_J$n-Q\rR[M?m%$EA"Tud'A->R;!@J,VJ'p@GY`dGo`,`E3^uU_67QL$^#c#VO(c5$D)D6eUg^-%D"Q:]q].iUXgEqs@3mD!X&J!&'Rk<U'K#f-8:j[Z3WG&q>]uN,\_K!($<UU7$dm`)hE6bS;W4&AU5uGlNkV-I-cJ33h#c!A3Y<s7=!aO6`QMc_$bt"rnl&6Zn/G!cgNXiN!Y)YCd:)Rp(Y/9Q=QSWL[Vj%IE-Cdc'Q`B+5,p\DC@*mY[P>aGd]Rc,(%PdK)1`ntA<AO(kHSQS<h<btYgENN\Ll1jmUs@o%S.hV6c=\W;g4lj>'=A:s#_m@n]%[53mt2D2Ij#RjlOC?Qb#_52pL'pt0Ce)HO$^@4U'*F5l,shM/YSB5[.'rHg>mK6G3'5K0'pG48[H>Ga+N5VMaZ8#)<?^ap]r_5@;[Yma5UIIisp'GX0HRk[<3sh9aA@mh2B39`?8ZL]@Sf:8bG(No1tu"W/TD_g9`X7[Z-fGKNVnuRnU"P%gK_:V)1GqiHDF/:O[%?%+i:`BT``G:`5e@'1cc@r(EoEN04J&mcoJkU3!:@PaC@Sj*VU:;]cf&*Gk\![jLGIL+*P+f&Vn=Ec!a=R]AOuIc-hI'HP>q_O,dto_jar\:+"G<PIQ-Tf^,CHl\:`q0KMLTKh=(JSC>'I^Z0T6X>UOk9q9.:pMHY5EgYJXe$b'6.Mu$R)CarVcM_E@@G"A*bQhY(0/FKl/V<@LbT<I)k8j-&s^_)&-\cC9SM.PI(Y$s.`N[sB'O/dp?%EAG]irX9j'@Q8=Q0lD$Xc?YRMT.]_quB/]p(3IW!j44VPDQk1#=Zq0h)'_ZR&sh>+]Gr71d%4Xp7]30m1nfEa-[OtlV&e<pZ!0Z+_KW"]WQc]$\c&[IRn#9TZ`Uu"L6AP/s4>)DRYn^ZuHQ)F&Iji5/J(i6>$VuAZ?GUNoJW(pu_KsrV13j1"a]oRi.:Wq2-6IaT,c(]9!,L"6D@/3r!_Bu(i.qn+"*YQGJfSa@..@R*MNI"="e8(HCk=)o"D.*K#$<TXM>&T4EY1;Hg*A0$"0L)@S&t?'L+g,N>(Ab<./=D1?.[Q&T8MiW0EZbTubO>Md)q?rr.(qDs<pPs@$]DM]\/"NU17KMaR]=fUb/(_kR9>d+cdQR+!hCTDZT*fIaGYY!3]q1T?a<1sOmi)J___Du^HFZ3j6*W)6AFtdbU,mqqqcWZH&'cKVI$nk_7:,UUd\e8n6RE4$?kK,`],pcI.7DX-`?2U<Q/L]NeT,($9CD1B3A6JEu]I?^;Ac+b^Gun#3I&;*J)J*fU+/DL8/*:`.Z;`bGIuti$s7eS`+@"b<S07&/41d^7IS,R-F>,pXHA&hcV03[;"1!l/[-,Fl2Mq":h.^.0CD6;YkU%BWXEf[*5o%s8W)V11G:Y[-$ffDEc5`gT']QY#uCf(=YGArLmQIpP4'M2Ss.&lPn_'(/W.)]_5ikUL:$`O\2sN]V%`Ckbf(O,l$d.JA`CE&p/WI&]Rc=&TYr8GQ>VM@R+aW6.AsrNNn]271aaM7E>O.p^Sog=Cpq2TC+j*hZ*NAkPuu?]fb.9HN3Qhro^&c#Q(-#n,J6SWug9%cZhHYmtEN;-^.\hpR7!LBVV/*q7WXTNUrG7]JL'9I_<#8LD=kdBFC4s0b9XgDld:J'::M0e[ecb<`k0[_a4U\[h7c!YoNY7gg,IPP:VW9qgEmQP:4\!%8,kSCXD0]kj^lX3$3*qro2#F!V!eXm3@GU@k"OIZuHj8*(N`C4OCs_\pi&R/@g@Jb].I\IL)id`F?1^a(?$g@*:hhcm&O/BCMroYagmcT$<E4LMcu#J]4j7domA_^-SfJbS*ca;3JdUM,\^1[#&ab=JfMES9V3=hi%MDjC,fW!n\;3VL[OfP*ODi)h-%lZN~>endstream
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1615
|
||||
>>
|
||||
stream
|
||||
Gb!;e>uT`R'Rf^Wgp_EL,`mZZ;RI#r3\sOA]/W\PCg"0r,St,cdS+iiep5p*^:0:+=U7R*MF(1V8od-kGFrrf[@ol>48nP:!1/,4J%!1:TK`RkBf2T6$eP?@+")5\B?RP`kK^UID7=)!]LJ*VUW\r*fs_rl:_7skQQeh#bXNAs6p//>eW`_(3HdVPrsAP:ROi[T>%R8.Gp]`N-tI(gQa8N&\B[R?*D4md<7c0E9b/eadr<X4c?%TbD(F35J$G&X]=kUEf=!frmb"RNRh;tdX1/>;i:IE/?^!boJ(;gn4??4V\5EX51['7b?P98ghL#o(>c/YFX=7].+)e9A0Q9&X`*e_/+pRe`PRX"sEYPZ/iCAK_)^LY4`e^0O`(+G9Lfj'0h/44:pI2BF]g&1^0eDd@.`E(tB!;B-L>k51f_g<j$t`3$C7UeU=QbTI'c[1s6WDB(@oV<4LoBSqhNC)4@48tmGBVbZ*j4NlY,]r5AH&C;fa3QS$hNJ-#.2hOY%G?L&tAVV?G#;R2$8!r7RUV?[-%>j[A(2O-X=R6eb+rgh#m58q`5Ym6Pr;HZBX#J'mt[bl'1IVNNR=fg2^N?>*kDs&FNWbZdJ7^#OnHK*K[+(7oDKRLa.cNGIQ[>#$_d,_r?!IK"JE_QHmVDS:[Bd?BAGncsHi%+Jt6ej"ALE'<-Yla;rbTJ%ALY(iDGjid.+'Z7!^cKYE7"rhE&+[.$Ld="%\hat8:P[R.ZBYh?89B$2t>QJ^&&/oTBloNTMdn!H_;D"MT!Jd:?[+,uBf0>#?';p8O`9cb,6Q;3ISQsd!<k,<?nW%lRElMB`E>IUmeA#>1WUe5l)BuW!6&-g(u'IS)L(r%#U/d5rt2S0oq87]Qo+qC5pdk]T5UGF&=)6la1U*(Y5$'-5!Bb+,uUjcj%??&>um6[IidI4r6Pe0EYRbiY'd>5h*@NhUQO;\_fGkWIC4KDQM2Y5^0q"#]W@hN@=BHaoQPVh4<GK)nlVklrH!LDH&Xk_d?4fd%R[SrgQ5%!19B'OFGogppYA<q`5(m63@3?>_n]`S#C"*a_@V]MccqW1&W^=LIQcf3r4HgXZYUOtiV&H7E+QQo@H%d'I]I<Z8*^[j:ui34:+.Xtn2_C4@DD*LOh2316bP!Z-&GQF:Xi7g'MgE2hdHN+<gg;V"!,X@3>/uX)>^%ML[a6rM>p"LFNJp#@&k&1HZ>3;=KQOKeZ>-?UoD3YOMD(fgPX(:"l#icdqBD'G;hB=a,B!*Jd/.NL9_3d-4:]$[Rg5]I[G3%X^&I_%K,Q,[*>-LhcMkiKUI3r8T+NY`^QPVS<5NLsgn(m2(WPs>ER&Z7BSgRu.`K2#!%)2H4]c)aqOH4q4qsI%97u/W)q@u=>Bm\d!R8u!XE\\'BS>9</cjYC?EoJgfH@\P,X['pBXcZM1"a]F$kRa#6+YoTZX<&V<KC!jYaJ>p^g><I'P<9S"1!;311Gn]"U:D\L:cNGZ(;6_/Z"R0LlUr*ZLQkm&-u&Eof%$P]g#aXd1\9OEi<qledMdNbT_/"fJ$H9umMcj+KJWhMI3GUCI)#GCAR%_-$j'<OPtCA^%Brs\Bo)uGBMl<&4X/0AQY]3%rXXVNqFMBkr!0]:V,[~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 24
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000132 00000 n
|
||||
0000000239 00000 n
|
||||
0000000351 00000 n
|
||||
0000000456 00000 n
|
||||
0000000661 00000 n
|
||||
0000000866 00000 n
|
||||
0000000943 00000 n
|
||||
0000001148 00000 n
|
||||
0000001231 00000 n
|
||||
0000001437 00000 n
|
||||
0000001643 00000 n
|
||||
0000001849 00000 n
|
||||
0000002055 00000 n
|
||||
0000002125 00000 n
|
||||
0000002417 00000 n
|
||||
0000002517 00000 n
|
||||
0000004176 00000 n
|
||||
0000005790 00000 n
|
||||
0000007646 00000 n
|
||||
0000009767 00000 n
|
||||
0000011533 00000 n
|
||||
0000013604 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<0292bdcceb39b20ff85637fe2f02e122><0292bdcceb39b20ff85637fe2f02e122>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 15 0 R
|
||||
/Root 14 0 R
|
||||
/Size 24
|
||||
>>
|
||||
startxref
|
||||
15311
|
||||
%%EOF
|
||||
404
Les15-MCP/Les15-Slide-Overzicht.md
Normal file
404
Les15-MCP/Les15-Slide-Overzicht.md
Normal file
@@ -0,0 +1,404 @@
|
||||
# Les 15 — MCP (Model Context Protocol)
|
||||
## Slide Overzicht (Klas A — 3 uur fysiek, demo + hands-on)
|
||||
|
||||
**Lesvorm:** ±1 uur theorie + live demos (veel koppelen, weinig zelf coderen), daarna ±1,5 uur hands-on
|
||||
**Demo-MCPs:** GitHub MCP, Supabase MCP, Vercel MCP (bestaand) + Polderfest MCP (eigen)
|
||||
**Vervolg op:** Les 13 — Agents (Polderfest agent met 5 tools)
|
||||
**Aansluit op:** Les 16 — RAG + Embeddings
|
||||
|
||||
---
|
||||
|
||||
## Blok-structuur
|
||||
|
||||
| Blok | Onderwerp | Slides |
|
||||
|------|-----------|--------|
|
||||
| 1 | Welkom + Vandaag | 1–2 |
|
||||
| 2 | Wat is MCP + ecosysteem | 3–6 |
|
||||
| 3 | Vier MCPs koppelen — live | 7–10 |
|
||||
| 4 | Polderfest MCP — eigen server | 11–13 |
|
||||
| 5 | LIVE — De drie chats workflow | 14–17 |
|
||||
| 6 | Lesopdracht + Extra + Vooruitblik | 18–20 |
|
||||
|
||||
---
|
||||
|
||||
## Slide 1: Title
|
||||
### Les 15 — MCP
|
||||
|
||||
**Visual:**
|
||||
- Background: CREAM
|
||||
- "Les 15" in BLUE
|
||||
- "Model Context Protocol" in BLACK
|
||||
- Subtitle: "Geef AI-clients superkrachten via standard-tools"
|
||||
- Iconen: GitHub + Supabase + Vercel + Polderfest
|
||||
|
||||
---
|
||||
|
||||
## Slide 2: Vandaag
|
||||
### Drie blokken + climax
|
||||
|
||||
**Wat we vandaag doen:**
|
||||
1. **Theorie** — wat is MCP, waarom bestaat het (kort)
|
||||
2. **Vier MCPs koppelen** — GitHub, Supabase, Vercel, Polderfest
|
||||
3. **De drie chats workflow** — alles samen aan het werk
|
||||
|
||||
**Climax (in drie chats):**
|
||||
- Chat 1: GitHub MCP maakt repo + voegt remote toe
|
||||
- Chat 2: Polderfest features bouwen via Cursor + Supabase MCP
|
||||
- Chat 3: Vercel MCP project + deploy
|
||||
|
||||
Vandaag is dus meer koppelen en zien dan zelf coderen.
|
||||
|
||||
---
|
||||
|
||||
## Slide 3: Wat is MCP
|
||||
### Eén standaard, vele clients
|
||||
|
||||
**Model Context Protocol legt vast:**
|
||||
- Hoe AI-clients verbinding maken met tools en data
|
||||
- Hoe servers tools, resources en prompts beschrijven
|
||||
- Hoe data heen-en-weer stroomt over een transport
|
||||
|
||||
**Open spec — geen vendor lock-in:**
|
||||
- Gestart door Anthropic (november 2024)
|
||||
- Cursor, Claude Desktop, OpenAI, Windsurf, Zed ondersteunen het
|
||||
|
||||
**Analogie:** USB-C voor AI. Eén stekker, elke laptop.
|
||||
|
||||
---
|
||||
|
||||
## Slide 4: Het probleem
|
||||
### Voor MCP
|
||||
|
||||
| Voor MCP | Met MCP |
|
||||
|----------|---------|
|
||||
| Cursor: extension API | Eén MCP-server |
|
||||
| Claude Desktop: plugins | Cursor: werkt |
|
||||
| ChatGPT: GPTs/Actions | Claude Desktop: werkt |
|
||||
| Windsurf: eigen API | ChatGPT: werkt |
|
||||
| Zed: eigen API | Windsurf: werkt |
|
||||
| **= 5 integraties** | **= 1 integratie** |
|
||||
|
||||
Dezelfde tool, één keer bouwen, overal beschikbaar.
|
||||
|
||||
---
|
||||
|
||||
## Slide 5: Architectuur + primitives
|
||||
### MCP in één plaatje
|
||||
|
||||
**Drie rollen:**
|
||||
|
||||
| Rol | Voorbeeld |
|
||||
|-----|-----------|
|
||||
| **Host** | Cursor, Claude Desktop |
|
||||
| **Client** | MCP library (zit in host) |
|
||||
| **Server** | GitHub MCP, jouw Polderfest |
|
||||
|
||||
**Drie primitives:**
|
||||
|
||||
| Primitive | Wat |
|
||||
|-----------|-----|
|
||||
| **Tools** | functies (AI doet iets) |
|
||||
| **Resources** | read-only data |
|
||||
| **Prompts** | templates / persona's |
|
||||
|
||||
Vandaag focus op tools.
|
||||
|
||||
---
|
||||
|
||||
## Slide 6: Het MCP-ecosysteem
|
||||
### Honderden servers beschikbaar
|
||||
|
||||
**Top servers:**
|
||||
|
||||
| Server | Wat je krijgt |
|
||||
|--------|---------------|
|
||||
| GitHub | issues, PRs, repos, commits |
|
||||
| Supabase | DB queries, schema, migrations |
|
||||
| Vercel | projects aanmaken, deploys |
|
||||
| Filesystem | lees/schrijf in folders |
|
||||
| Slack / Linear / Jira | werk-tickets |
|
||||
| Brave Search / Notion / Postgres | enz. |
|
||||
|
||||
Vandaag koppelen we **vier** in Cursor: GitHub, Supabase, Vercel — en onze eigen Polderfest.
|
||||
|
||||
---
|
||||
|
||||
## Slide 7: Vier MCPs vandaag
|
||||
### Het overzicht
|
||||
|
||||
| MCP | Type | Auth |
|
||||
|-----|------|------|
|
||||
| **GitHub** | HTTP (remote) | Bearer (PAT) |
|
||||
| **Supabase** | HTTP (remote) | Bearer (sbp_-token) |
|
||||
| **Vercel** | HTTP (remote) | OAuth (browser-flow) |
|
||||
| **Polderfest** | HTTP (lokaal) | geen (jouw eigen server) |
|
||||
|
||||
**Alle vier in één `mcp.json`** — zelfde patroon, andere URLs.
|
||||
|
||||
**Visual:** vier logo's met een pijl naar Cursor.
|
||||
|
||||
---
|
||||
|
||||
## Slide 8: De `mcp.json` config
|
||||
### `~/.cursor/mcp.json` — globaal
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"mcpServers": {
|
||||
"supabase": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.supabase.com/mcp?project_ref=<jouw-ref>&read_only=true",
|
||||
"headers": { "Authorization": "Bearer sbp_..." }
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": { "Authorization": "Bearer ghp_..." }
|
||||
},
|
||||
"vercel": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.vercel.com"
|
||||
},
|
||||
"polderfest": {
|
||||
"type": "http",
|
||||
"url": "http://localhost:3000/api/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Eén patroon: `type: http` + `url` + optioneel `headers`. Vercel doet OAuth.
|
||||
|
||||
---
|
||||
|
||||
## Slide 9: Tokens regelen
|
||||
### Drie tokens, één OAuth
|
||||
|
||||
**GitHub PAT (fine-grained):**
|
||||
- github.com → Settings → Developer settings → Personal access tokens → Fine-grained
|
||||
- Repo-permissies: Contents + Issues + Pull requests (read+write)
|
||||
- Token begint met `ghp_...`
|
||||
|
||||
**Supabase Personal Access Token:**
|
||||
- supabase.com → Account → Access Tokens → Generate
|
||||
- ⚠️ Account-level — NIET project anon key / service role
|
||||
- Token begint met `sbp_...`
|
||||
- `project_ref` haal je uit Supabase project URL
|
||||
|
||||
**Vercel:** geen token vooraf. Bij eerste gebruik in Cursor → OAuth-prompt → toestaan.
|
||||
|
||||
---
|
||||
|
||||
## Slide 10: Live koppelen + check
|
||||
### Vier stappen
|
||||
|
||||
**1.** `mkdir -p ~/.cursor && nano ~/.cursor/mcp.json`
|
||||
**2.** Plak de JSON van slide 8 + vul je tokens in
|
||||
**3.** **Volledig herstart Cursor** (`Cmd+Q`, opnieuw openen)
|
||||
**4.** Open een chat — onderaan MCP-indicator → moet **4 servers groen** tonen
|
||||
|
||||
**Voor Vercel:** klik op "Needs login" → OAuth-flow → autoriseer
|
||||
|
||||
**Visual:** screenshot van 4 groene indicators.
|
||||
|
||||
---
|
||||
|
||||
## Slide 11: Polderfest MCP
|
||||
### Onze eigen server
|
||||
|
||||
`polderfest-mcp-finished.zip` bevat een complete Next.js-app:
|
||||
|
||||
```
|
||||
polderfest-mcp/
|
||||
├── app/
|
||||
│ ├── api/mcp/route.ts ← MCP server zelf
|
||||
│ ├── lib/supabase.ts ← Supabase client
|
||||
│ ├── page.tsx ← landingspagina
|
||||
│ └── layout.tsx
|
||||
├── package.json ← mcp-handler + sdk + zod
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Vier tools:** `searchBands` · `getStageSchedule` · `festivalStats` · `getWeather`
|
||||
|
||||
---
|
||||
|
||||
## Slide 12: Code-tour
|
||||
### Eén tool — anatomie
|
||||
|
||||
```typescript
|
||||
server.tool(
|
||||
"searchBands",
|
||||
"Zoek bands op query, genre, of dag.",
|
||||
{
|
||||
query: z.string().optional(),
|
||||
genre: z.string().optional(),
|
||||
day: z.enum(["vrijdag","zaterdag","zondag"]).optional(),
|
||||
},
|
||||
async ({ query, genre, day }) => {
|
||||
let q = supabase.from("bands").select("*").limit(50);
|
||||
if (query) q = q.ilike("name", `%${query}%`);
|
||||
if (day) q = q.eq("day", capitalizeDay(day));
|
||||
const { data } = await q;
|
||||
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
**Bekend:** Zod (uit Les 12/13). **Nieuw:** `server.tool()` + content-array.
|
||||
|
||||
---
|
||||
|
||||
## Slide 13: Lokaal draaien
|
||||
### Drie commando's
|
||||
|
||||
```bash
|
||||
unzip polderfest-mcp-finished.zip
|
||||
cd polderfest-mcp-finished
|
||||
npm install
|
||||
cp .env.example .env.local
|
||||
# vul SUPABASE_URL + SERVICE_ROLE_KEY (uit Les 11/12)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
- Landingspagina: `localhost:3000`
|
||||
- MCP endpoint: `localhost:3000/api/mcp`
|
||||
- Polderfest entry zit al in `mcp.json` — herstart Cursor → 4 servers groen
|
||||
|
||||
---
|
||||
|
||||
## Slide 14: De drie chats workflow
|
||||
### Demo overview — alles samen
|
||||
|
||||
**Drie aparte Cursor-chats. Elke chat = één doel:**
|
||||
|
||||
| Chat | MCP | Doel |
|
||||
|------|-----|------|
|
||||
| **Chat 1** | GitHub MCP | Maak GitHub repo + voeg toe als remote |
|
||||
| **Chat 2** | Polderfest + Supabase MCP | Bouw een nieuwe feature in onze MCP-server |
|
||||
| **Chat 3** | Vercel MCP | Maak Vercel project + deploy + env vars |
|
||||
|
||||
**Resultaat:** complete deploy van een nieuwe feature, vanaf je editor, zonder browser of CLI-commando's.
|
||||
|
||||
**Visual:** flow-diagram drie chats → één live productie-URL.
|
||||
|
||||
---
|
||||
|
||||
## Slide 15: Chat 1 — GitHub MCP
|
||||
### Repo aanmaken + remote toevoegen
|
||||
|
||||
**Cursor chat 1 — prompt:**
|
||||
|
||||
```
|
||||
Maak een nieuwe public GitHub repo "polderfest-mcp" aan
|
||||
via GitHub MCP. Voeg toe als remote `origin` aan deze folder.
|
||||
Push de main branch.
|
||||
```
|
||||
|
||||
**Wat er gebeurt:**
|
||||
- GitHub MCP roept `create_repository` aan → krijgt URL terug
|
||||
- Cursor draait `git remote add origin <url>` in terminal
|
||||
- Cursor draait `git push -u origin main`
|
||||
- Open github.com → repo staat er met onze code
|
||||
|
||||
**Geen browser. Geen `gh repo create` zelf typen.**
|
||||
|
||||
---
|
||||
|
||||
## Slide 16: Chat 2 — Polderfest features
|
||||
### Bouw via Cursor + Supabase MCP
|
||||
|
||||
**Cursor chat 2 — prompt (nieuwe chat!):**
|
||||
|
||||
```
|
||||
Ik wil een review-systeem voor de Polderfest MCP.
|
||||
|
||||
Stap 1: vraag Supabase MCP om een tabel `band_reviews` te maken
|
||||
(id, band_id FK naar bands, score 1-10, comment, created_at)
|
||||
Stap 2: voeg twee tools toe aan @file:app/api/mcp/route.ts:
|
||||
- addReview(bandName, score, comment)
|
||||
- getReviews(bandName)
|
||||
Gebruik dezelfde patroon als bestaande tools.
|
||||
```
|
||||
|
||||
**Test live in dezelfde chat:**
|
||||
- *"Geef Maud and The Cathedrals een review: 9, episch."*
|
||||
- *"Toon alle reviews voor Maud and The Cathedrals."*
|
||||
|
||||
→ Polderfest MCP voert addReview en getReviews uit.
|
||||
|
||||
---
|
||||
|
||||
## Slide 17: Chat 3 — Vercel MCP
|
||||
### Project + deploy + env vars
|
||||
|
||||
**Cursor chat 3 — prompt (nieuwe chat!):**
|
||||
|
||||
```
|
||||
Maak via Vercel MCP een nieuw Vercel-project voor mijn
|
||||
GitHub repo `polderfest-mcp`. Voeg toe als env vars (production):
|
||||
- SUPABASE_URL = <plak>
|
||||
- SUPABASE_SERVICE_ROLE_KEY = <plak>
|
||||
Daarna: deploy naar productie en geef me de URL terug.
|
||||
```
|
||||
|
||||
**Resultaat:** je krijgt een productie-URL terug — `polderfest-mcp-<scope>.vercel.app/api/mcp`.
|
||||
|
||||
**Tot slot:** vervang in `mcp.json` de polderfest-URL `localhost:3000` door de productie-URL. Herstart Cursor → werkt nu **zonder** dat npm run dev draait.
|
||||
|
||||
---
|
||||
|
||||
## Slide 18: Nu jullie — Lesopdracht
|
||||
### Doorloop de hele flow zelf
|
||||
|
||||
**Stappen (basis, ±1 uur):**
|
||||
|
||||
1. Pak `polderfest-mcp-finished.zip` uit + `npm install` + env vars
|
||||
2. `npm run dev` — test lokaal
|
||||
3. Voeg vier MCPs toe aan `~/.cursor/mcp.json` (template in lesbestanden)
|
||||
4. Herstart Cursor — check 4 indicators groen
|
||||
5. **Chat 1:** GitHub MCP → repo aanmaken + remote
|
||||
6. **Chat 2:** Test Polderfest MCP met een paar vragen
|
||||
7. **Chat 3:** Vercel MCP → project + deploy
|
||||
8. Update mcp.json met productie-URL → herstart Cursor → werkt zonder localhost
|
||||
|
||||
**Geen deliverables.** Gewoon doen tot het werkt.
|
||||
|
||||
---
|
||||
|
||||
## Slide 19: Extra werk
|
||||
### Voeg een eigen feature toe
|
||||
|
||||
**Voor wie sneller is.** Doe wat Tim in Chat 2 deed — voor je eigen idee.
|
||||
|
||||
**Patroon:**
|
||||
1. Supabase MCP → tabel + seed-data via Cursor
|
||||
2. Cursor → genereer nieuwe tool(s) in `route.ts`
|
||||
3. Push → Vercel deploy't automatisch (omdat het project gekoppeld is)
|
||||
4. Test in Cursor chat
|
||||
|
||||
**Ideeën:**
|
||||
- `addReview` + `getReviews` (zoals de demo)
|
||||
- `addToWishlist` + `getMyWishlist`
|
||||
- `rateStage` + `getStageRatings`
|
||||
- `addNote` + `getNotes`
|
||||
|
||||
**Geen deliverables.**
|
||||
|
||||
---
|
||||
|
||||
## Slide 20: Volgende les
|
||||
### Les 16 — RAG + Embeddings
|
||||
|
||||
**Wat we gaan bouwen:**
|
||||
- PDF Q&A-app from scratch
|
||||
- Embeddings + vector search (pgvector)
|
||||
- AI antwoorden op basis van eigen documenten
|
||||
|
||||
**Voorbereiding:**
|
||||
- Polderfest MCP staat live op Vercel
|
||||
- Vier MCPs gekoppeld in Cursor — github, supabase, vercel, polderfest
|
||||
- Supabase project blijft draaien — we breiden uit met pgvector
|
||||
|
||||
**Visual:** document → vector → AI.
|
||||
BIN
Les15-MCP/Les15-Slides.pdf
Normal file
BIN
Les15-MCP/Les15-Slides.pdf
Normal file
Binary file not shown.
BIN
Les15-MCP/Les15-Slides.pptx
Normal file
BIN
Les15-MCP/Les15-Slides.pptx
Normal file
Binary file not shown.
BIN
Les15-MCP/lesbestanden/cursor-mcp-config.zip
Normal file
BIN
Les15-MCP/lesbestanden/cursor-mcp-config.zip
Normal file
Binary file not shown.
BIN
Les15-MCP/lesbestanden/polderfest-mcp-finished.zip
Normal file
BIN
Les15-MCP/lesbestanden/polderfest-mcp-finished.zip
Normal file
Binary file not shown.
BIN
Les15-MCP/lesbestanden/polderfest-mcp-starter.zip
Normal file
BIN
Les15-MCP/lesbestanden/polderfest-mcp-starter.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user