@emit-ia/gdrive-mcp-server
Version:
Google Drive MCP Server - Model Context Protocol server for Google Drive and Gmail APIs
827 lines • 38.7 kB
JavaScript
#!/usr/bin/env node
/**
* Google Drive & Gmail MCP Server
*
* This MCP (Model Context Protocol) server provides comprehensive access to Google Drive
* and Gmail services through a hybrid authentication system:
* - Service Account authentication for Google Drive (reliable, no expiration)
* - OAuth2 authentication for Gmail (required for personal Gmail access)
*
* The server implements automatic token management for Gmail to prevent the common
* 6-month token expiration issues that plague many integrations.
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import { GoogleDriveService } from "./google-drive.js";
import { GmailService } from "./gmail.js";
import { config } from "./config.js";
import { Logger } from "./logger.js";
/**
* Main MCP Server class that orchestrates Google Drive and Gmail services
*
* This server follows the MCP protocol specification and provides a unified interface
* for both Google Drive and Gmail operations. It handles tool registration, request
* routing, and error management.
*/
class GoogleDriveMCPServer {
server;
driveService;
gmailService;
constructor() {
// Initialize the MCP server with configuration from config.ts
this.server = new Server({
name: config.serverName,
version: config.serverVersion,
});
// Initialize Google services with their respective authentication methods
this.driveService = new GoogleDriveService();
this.gmailService = new GmailService();
// Set up MCP protocol handlers
this.setupToolHandlers();
this.setupErrorHandling();
}
/**
* Sets up MCP tool handlers for both Google Drive and Gmail operations
*
* This method registers all available tools with their schemas, providing
* comprehensive documentation for each tool's parameters and functionality.
*/
setupToolHandlers() {
// Register the list tools handler - this tells MCP clients what tools are available
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// File Operations
{
name: "gdrive_list_files",
description: "List files and folders in Google Drive",
inputSchema: {
type: "object",
properties: {
folderId: {
type: "string",
description: "ID of folder to list (optional, defaults to root)",
},
query: {
type: "string",
description: "Search query to filter files",
},
maxResults: {
type: "number",
description: "Maximum number of results (default: 100)",
default: 100,
},
includeShared: {
type: "boolean",
description: "Include shared files (default: true)",
default: true,
},
},
},
},
{
name: "gdrive_get_file",
description: "Get file metadata and download URL",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to get",
},
includeContent: {
type: "boolean",
description: "Include file content for text files (default: false)",
default: false,
},
},
required: ["fileId"],
},
},
{
name: "gdrive_download_file",
description: "Download file content",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to download",
},
format: {
type: "string",
description: "Export format for Google Docs (optional)",
},
},
required: ["fileId"],
},
},
{
name: "gdrive_upload_file",
description: "Upload a new file to Google Drive",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the file",
},
content: {
type: "string",
description: "File content (text files only)",
},
mimeType: {
type: "string",
description: "MIME type of the file",
},
parentId: {
type: "string",
description: "ID of parent folder (optional)",
},
},
required: ["name", "content"],
},
},
{
name: "gdrive_update_file",
description: "Update an existing file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to update",
},
name: {
type: "string",
description: "New name for the file (optional)",
},
content: {
type: "string",
description: "New content for the file (optional)",
},
mimeType: {
type: "string",
description: "New MIME type (optional)",
},
},
required: ["fileId"],
},
},
{
name: "gdrive_delete_file",
description: "Delete a file or move it to trash",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to delete",
},
permanent: {
type: "boolean",
description: "Permanently delete (true) or move to trash (false)",
default: false,
},
},
required: ["fileId"],
},
},
{
name: "gdrive_copy_file",
description: "Create a copy of a file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to copy",
},
name: {
type: "string",
description: "Name for the copy (optional)",
},
parentId: {
type: "string",
description: "ID of parent folder for the copy (optional)",
},
},
required: ["fileId"],
},
},
{
name: "gdrive_move_file",
description: "Move a file to a different folder",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to move",
},
newParentId: {
type: "string",
description: "ID of the new parent folder",
},
removeFromParents: {
type: "string",
description: "Comma-separated list of parent IDs to remove from",
},
},
required: ["fileId", "newParentId"],
},
},
// Folder Operations
{
name: "gdrive_create_folder",
description: "Create a new folder",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the folder",
},
parentId: {
type: "string",
description: "ID of parent folder (optional)",
},
},
required: ["name"],
},
},
{
name: "gdrive_get_folder_info",
description: "Get folder metadata and contents",
inputSchema: {
type: "object",
properties: {
folderId: {
type: "string",
description: "ID of the folder",
},
includeFiles: {
type: "boolean",
description: "Include files in the response (default: true)",
default: true,
},
},
required: ["folderId"],
},
},
// Sharing and Permissions
{
name: "gdrive_share_file",
description: "Share a file with specific users or make it public",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file to share",
},
email: {
type: "string",
description: "Email of user to share with (optional)",
},
role: {
type: "string",
description: "Permission role: owner, organizer, fileOrganizer, writer, commenter, reader",
enum: ["owner", "organizer", "fileOrganizer", "writer", "commenter", "reader"],
default: "reader",
},
type: {
type: "string",
description: "Permission type: user, group, domain, anyone",
enum: ["user", "group", "domain", "anyone"],
default: "user",
},
sendNotificationEmail: {
type: "boolean",
description: "Send notification email (default: true)",
default: true,
},
},
required: ["fileId"],
},
},
{
name: "gdrive_get_permissions",
description: "Get sharing permissions for a file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file",
},
},
required: ["fileId"],
},
},
{
name: "gdrive_remove_permission",
description: "Remove a sharing permission",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file",
},
permissionId: {
type: "string",
description: "ID of the permission to remove",
},
},
required: ["fileId", "permissionId"],
},
},
// Search and Discovery
{
name: "gdrive_search",
description: "Advanced search for files and folders",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query (supports Google Drive search syntax)",
},
mimeType: {
type: "string",
description: "Filter by MIME type",
},
modifiedTime: {
type: "string",
description: "Filter by modification time (ISO 8601 format)",
},
owner: {
type: "string",
description: "Filter by owner email",
},
maxResults: {
type: "number",
description: "Maximum number of results (default: 100)",
default: 100,
},
},
},
},
{
name: "gdrive_get_recent_files",
description: "Get recently modified files",
inputSchema: {
type: "object",
properties: {
maxResults: {
type: "number",
description: "Maximum number of results (default: 20)",
default: 20,
},
daysBack: {
type: "number",
description: "Number of days to look back (default: 7)",
default: 7,
},
},
},
},
// Comments and Revisions
{
name: "gdrive_get_comments",
description: "Get comments on a file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file",
},
includeDeleted: {
type: "boolean",
description: "Include deleted comments (default: false)",
default: false,
},
},
required: ["fileId"],
},
},
{
name: "gdrive_add_comment",
description: "Add a comment to a file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file",
},
content: {
type: "string",
description: "Comment content",
},
anchor: {
type: "string",
description: "Text anchor for the comment (optional)",
},
},
required: ["fileId", "content"],
},
},
{
name: "gdrive_get_revisions",
description: "Get revision history of a file",
inputSchema: {
type: "object",
properties: {
fileId: {
type: "string",
description: "ID of the file",
},
},
required: ["fileId"],
},
},
// Utility Functions
{
name: "gdrive_get_about",
description: "Get information about the user's Drive account",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "gdrive_empty_trash",
description: "Empty the trash folder",
inputSchema: {
type: "object",
properties: {},
},
},
// Gmail Operations
{
name: "gmail_list_messages",
description: "List Gmail messages with optional search query",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Gmail search query (e.g., 'is:unread', 'from:example@gmail.com')",
},
maxResults: {
type: "number",
description: "Maximum number of messages to return (default: 10, max: 500)",
default: 10,
},
},
},
},
{
name: "gmail_get_message",
description: "Get a specific Gmail message by ID",
inputSchema: {
type: "object",
properties: {
messageId: {
type: "string",
description: "ID of the message to retrieve",
},
},
required: ["messageId"],
},
},
{
name: "gmail_send_message",
description: "Send a Gmail message",
inputSchema: {
type: "object",
properties: {
to: {
type: "string",
description: "Recipient email address",
},
subject: {
type: "string",
description: "Email subject",
},
body: {
type: "string",
description: "Email body content",
},
from: {
type: "string",
description: "Sender email (optional, uses authenticated user by default)",
},
},
required: ["to", "subject", "body"],
},
},
{
name: "gmail_mark_as_read",
description: "Mark a Gmail message as read",
inputSchema: {
type: "object",
properties: {
messageId: {
type: "string",
description: "ID of the message to mark as read",
},
},
required: ["messageId"],
},
},
{
name: "gmail_mark_as_unread",
description: "Mark a Gmail message as unread",
inputSchema: {
type: "object",
properties: {
messageId: {
type: "string",
description: "ID of the message to mark as unread",
},
},
required: ["messageId"],
},
},
{
name: "gmail_search_messages",
description: "Search Gmail messages and return full message details",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Gmail search query",
},
maxResults: {
type: "number",
description: "Maximum number of messages to return (default: 50)",
default: 50,
},
},
required: ["query"],
},
},
{
name: "gmail_get_profile",
description: "Get Gmail user profile information",
inputSchema: {
type: "object",
properties: {},
},
},
// Token management tools
{
name: "gmail_check_token_status",
description: "Check the status of Gmail authentication token",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "gmail_refresh_token",
description: "Manually refresh the Gmail authentication token to prevent expiration",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
// Batch Processing and Smart Analysis Tools
{
name: "gmail_batch_process_messages",
description: "Batch process Gmail messages with intelligent rate limiting for large accounts",
inputSchema: {
type: "object",
properties: {
messageIds: {
type: "array",
items: { type: "string" },
description: "Array of message IDs to process",
},
batchSize: {
type: "number",
description: "Number of messages to process in each batch (default: 20)",
default: 20,
},
},
required: ["messageIds"],
},
},
{
name: "gmail_smart_analysis",
description: "Smart Gmail analysis for large accounts - efficiently processes messages with rate limiting",
inputSchema: {
type: "object",
properties: {
analysisType: {
type: "string",
enum: ["recent", "unread", "important", "senders"],
description: "Type of analysis to perform",
},
maxMessages: {
type: "number",
description: "Maximum number of messages to analyze (default: 1000)",
default: 1000,
},
daysBack: {
type: "number",
description: "Number of days to look back for 'recent' analysis (default: 30)",
default: 30,
},
query: {
type: "string",
description: "Custom query for 'senders' analysis type",
},
},
required: ["analysisType"],
},
},
],
};
});
/**
* Handle individual tool calls from MCP clients
*
* This handler routes tool calls to the appropriate service method based on
* the tool name. It provides consistent error handling and response formatting
* for all tools.
*/
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
// Route tool calls to appropriate service methods
switch (name) {
// Google Drive File Operations
case "gdrive_list_files":
result = await this.driveService.listFiles(args || {});
break;
case "gdrive_get_file":
result = await this.driveService.getFile(args);
break;
case "gdrive_download_file":
result = await this.driveService.downloadFile(args);
break;
case "gdrive_upload_file":
result = await this.driveService.uploadFile(args);
break;
case "gdrive_update_file":
result = await this.driveService.updateFile(args);
break;
case "gdrive_delete_file":
result = await this.driveService.deleteFile(args);
break;
case "gdrive_copy_file":
result = await this.driveService.copyFile(args);
break;
case "gdrive_move_file":
result = await this.driveService.moveFile(args);
break;
case "gdrive_create_folder":
result = await this.driveService.createFolder(args);
break;
case "gdrive_get_folder_info":
result = await this.driveService.getFolderInfo(args);
break;
case "gdrive_share_file":
result = await this.driveService.shareFile(args);
break;
case "gdrive_get_permissions":
result = await this.driveService.getPermissions(args);
break;
case "gdrive_remove_permission":
result = await this.driveService.removePermission(args);
break;
case "gdrive_search":
result = await this.driveService.search(args || {});
break;
case "gdrive_get_recent_files":
result = await this.driveService.getRecentFiles(args || {});
break;
case "gdrive_get_comments":
result = await this.driveService.getComments(args);
break;
case "gdrive_add_comment":
result = await this.driveService.addComment(args);
break;
case "gdrive_get_revisions":
result = await this.driveService.getRevisions(args);
break;
case "gdrive_get_about":
result = await this.driveService.getAbout();
break;
case "gdrive_empty_trash":
result = await this.driveService.emptyTrash();
break;
// Gmail Operations
case "gmail_list_messages":
result = await this.gmailService.listMessages(args?.query, args?.maxResults);
break;
case "gmail_get_message":
result = await this.gmailService.getMessage(args.messageId);
break;
case "gmail_send_message":
result = await this.gmailService.sendMessage(args.to, args.subject, args.body, args.from);
break;
case "gmail_mark_as_read":
result = await this.gmailService.markAsRead(args.messageId);
break;
case "gmail_mark_as_unread":
result = await this.gmailService.markAsUnread(args.messageId);
break;
case "gmail_search_messages":
result = await this.gmailService.searchMessages(args.query, args.maxResults);
break;
case "gmail_get_profile":
result = await this.gmailService.getProfile();
break;
// Gmail Token Management (prevents 6-month expiration issues)
case "gmail_check_token_status":
result = this.gmailService.getTokenStatus();
break;
case "gmail_refresh_token":
result = await this.gmailService.refreshToken();
break;
// Batch Processing and Smart Analysis
case "gmail_batch_process_messages":
result = await this.gmailService.batchProcessMessages(args.messageIds, args.batchSize);
break;
case "gmail_smart_analysis":
result = await this.gmailService.smartAnalysis(args.analysisType, {
maxMessages: args.maxMessages,
daysBack: args.daysBack,
query: args.query
});
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
// Return successful result in MCP format
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
// Return error in MCP format with proper error flag
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
}
/**
* Sets up global error handling for the MCP server
*
* This includes both MCP server errors and process termination signals
* to ensure graceful shutdown and proper cleanup.
*/
setupErrorHandling() {
// Handle MCP server errors
this.server.onerror = (error) => {
Logger.error("MCP Server Error:", error);
};
// Handle graceful shutdown on SIGINT (Ctrl+C)
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
/**
* Starts the MCP server using stdio transport
*
* This method connects the server to the stdio transport, which allows
* communication with MCP clients like Claude Desktop.
*/
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
Logger.info("Google Drive MCP server running on stdio");
}
}
// Initialize and start the server
// This is the main entry point for the MCP server
const server = new GoogleDriveMCPServer();
server.run().catch((error) => {
Logger.error("Failed to start server:", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map