Building Google Tasks MCP Server for Cursor IDE Integration

5 min readSargon Piraev

Learn how to build a production-ready MCP server that integrates Google Tasks API with Cursor IDE for seamless workflow automation

Motivation

As a daily Cursor IDE user, I'm constantly looking for ways to enhance my development workflow. I love how Cursor's AI capabilities help me code faster, but I wanted to take it further by integrating my personal productivity tools directly into the IDE. Google Tasks is my go-to task management system, and building an MCP server to connect it with Cursor seemed like the perfect project to tackle.

This project gave me a chance to dive deeper into MCP server development while creating something I'll actually use every day. Plus, working with the Google Tasks API provided great practice with OAuth2 authentication and RESTful service integration.

Implementation

Let's walk through building a production-ready MCP server for Google Tasks integration. We'll create a TypeScript project that handles authentication, makes API calls, and exposes task management capabilities to Cursor IDE.

Project Structure

First, let's set up our project structure:

mcp-server/
├── src/
│   ├── server.ts     # Main MCP server implementation
│   ├── main.ts       # Entry point
│   └── types.ts      # Type definitions
├── package.json
├── tsconfig.json
└── .env

Installing Dependencies

Create a new project and install the required dependencies:

npm init -y
npm install @cursor/mcp-server axios dotenv zod google-auth-library
npm install -D typescript @types/node

Our package.json should include:

{
  "name": "google-tasks-mcp",
  "version": "1.0.0",
  "main": "dist/main.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/main.js"
  },
  "dependencies": {
    "@cursor/mcp-server": "^1.0.0",
    "axios": "^1.6.0",
    "dotenv": "^16.3.1",
    "zod": "^3.22.4",
    "google-auth-library": "^9.0.0"
  }
}

Environment Configuration

Create a .env file to store API credentials:

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_token

Server Implementation

Let's build our server.ts file step by step:

import { MCPServer } from '@cursor/mcp-server';
import { z } from 'zod';
import { OAuth2Client } from 'google-auth-library';
import axios from 'axios';
import dotenv from 'dotenv';

// Load environment variables
dotenv.config();

// Validate environment variables
const envSchema = z.object({
  GOOGLE_CLIENT_ID: z.string(),
  GOOGLE_CLIENT_SECRET: z.string(),
  GOOGLE_REFRESH_TOKEN: z.string(),
});

const env = envSchema.parse(process.env);

// Create OAuth2 client
const oauth2Client = new OAuth2Client(
  env.GOOGLE_CLIENT_ID,
  env.GOOGLE_CLIENT_SECRET,
  'urn:ietf:wg:oauth:2.0:oob'
);

oauth2Client.setCredentials({
  refresh_token: env.GOOGLE_REFRESH_TOKEN
});

// Create axios instance with auth interceptor
const client = axios.create({
  baseURL: 'https://tasks.googleapis.com/tasks/v1'
});

client.interceptors.request.use(async (config) => {
  const token = await oauth2Client.getAccessToken();
  config.headers.Authorization = `Bearer ${token.token}`;
  return config;
});

// Create MCP server instance
const mcpServer = new MCPServer();

// Add tools
mcpServer.tool({
  name: 'list_tasks',
  description: 'List all tasks in a specified task list',
  parameters: z.object({
    taskListId: z.string().describe('The ID of the task list to fetch tasks from')
  }),
  handler: async ({ taskListId }) => {
    const response = await client.get(`/lists/${taskListId}/tasks`);
    return response.data.items;
  }
});

mcpServer.tool({
  name: 'create_task',
  description: 'Create a new task in a specified task list',
  parameters: z.object({
    taskListId: z.string().describe('The ID of the task list'),
    title: z.string().describe('The title of the task'),
    notes: z.string().optional().describe('Optional notes for the task'),
    due: z.string().optional().describe('Due date in RFC3339 format')
  }),
  handler: async ({ taskListId, title, notes, due }) => {
    const response = await client.post(`/lists/${taskListId}/tasks`, {
      title,
      notes,
      due
    });
    return response.data;
  }
});

export default mcpServer;

Entry Point

Create main.ts to start the server:

import mcpServer from './server';

const port = process.env.PORT || 3000;
mcpServer.listen(port, () => {
  console.log(`Google Tasks MCP server listening on port ${port}`);
});

Usage

Let's go through how to set up and use the Google Tasks MCP server with Cursor IDE.

Installation

Install the package globally:

npm install -g google-tasks-mcp

Configuration

Add the following entry to your Cursor IDE's mcp.json file:

{
  "servers": {
    "google-tasks": {
      "command": "google-tasks-mcp",
      "tools": [
        "list_tasks",
        "create_task"
      ]
    }
  }
}

Example Prompts

Here are some example prompts you can use in Cursor IDE:

  1. Listing tasks:
Show me all tasks in my main task list

Response:

Here are your tasks from the main list:
- Complete MCP server documentation (due: tomorrow)
- Review pull requests (due: today)
- Update deployment scripts (due: next week)
  1. Creating a task:
Create a new task titled "Implement error handling" due tomorrow

Response:

Created new task:
Title: Implement error handling
Due: 2024-01-10T23:59:59Z
Status: needsAction
  1. Task management:
Add a task to review the API documentation with notes about checking rate limits

Response:

Created new task:
Title: Review API documentation
Notes: Check and document rate limits
Status: needsAction

How It Works

Here's a sequence diagram showing how the Google Tasks MCP server integrates with Cursor IDE:

Rendering diagram...

The diagram shows how user prompts in Cursor IDE are transformed into API calls through the MCP server, which handles authentication and data transformation before returning results back to the user.