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
JavaScript
#!/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