UNPKG

google-docs-mcp-server

Version:

A powerful Model Context Protocol (MCP) server implementation for seamless Google Docs API integration, enabling AI assistants to create, read, update, and manage Google Docs

400 lines 17.3 kB
#!/usr/bin/env node 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 { GoogleDocsClient } from './google-docs-client.js'; import * as dotenv from 'dotenv'; import { parseArgs } from 'node:util'; // Load environment variables dotenv.config(); // Parse command line arguments const { values } = parseArgs({ options: { 'GOOGLE_APPLICATION_CREDENTIALS': { type: 'string' }, 'GOOGLE_APPLICATION_CREDENTIALS_JSON': { type: 'string' }, 'GOOGLE_API_KEY': { type: 'string' }, 'GOOGLE_CLOUD_PROJECT_ID': { type: 'string' }, 'client_id': { type: 'string' }, 'client_secret': { type: 'string' }, 'refresh_token': { type: 'string' } } }); // Get authentication parameters const serviceAccountPath = values['GOOGLE_APPLICATION_CREDENTIALS'] || process.env.GOOGLE_APPLICATION_CREDENTIALS; const serviceAccountJson = values['GOOGLE_APPLICATION_CREDENTIALS_JSON'] || process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON; const apiKey = values['GOOGLE_API_KEY'] || process.env.GOOGLE_API_KEY; const projectId = values['GOOGLE_CLOUD_PROJECT_ID'] || process.env.GOOGLE_CLOUD_PROJECT_ID; const oauthClientId = values['client_id'] || process.env.client_id; const oauthClientSecret = values['client_secret'] || process.env.client_secret; const oauthRefreshToken = values['refresh_token'] || process.env.refresh_token; let isoAuthEnabled = false; if (!serviceAccountPath && !serviceAccountJson && !apiKey && !(oauthClientId && oauthClientSecret && oauthRefreshToken)) { throw new Error('Either GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_APPLICATION_CREDENTIALS_JSON, GOOGLE_API_KEY, or OAuth credentials are required'); } if (oauthClientId && oauthClientSecret && oauthRefreshToken) { isoAuthEnabled = true; } console.log({ oauthClientId, oauthClientSecret, oauthRefreshToken, isoAuthEnabled }); if (!projectId && !isoAuthEnabled) { throw new Error('GOOGLE_CLOUD_PROJECT_ID environment variable is required'); } class GoogleDocsServer { // Core server properties server; googleDocs; constructor() { this.server = new Server({ name: 'google-docs-manager', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, }); if (serviceAccountPath) { console.log(`Using service account: ${serviceAccountPath}`); } if (apiKey) { console.log(`Using API key: ${apiKey.substring(0, 4)}...`); } if (oauthClientId && oauthClientSecret && oauthRefreshToken) { console.log(`Using OAuth credentials with client ID: ${oauthClientId.substring(0, 4)}...`); } console.log(`Using project ID: ${projectId}`); let isoAuthEnabled = false; if (oauthClientId && oauthClientSecret && oauthRefreshToken) { isoAuthEnabled = true; } this.googleDocs = new GoogleDocsClient({ serviceAccountPath, serviceAccountJson, apiKey, projectId, oauthClientId, oauthClientSecret, oauthRefreshToken, isOAuth: isoAuthEnabled }); // Log authentication information this.setupToolHandlers(); this.setupErrorHandling(); } setupErrorHandling() { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { // Define available tools const tools = [ { name: 'google_docs_create', description: 'Create a new Google Doc', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the document' }, content: { type: 'string', description: 'Initial content of the document (plain text)' } }, required: ['title'] } }, { name: 'google_docs_get', description: 'Get a Google Doc by ID', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to retrieve' } }, required: ['documentId'] } }, { name: 'google_docs_update', description: 'Update a Google Doc with new content', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to update' }, content: { type: 'string', description: 'New content to add or replace (plain text)' }, replaceAll: { type: 'boolean', description: 'Whether to replace all content (true) or append (false)' } }, required: ['documentId', 'content'] } }, { name: 'google_docs_append', description: 'Append content to the end of a Google Doc', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to append to' }, content: { type: 'string', description: 'Content to append (plain text)' } }, required: ['documentId', 'content'] } }, { name: 'google_docs_list', description: 'List Google Docs accessible to the authenticated user', inputSchema: { type: 'object', properties: { pageSize: { type: 'number', description: 'Number of documents to return (default: 10)' }, pageToken: { type: 'string', description: 'Token for pagination' } } } }, { name: 'google_docs_delete', description: 'Delete a Google Doc', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to delete' } }, required: ['documentId'] } }, { name: 'google_docs_export', description: 'Export a Google Doc to different formats', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to export' }, mimeType: { type: 'string', description: 'MIME type for export (e.g., "application/pdf", "text/plain")' } }, required: ['documentId'] } }, { name: 'google_docs_share', description: 'Share a Google Doc with specific users', inputSchema: { type: 'object', properties: { documentId: { type: 'string', description: 'ID of the document to share' }, emailAddress: { type: 'string', description: 'Email address to share with' }, role: { type: 'string', description: 'Role to assign (reader, writer, commenter)' } }, required: ['documentId', 'emailAddress'] } }, { name: 'google_docs_search', description: 'Search for Google Docs by title or content', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for document title or content' }, pageSize: { type: 'number', description: 'Number of results to return (default: 10)' }, pageToken: { type: 'string', description: 'Token for pagination' } }, required: ['query'] } }, { name: 'google_docs_verify_connection', description: 'Verify connection with Google Docs API and check credentials', inputSchema: { type: 'object', properties: {} } } ]; return { tools }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const args = request.params.arguments ?? {}; switch (request.params.name) { case 'google_docs_create': { const result = await this.googleDocs.createDocument(args.title, args.content); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_get': { const result = await this.googleDocs.getDocument(args.documentId); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_update': { const result = await this.googleDocs.updateDocument(args.documentId, args.content, args.replaceAll); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_append': { const result = await this.googleDocs.appendDocument(args.documentId, args.content); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_list': { const result = await this.googleDocs.listDocuments(args.pageSize, args.pageToken); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_delete': { const result = await this.googleDocs.deleteDocument(args.documentId); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_export': { const result = await this.googleDocs.exportDocument(args.documentId, args.mimeType); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_share': { const result = await this.googleDocs.shareDocument(args.documentId, args.emailAddress, args.role); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_search': { const result = await this.googleDocs.searchDocuments(args.query, args.pageSize, args.pageToken); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } case 'google_docs_verify_connection': { const result = await this.googleDocs.verifyConnection(); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } } catch (error) { console.error(`Error executing tool ${request.params.name}:`, error); return { content: [{ type: 'text', text: `Google Docs API error: ${error.message}` }], isError: true, }; } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.log('Google Docs MCP server started'); } } export async function serve() { const server = new GoogleDocsServer(); await server.run(); } const server = new GoogleDocsServer(); server.run().catch(console.error); //# sourceMappingURL=index.js.map