# MCP-server voor Google Drive [[TOC]] ## Wat je bouwt Een MCP-server die je AI-assistent toegang geeft tot Google Drive: bestanden zoeken op naam of inhoud, documenten lezen, nieuwe bestanden aanmaken en mappen beheren. Dat is bijzonder nuttig voor RAG-achtige workflows waarbij de kennisbank in Drive staat en je niet voor elk project een aparte indexeringspijplijn wilt bouwen. In dit artikel gebruiken we de officiele MCP TypeScript SDK met de aanbevolen `registerTool`- en `registerResource`-methoden, samen met de Google Drive v3 API via het `googleapis`-pakket. :::info title="Vereisten" Je hebt een Google Cloud-project nodig met de Drive API ingeschakeld, een OAuth-client en een geldige `OAuth2Client` met de juiste scopes (bijvoorbeeld `drive.readonly` om te lezen of `drive` voor lezen en schrijven). Geef altijd de smalste scope die je workflow echt nodig heeft. ::: ## Setup ```bash npm install @modelcontextprotocol/sdk googleapis google-auth-library zod ``` ## Drive-client initialiseren ```typescript import { google, drive_v3 } from "googleapis"; import { OAuth2Client } from "google-auth-library"; function getDriveClient(auth: OAuth2Client): drive_v3.Drive { return google.drive({ version: "v3", auth }); } ``` ## Bestanden zoeken In de nieuwere SDK geef je naam, metadata en het Zod-schema als één configuratie-object mee aan `registerTool`. De oudere `server.tool(...)`-vorm werkt nog voor bestaande code, maar `registerTool` is de aanbevolen aanpak voor nieuwe servers. ```typescript server.registerTool( "search_drive", { title: "Drive doorzoeken", description: "Zoek bestanden in Google Drive op naam, type of inhoud. Geeft een lijst van gevonden bestanden met ID, naam en type.", inputSchema: z.object({ query: z .string() .describe("Zoekopdracht, bijvoorbeeld 'rapport 2025' of een trefwoord"), max_results: z.number().int().min(1).max(50).default(10), folder_id: z .string() .optional() .describe("Beperk de zoekopdracht tot een map"), }), }, async ({ query, max_results, folder_id }) => { const safe = query.replace(/['\\]/g, "\\$&"); let q = `name contains '${safe}' and trashed=false`; if (folder_id) q += ` and '${folder_id}' in parents`; const res = await drive.files.list({ q, pageSize: max_results, fields: "files(id,name,mimeType,modifiedTime,size,webViewLink)", }); return { content: [{ type: "text", text: JSON.stringify(res.data.files) }], }; } ); ``` :::warn title="Escape de zoekterm correct" De Drive-query is een string. Zowel een enkele quote als een backslash in de gebruikersinvoer kan de query breken of misbruikt worden. Escape daarom beide tekens (zie de `replace` hierboven) voordat je de waarde in de `q`-parameter plakt. ::: ## Bestand lezen Google Docs, Sheets en Slides zijn geen binaire bestanden maar worden via de export-API omgezet naar een leesbaar formaat. ```typescript const EXPORT_FORMATS: Record = { "application/vnd.google-apps.document": "text/plain", "application/vnd.google-apps.spreadsheet": "text/csv", "application/vnd.google-apps.presentation": "text/plain", }; server.registerTool( "read_file", { title: "Bestand lezen", description: "Lees de inhoud van een bestand in Google Drive. Geeft platte tekst van documenten, CSV van spreadsheets of de binaire inhoud als base64.", inputSchema: z.object({ file_id: z.string().describe("Het Drive-bestand-ID"), }), }, async ({ file_id }) => { const meta = await drive.files.get({ fileId: file_id, fields: "id,name,mimeType", }); const mimeType = meta.data.mimeType ?? ""; const exportMime = EXPORT_FORMATS[mimeType]; if (exportMime) { const res = await drive.files.export( { fileId: file_id, mimeType: exportMime }, { responseType: "text" } ); return { content: [{ type: "text", text: res.data as string }], }; } const res = await drive.files.get( { fileId: file_id, alt: "media" }, { responseType: "arraybuffer" } ); return { content: [ { type: "resource", resource: { uri: `drive://${file_id}`, blob: Buffer.from(res.data as ArrayBuffer).toString("base64"), mimeType, }, }, ], }; } ); ``` ## Document aanmaken ```typescript server.registerTool( "create_document", { title: "Document aanmaken", description: "Maak een nieuw Google Docs-document aan met opgegeven inhoud.", inputSchema: z.object({ title: z.string(), content: z .string() .describe("Wordt als platte tekst in het nieuwe document opgeslagen"), folder_id: z.string().optional(), }), }, async ({ title, content, folder_id }) => { const fileMetadata: drive_v3.Schema$File = { name: title, mimeType: "application/vnd.google-apps.document", parents: folder_id ? [folder_id] : undefined, }; const media = { mimeType: "text/plain", body: content }; const res = await drive.files.create({ requestBody: fileMetadata, media, fields: "id,webViewLink", }); return { content: [ { type: "text", text: `Document aangemaakt: ${res.data.webViewLink}`, }, ], }; } ); ``` ## Resources voor Drive-bestanden Naast tools kun je bestanden als MCP-resources aanbieden. Zo kan de assistent een document rechtstreeks als context laden via een `drive://{fileId}`-URI. ```typescript import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; server.registerResource( "drive-file", new ResourceTemplate("drive://{fileId}", { list: undefined }), { title: "Drive-bestand", description: "Inhoud van een Google Drive-bestand", mimeType: "text/plain", }, async (uri, { fileId }) => { const meta = await drive.files.get({ fileId, fields: "id,name,mimeType", }); const exportMime = EXPORT_FORMATS[meta.data.mimeType ?? ""] ?? "text/plain"; const res = await drive.files.export( { fileId, mimeType: exportMime }, { responseType: "text" } ); return { contents: [ { uri: uri.href, text: res.data as string, mimeType: exportMime, }, ], }; } ); ``` :::tip title="Drive als RAG-bron" Door Drive-bestanden als MCP-resources te registreren, kan je AI-assistent documenten laden als context voor complexe vragen, zonder dat je een aparte RAG-pijplijn hoeft te bouwen voor intern gebruik. Combineer dit met `search_drive` zodat de assistent eerst het juiste bestand vindt en het daarna als resource ophaalt. ::: ## Mappen beheren ```typescript server.registerTool( "list_folder_contents", { title: "Mapinhoud opvragen", description: "Geef de inhoud van een Drive-map terug.", inputSchema: z.object({ folder_id: z .string() .describe("Map-ID, of 'root' voor de hoofdmap"), }), }, async ({ folder_id }) => { const res = await drive.files.list({ q: `'${folder_id}' in parents and trashed=false`, fields: "files(id,name,mimeType,modifiedTime)", orderBy: "name", }); return { content: [{ type: "text", text: JSON.stringify(res.data.files) }], }; } ); ``` ## Veilig houden Een server met schrijfrechten op Drive is krachtig, dus bouw een paar grenzen in. :::warn title="Beperk de blast radius" Geef de OAuth-client de smalst mogelijke scope, log elke schrijf- of verwijderactie en overweeg een allowlist van map-ID's waarbinnen de server mag werken. Zo voorkom je dat een verkeerd geinterpreteerde prompt per ongeluk de verkeerde bestanden raakt. ::: :::faq ### Hoe zoek ik op bestandsinhoud in plaats van naam? Gebruik `fullText contains 'zoekterm'` in de query in plaats van `name contains`. Dit doorzoekt ook de inhoud van Google Docs, niet alleen de bestandsnaam. ### Kan ik ook bestanden delen via de API? Ja, via `drive.permissions.create`. Voeg een aparte tool toe met een `role`-parameter (reader, writer of commenter) en een `emailAddress`. Beperk dit tot vertrouwde workflows, want delen is moeilijk terug te draaien. ### Hoe ga ik om met grote bestanden? Exporteer grote documenten in stukken of gebruik de streaming-opties van de Drive API. Houd losse tool-responses klein, in de orde van enkele tientallen kilobytes tekst, zodat je het contextvenster van het model niet overbelast. ### Kan ik ook in Shared Drives zoeken? Ja, voeg `supportsAllDrives: true` en `includeItemsFromAllDrives: true` toe aan de API-aanroep, zodat ook gedeelde drives in de resultaten meekomen. ### Werkt server.tool nog of moet ik registerTool gebruiken? Beide werken. De oudere `server.tool(...)`- en `server.resource(...)`-vormen blijven beschikbaar voor compatibiliteit, maar `registerTool` en `registerResource` zijn de aanbevolen methoden voor nieuwe servers en houden naam, metadata en schema netjes bij elkaar. :::