Skip to main content
Glama
server.js16.8 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { google } from "googleapis"; import { promises as fs } from "fs"; import path from "path"; class GoogleDriveMCPServer { constructor() { this.server = new Server( { name: "google-drive-mcp-server", version: "0.1.0", }, { capabilities: { tools: {}, }, } ); this.drive = null; this.auth = null; this.authType = null; // Track which auth method we're using this.setupToolHandlers(); } async initializeGoogleAuth() { try { console.error("Initializing Google authentication..."); // Use absolute paths - try multiple approaches to find the correct directory let scriptDir; try { // Method 1: Using import.meta.url scriptDir = path.dirname(new URL(import.meta.url).pathname); } catch (e) { // Method 2: Fallback to __dirname equivalent scriptDir = path.dirname(process.argv[1]); } // If still not working, use the hardcoded path as absolute fallback if (!scriptDir || scriptDir === '/') { scriptDir = '/Users/vilhelm/Desktop/google-drive-mcp-server'; } // Define paths for both auth methods const serviceAccountPath = path.join(scriptDir, 'service-account.json'); const credentialsPath = path.join(scriptDir, 'credentials.json'); const tokenPath = path.join(scriptDir, 'token.json'); console.error("Script directory:", scriptDir); // Try service account first, then fallback to OAuth try { console.error("Trying service account authentication..."); console.error("Looking for service account at:", serviceAccountPath); const serviceAccountData = await fs.readFile(serviceAccountPath, 'utf8'); const serviceAccount = JSON.parse(serviceAccountData); this.auth = new google.auth.GoogleAuth({ credentials: serviceAccount, scopes: [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.readonly', 'https://www.googleapis.com/auth/drive.metadata.readonly' ] }); this.authType = 'service-account'; console.error("✓ Service account authentication initialized"); console.error("Service account email:", serviceAccount.client_email); } catch (serviceAccountError) { // Fallback to OAuth authentication console.error("Service account not found, falling back to OAuth..."); console.error("Service account error:", serviceAccountError.message); console.error("Loading OAuth credentials from:", credentialsPath); const credentials = JSON.parse(await fs.readFile(credentialsPath, 'utf8')); const { client_secret, client_id, redirect_uris } = credentials.installed || credentials.web; this.auth = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); console.error("✓ OAuth2 client created"); // Try to load existing token try { console.error("Loading OAuth token from:", tokenPath); const token = JSON.parse(await fs.readFile(tokenPath, 'utf8')); this.auth.setCredentials(token); this.authType = 'oauth'; console.error("✓ OAuth token loaded and set"); } catch (tokenError) { console.error("Error loading OAuth token:", tokenError.message); throw new Error('OAuth authentication required. Run: node auth.js'); } } // Initialize Google Drive client this.drive = google.drive({ version: 'v3', auth: this.auth }); console.error("✓ Google Drive client initialized"); console.error("Authentication method:", this.authType); // Test the connection try { const testResponse = await this.drive.about.get({ fields: 'user' }); if (this.authType === 'oauth') { console.error("✓ Connected as:", testResponse.data.user?.displayName || testResponse.data.user?.emailAddress); } else { console.error("✓ Service account connection verified"); } } catch (testError) { console.error("Warning: Could not verify connection:", testError.message); } } catch (error) { console.error('Error initializing Google auth:', error); throw error; } } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "list_files", description: "List files in Google Drive. Can search by query, folder, or show shared files.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (optional). Examples: 'name contains \"report\"', 'mimeType=\"application/pdf\"'", }, maxResults: { type: "number", description: "Maximum number of results (default: 10)", }, folderId: { type: "string", description: "Folder ID to search in (optional)", }, includeShared: { type: "boolean", description: "Include files shared with me (default: false)", }, }, }, }, { name: "read_file", description: "Read the contents of a file from Google Drive", inputSchema: { type: "object", properties: { fileId: { type: "string", description: "The ID of the file to read", }, }, required: ["fileId"], }, }, { name: "create_file", description: "Create a new file in Google Drive", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the file", }, content: { type: "string", description: "Content of the file", }, parentId: { type: "string", description: "Parent folder ID (optional)", }, mimeType: { type: "string", description: "MIME type (default: text/plain)", }, }, required: ["name", "content"], }, }, { name: "update_file", description: "Update an existing file in Google Drive", inputSchema: { type: "object", properties: { fileId: { type: "string", description: "ID of the file to update", }, content: { type: "string", description: "New content for the file", }, }, required: ["fileId", "content"], }, }, { name: "delete_file", description: "Delete a file from Google Drive", inputSchema: { type: "object", properties: { fileId: { type: "string", description: "ID of the file to delete", }, }, required: ["fileId"], }, }, { name: "search_shared_files", description: "Search for files shared with the authenticated account", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (optional)", }, maxResults: { type: "number", description: "Maximum number of results (default: 10)", }, }, }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "list_files": return await this.listFiles(args); case "read_file": return await this.readFile(args); case "create_file": return await this.createFile(args); case "update_file": return await this.updateFile(args); case "delete_file": return await this.deleteFile(args); case "search_shared_files": return await this.searchSharedFiles(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { console.error(`Error executing ${name}:`, error); throw new McpError( ErrorCode.InternalError, `Error executing ${name}: ${error.message}` ); } }); } async listFiles(args) { const { query = "", maxResults = 10, folderId, includeShared = false } = args; let searchQuery = query; // Build search query const queryParts = []; if (folderId) { queryParts.push(`'${folderId}' in parents`); } if (includeShared) { queryParts.push("sharedWithMe=true"); } if (query) { queryParts.push(query); } // Don't include trashed files by default queryParts.push("trashed=false"); searchQuery = queryParts.join(" and "); console.error("Search query:", searchQuery); const response = await this.drive.files.list({ q: searchQuery, pageSize: maxResults, fields: 'files(id,name,mimeType,size,modifiedTime,parents,shared,owners)', includeItemsFromAllDrives: true, supportsAllDrives: true, }); const files = response.data.files.map(file => ({ id: file.id, name: file.name, mimeType: file.mimeType, size: file.size, modifiedTime: file.modifiedTime, shared: file.shared, owners: file.owners?.map(owner => owner.emailAddress || owner.displayName) })); return { content: [ { type: "text", text: `Found ${files.length} files:\n\n${JSON.stringify(files, null, 2)}`, }, ], }; } async searchSharedFiles(args) { const { query = "", maxResults = 10 } = args; let searchQuery = "sharedWithMe=true and trashed=false"; if (query) { searchQuery += ` and ${query}`; } console.error("Shared files search query:", searchQuery); const response = await this.drive.files.list({ q: searchQuery, pageSize: maxResults, fields: 'files(id,name,mimeType,size,modifiedTime,shared,owners,sharingUser)', includeItemsFromAllDrives: true, supportsAllDrives: true, }); const files = response.data.files.map(file => ({ id: file.id, name: file.name, mimeType: file.mimeType, size: file.size, modifiedTime: file.modifiedTime, owners: file.owners?.map(owner => owner.emailAddress || owner.displayName), sharedBy: file.sharingUser?.emailAddress || file.sharingUser?.displayName })); return { content: [ { type: "text", text: `Found ${files.length} shared files:\n\n${JSON.stringify(files, null, 2)}`, }, ], }; } async readFile(args) { const { fileId } = args; // Get file metadata first const metadata = await this.drive.files.get({ fileId: fileId, fields: 'name,mimeType,size,owners', supportsAllDrives: true, }); // Handle different file types let content = ""; const mimeType = metadata.data.mimeType; try { if (mimeType.includes('google-apps')) { // Handle Google Workspace files (Docs, Sheets, etc.) let exportMimeType = 'text/plain'; if (mimeType.includes('document')) { exportMimeType = 'text/plain'; } else if (mimeType.includes('spreadsheet')) { exportMimeType = 'text/csv'; } else if (mimeType.includes('presentation')) { exportMimeType = 'text/plain'; } const response = await this.drive.files.export({ fileId: fileId, mimeType: exportMimeType, }); content = response.data; } else { // Handle regular files const response = await this.drive.files.get({ fileId: fileId, alt: 'media', supportsAllDrives: true, }); content = response.data; } } catch (error) { content = `Error reading file content: ${error.message}`; } return { content: [ { type: "text", text: `File: ${metadata.data.name} MIME Type: ${metadata.data.mimeType} Size: ${metadata.data.size || 'N/A'} bytes Owners: ${metadata.data.owners?.map(o => o.emailAddress).join(', ') || 'Unknown'} Content: ${content}`, }, ], }; } async createFile(args) { const { name, content, parentId, mimeType = 'text/plain' } = args; const fileMetadata = { name: name, parents: parentId ? [parentId] : undefined, }; const media = { mimeType: mimeType, body: content, }; const response = await this.drive.files.create({ requestBody: fileMetadata, media: media, fields: 'id,name,parents', supportsAllDrives: true, }); return { content: [ { type: "text", text: `File created successfully! File ID: ${response.data.id} Name: ${response.data.name} Parent folder: ${response.data.parents?.[0] || 'Root'} Authentication method used: ${this.authType}`, }, ], }; } async updateFile(args) { const { fileId, content } = args; const media = { mimeType: 'text/plain', body: content, }; const response = await this.drive.files.update({ fileId: fileId, media: media, fields: 'id,name,modifiedTime', supportsAllDrives: true, }); return { content: [ { type: "text", text: `File updated successfully! File ID: ${response.data.id} Name: ${response.data.name} Modified: ${response.data.modifiedTime} Authentication method used: ${this.authType}`, }, ], }; } async deleteFile(args) { const { fileId } = args; // Get file name before deleting const metadata = await this.drive.files.get({ fileId: fileId, fields: 'name', supportsAllDrives: true, }); await this.drive.files.delete({ fileId: fileId, supportsAllDrives: true, }); return { content: [ { type: "text", text: `File "${metadata.data.name}" (ID: ${fileId}) deleted successfully! Authentication method used: ${this.authType}`, }, ], }; } async run() { try { console.error("Starting Google Drive MCP server..."); await this.initializeGoogleAuth(); console.error("✓ Google authentication initialized"); const transport = new StdioServerTransport(); console.error("✓ Transport created"); await this.server.connect(transport); console.error("✓ Server connected successfully"); // Keep the process alive process.on('SIGINT', () => { console.error("Received SIGINT, shutting down gracefully..."); process.exit(0); }); process.on('SIGTERM', () => { console.error("Received SIGTERM, shutting down gracefully..."); process.exit(0); }); } catch (error) { console.error("Failed to start server:", error); console.error("Error details:", error.stack); process.exit(1); } } } // Start the server const server = new GoogleDriveMCPServer(); server.run().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Raincode-Bahrain/mcp-drive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server