Skip to main content
Glama

Google Tasks MCP Server

index.ts22.2 kB
// src/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { google } from "googleapis"; import { OAuth2Client } from "google-auth-library"; import * as http from "http"; import * as path from "path"; import dotenv from "dotenv"; import url from "url"; // Load environment variables dotenv.config(); // Constants const SCOPES = ["https://www.googleapis.com/auth/tasks"]; // Create server instance const server = new McpServer({ name: "google-tasks", version: "1.0.0", }); const CLIENT_ID = process.env.GOOGLE_CLIENT_ID; const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET; const REDIRECT_URI = 'http://localhost:3000/oauth2callback'; const REDIRECT_PORT = 3000; // Google OAuth setup const oauth2Client = new OAuth2Client( CLIENT_ID, CLIENT_SECRET, REDIRECT_URI ); // Load saved credentials if any let credentials : any = null; // Initialize Google Tasks client const tasks = google.tasks({ version: 'v1', auth: oauth2Client }); // Authentication server reference let authServer : http.Server | null = null; // Helper function to check if authenticated function isAuthenticated() { return credentials !== null; } // Authentication tool server.tool( "authenticate", "Get URL to authenticate with Google Tasks", {}, async () => { // Make sure any previous server is closed if (authServer) { try { authServer.close(); } catch (error) { console.error('Error closing existing auth server:', error); } authServer = null; } // Create the temporary HTTP server for OAuth callback authServer = http.createServer(async (req, res) => { try { // Parse the URL to get the authorization code const queryParams = url.parse(req.url || '', true).query; const code = queryParams.code; if (code) { console.error('✅ Authorization code received'); // Send success response with the code res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <h1>Authorization Code Received</h1> <p>Please copy this code and use it with the 'set-auth-code' tool in Claude:</p> <div style="padding: 10px; background-color: #f0f0f0; border: 1px solid #ccc; margin: 20px 0;"> <code>${code}</code> </div> <p>You can close this window after copying the code.</p> `); // Close the server after a short delay setTimeout(() => { if (authServer) { authServer.close(); authServer = null; } }, 60000); // Keep the server alive for 1 minute } else { res.writeHead(400, { 'Content-Type': 'text/html' }); res.end(` <h1>Authentication Failed</h1> <p>No authorization code received.</p> <p>Please try again.</p> `); } } catch (error) { console.error('Error during authentication:', error); res.writeHead(500, { 'Content-Type': 'text/html' }); res.end(` <h1>Authentication Error</h1> <p>${error instanceof Error ? error.message : String(error)}</p> `); } }); // Start the server authServer.listen(REDIRECT_PORT, () => { console.error(`Temporary authentication server running at http://localhost:${REDIRECT_PORT}/`); console.error('Waiting for authentication...'); }); // Generate the auth URL const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, // Force approval prompt to always get a refresh token prompt: 'consent' }); return { content: [ { type: "text", text: `Please visit this URL to authenticate with Google Tasks:\n\n${authUrl}\n\nAfter authenticating, you'll receive a code. Use the 'set-auth-code' tool with that code.`, }, ], }; } ); // Set authentication code tool server.tool( "set-auth-code", "Set the authentication code received from Google OAuth flow", { code: z.string().describe("The authentication code received from Google"), }, async ({ code }) => { try { const { tokens } = await oauth2Client.getToken(code); oauth2Client.setCredentials(tokens); // Store tokens in memory only credentials = tokens; // Close auth server if it's still running if (authServer) { try { authServer.close(); } catch (error) { console.error('Error closing auth server:', error); } authServer = null; } return { content: [ { type: "text", text: "Authentication successful! You can now use the Google Tasks tools.", }, ], }; } catch (error) { console.error('Error retrieving access token:', error); return { isError: true, content: [ { type: "text", text: `Authentication failed: ${error}`, }, ], }; } } ); // Task List Tools // 1. List all task lists server.tool("list-tasklists", "List all task lists", {}, async () => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response = await tasks.tasklists.list(); const taskLists = response.data.items || []; if (taskLists.length === 0) { return { content: [ { type: "text", text: "No task lists found.", }, ], }; } const formattedLists = taskLists.map((list) => ({ id: list.id, title: list.title, updated: list.updated, })); return { content: [ { type: "text", text: JSON.stringify(formattedLists, null, 2), }, ], }; } catch (error) { console.error("Error listing task lists:", error); return { isError: true, content: [ { type: "text", text: `Error listing task lists: ${error}`, }, ], }; } }); // 2. Get task list by ID server.tool( "get-tasklist", "Get a task list by ID", { tasklist: z.string().describe("Task list ID"), }, async ({ tasklist }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response = await tasks.tasklists.get({ tasklist, }); return { content: [ { type: "text", text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { console.error("Error getting task list:", error); return { isError: true, content: [ { type: "text", text: `Error getting task list: ${error}`, }, ], }; } } ); // 3. Create a new task list server.tool( "create-tasklist", "Create a new task list", { title: z.string().describe("Title of the new task list"), }, async ({ title }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response = await tasks.tasklists.insert({ requestBody: { title, }, }); return { content: [ { type: "text", text: `Task list created successfully:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error creating task list:", error); return { isError: true, content: [ { type: "text", text: `Error creating task list: ${error}`, }, ], }; } } ); // 4. Update a task list server.tool( "update-tasklist", "Update an existing task list", { tasklist: z.string().describe("Task list ID"), title: z.string().describe("New title for the task list"), }, async ({ tasklist, title }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response = await tasks.tasklists.update({ tasklist, requestBody: { title, }, }); return { content: [ { type: "text", text: `Task list updated successfully:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error updating task list:", error); return { isError: true, content: [ { type: "text", text: `Error updating task list: ${error}`, }, ], }; } } ); // 5. Delete a task list server.tool( "delete-tasklist", "Delete a task list", { tasklist: z.string().describe("Task list ID to delete"), }, async ({ tasklist }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { await tasks.tasklists.delete({ tasklist, }); return { content: [ { type: "text", text: `Task list with ID '${tasklist}' was successfully deleted.`, }, ], }; } catch (error) { console.error("Error deleting task list:", error); return { isError: true, content: [ { type: "text", text: `Error deleting task list: ${error}`, }, ], }; } } ); // Task Tools // 1. List tasks in a task list server.tool( "list-tasks", "List all tasks in a task list", { tasklist: z.string().describe("Task list ID"), showCompleted: z .boolean() .optional() .describe("Whether to include completed tasks"), showHidden: z .boolean() .optional() .describe("Whether to include hidden tasks"), showDeleted: z .boolean() .optional() .describe("Whether to include deleted tasks"), }, async ({ tasklist, showCompleted = true, showHidden = false, showDeleted = false, }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response: any = await tasks.tasks.list({ tasklist, showCompleted, showHidden, showDeleted, }); const tasksResponse = response.data.items || []; if (tasksResponse.length === 0) { return { content: [ { type: "text", text: "No tasks found in this list.", }, ], }; } const formattedTasks = tasksResponse.map((task: any) => ({ id: task.id, title: task.title, status: task.status, due: task.due, notes: task.notes, completed: task.completed, })); return { content: [ { type: "text", text: JSON.stringify(formattedTasks, null, 2), }, ], }; } catch (error) { console.error("Error listing tasks:", error); return { isError: true, content: [ { type: "text", text: `Error listing tasks: ${error}`, }, ], }; } } ); // 2. Get a specific task server.tool( "get-task", "Get a specific task by ID", { tasklist: z.string().describe("Task list ID"), task: z.string().describe("Task ID"), }, async ({ tasklist, task }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const response = await tasks.tasks.get({ tasklist, task, }); return { content: [ { type: "text", text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { console.error("Error getting task:", error); return { isError: true, content: [ { type: "text", text: `Error getting task: ${error}`, }, ], }; } } ); // 3. Create a new task server.tool( "create-task", "Create a new task in a task list", { tasklist: z.string().describe("Task list ID"), title: z.string().describe("Title of the task"), notes: z.string().optional().describe("Notes for the task"), due: z .string() .optional() .describe("Due date in RFC 3339 format (e.g., 2025-03-19T12:00:00Z)"), }, async ({ tasklist, title, notes, due }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const requestBody: any = { title, status: "needsAction", }; if (notes) requestBody.notes = notes; if (due) requestBody.due = due; const response = await tasks.tasks.insert({ tasklist, requestBody, }); return { content: [ { type: "text", text: `Task created successfully:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error creating task:", error); return { isError: true, content: [ { type: "text", text: `Error creating task: ${error}`, }, ], }; } } ); // 4. Update a task server.tool( "update-task", "Update an existing task", { tasklist: z.string().describe("Task list ID"), task: z.string().describe("Task ID"), title: z.string().optional().describe("New title for the task"), notes: z.string().optional().describe("New notes for the task"), status: z .enum(["needsAction", "completed"]) .optional() .describe("Status of the task"), due: z .string() .optional() .describe("Due date in RFC 3339 format (e.g., 2025-03-19T12:00:00Z)"), }, async ({ tasklist, task, title, notes, status, due }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { // First, get the current task data const currentTask = await tasks.tasks.get({ tasklist, task, }); // Prepare the update request const requestBody: any = { ...currentTask.data, }; if (title !== undefined) requestBody.title = title; if (notes !== undefined) requestBody.notes = notes; if (status !== undefined) requestBody.status = status; if (due !== undefined) requestBody.due = due; const response = await tasks.tasks.update({ tasklist, task, requestBody, }); return { content: [ { type: "text", text: `Task updated successfully:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error updating task:", error); return { isError: true, content: [ { type: "text", text: `Error updating task: ${error}`, }, ], }; } } ); // 5. Delete a task server.tool( "delete-task", "Delete a task", { tasklist: z.string().describe("Task list ID"), task: z.string().describe("Task ID to delete"), }, async ({ tasklist, task }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { await tasks.tasks.delete({ tasklist, task, }); return { content: [ { type: "text", text: `Task with ID '${task}' was successfully deleted.`, }, ], }; } catch (error) { console.error("Error deleting task:", error); return { isError: true, content: [ { type: "text", text: `Error deleting task: ${error}`, }, ], }; } } ); // 6. Complete a task server.tool( "complete-task", "Mark a task as completed", { tasklist: z.string().describe("Task list ID"), task: z.string().describe("Task ID to mark as completed"), }, async ({ tasklist, task }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { // Get the current task const currentTask = await tasks.tasks.get({ tasklist, task, }); // Update the status to completed const requestBody = { ...currentTask.data, status: "completed", completed: new Date().toISOString(), }; const response = await tasks.tasks.update({ tasklist, task, requestBody, }); return { content: [ { type: "text", text: `Task marked as completed:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error completing task:", error); return { isError: true, content: [ { type: "text", text: `Error completing task: ${error}`, }, ], }; } } ); // 7. Move a task server.tool( "move-task", "Move a task to another position", { tasklist: z.string().describe("Task list ID"), task: z.string().describe("Task ID to move"), parent: z.string().optional().describe("Optional new parent task ID"), previous: z .string() .optional() .describe("Optional previous sibling task ID"), }, async ({ tasklist, task, parent, previous }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { const moveParams: any = { tasklist, task, }; if (parent !== undefined) moveParams.parent = parent; if (previous !== undefined) moveParams.previous = previous; const response = await tasks.tasks.move(moveParams); return { content: [ { type: "text", text: `Task moved successfully:\n\n${JSON.stringify( response.data, null, 2 )}`, }, ], }; } catch (error) { console.error("Error moving task:", error); return { isError: true, content: [ { type: "text", text: `Error moving task: ${error}`, }, ], }; } } ); // 8. Clear completed tasks server.tool( "clear-completed-tasks", "Clear all completed tasks from a task list", { tasklist: z.string().describe("Task list ID"), }, async ({ tasklist }) => { if (!isAuthenticated()) { return { isError: true, content: [ { type: "text", text: "Not authenticated. Please use the 'authenticate' tool first.", }, ], }; } try { await tasks.tasks.clear({ tasklist, }); return { content: [ { type: "text", text: `All completed tasks in list '${tasklist}' have been cleared.`, }, ], }; } catch (error) { console.error("Error clearing completed tasks:", error); return { isError: true, content: [ { type: "text", text: `Error clearing completed tasks: ${error}`, }, ], }; } } ); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Google Tasks MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

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/arpitbatra123/mcp-googletasks'

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