Google Tasks MCP Server

  • src
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { google } from "googleapis"; import dotenv from "dotenv"; dotenv.config(); const { OAuth2 } = google.auth; const GOOGLE_TASKS_API_VERSION = "v1"; const oAuth2Client = new OAuth2( process.env.CLIENT_ID, process.env.CLIENT_SECRET, process.env.REDIRECT_URI ); oAuth2Client.setCredentials({ access_token: process.env.ACCESS_TOKEN, refresh_token: process.env.REFRESH_TOKEN, }); const tasks = google.tasks({ version: GOOGLE_TASKS_API_VERSION, auth: oAuth2Client, }); interface CreateTaskArgs { title: string; notes?: string; taskId?: string; status?: string; } // Type guard for CreateTaskArgs export function isValidCreateTaskArgs(args: any): args is CreateTaskArgs { return ( typeof args === "object" && args !== null && (args.title === undefined || typeof args.title === "string") && (args.notes === undefined || typeof args.notes === "string") && (args.taskId === undefined || typeof args.taskId === "string") && (args.status === undefined || typeof args.status === "string") ); } class TasksServer { private server: Server; constructor() { this.server = new Server( { name: "google-tasks-server", version: "1.0.0", }, { capabilities: { resources: {}, tools: {}, }, } ); this.setupHandlers(); this.setupErrorHandling(); } private setupErrorHandling(): void { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on("SIGINT", async () => { await this.server.close(); process.exit(0); }); } private setupHandlers(): void { this.setupResourceHandlers(); this.setupToolHandlers(); } private setupResourceHandlers(): void { this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: "tasks://default", name: "Default Task List", mimeType: "application/json", description: "Manage your Google Tasks", }, ], })); this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => { if (request.params.uri !== "tasks://default") { throw new McpError( ErrorCode.InvalidRequest, `Unknown resource: ${request.params.uri}` ); } try { const response = await tasks.tasks.list({ tasklist: "@default", }); return { contents: [ { uri: request.params.uri, mimeType: "application/json", text: JSON.stringify(response.data.items, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Tasks API error: ${error}` ); } } ); } private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "create_task", description: "Create a new task in Google Tasks", inputSchema: { type: "object", properties: { title: { type: "string", description: "Title of the task" }, notes: { type: "string", description: "Notes for the task" }, }, required: ["title"], }, }, { name: "list_tasks", description: "List all tasks in the default task list", inputSchema: { type: "object", properties: {}, }, }, { name: "delete_task", description: "Delete a task from the default task list", inputSchema: { type: "object", properties: { taskId: { type: "string", description: "ID of the task to delete" }, }, required: ["taskId"], }, }, { name: "complete_task", description: "Toggle the completion status of a task", inputSchema: { type: "object", properties: { taskId: { type: "string", description: "ID of the task to toggle completion status" }, status: { type: "string", description: "Status of task, needsAction or completed" }, }, required: ["taskId"], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "list_tasks") { try { const response = await tasks.tasks.list({ tasklist: "@default", }); return { content: [ { type: "text", text: JSON.stringify(response.data.items, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Tasks API error: ${error}` ); } } if (request.params.name === "create_task") { if (!isValidCreateTaskArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, "Invalid arguments for creating a task. 'title' must be a string, and 'notes' must be a string or undefined." ); } const args = request.params.arguments; try { const response = await tasks.tasks.insert({ tasklist: "@default", requestBody: { title: args.title, notes: args.notes, }, }); return { content: [ { type: "text", text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Tasks API error: ${error}` ); } } if (request.params.name === "delete_task") { if (!isValidCreateTaskArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, "Invalid arguments for creating a task. 'title' must be a string, and 'notes' must be a string or undefined." ); } const args = request.params.arguments; const taskId = args.taskId; if (!taskId) { throw new McpError( ErrorCode.InvalidParams, "The 'taskId' field is required." ); } try { await tasks.tasks.delete({ tasklist: "@default", task: taskId, }); return { content: [ { type: "text", text: "Task deleted successfully.", }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Tasks API error: ${error}` ); } } if (request.params.name === "complete_task") { if (!isValidCreateTaskArgs(request.params.arguments)) { throw new McpError( ErrorCode.InvalidParams, "Invalid arguments for creating a task. 'title' must be a string, and 'notes' must be a string or undefined." ); } const args = request.params.arguments; const taskId = args.taskId; const newStatus = args.status; if (!taskId) { throw new McpError( ErrorCode.InvalidParams, "The 'taskId' field is required." ); } try { // Durumu güncelle const updateResponse = await tasks.tasks.patch({ tasklist: "@default", task: taskId, requestBody: { status: newStatus }, }); return { content: [ { type: "text", text: JSON.stringify(updateResponse.data, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Tasks API error: ${error}` ); } } throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); }); } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Google Tasks MCP server running on stdio"); } } const server = new TasksServer(); server.run().catch(console.error);