Skip to main content
Glama
H5SH
by H5SH
README.md
# google-calendar-mcp

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
[![MCP](https://img.shields.io/badge/MCP-TypeScript%20SDK-blue.svg)](https://modelcontextprotocol.io/)

A production-ready [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that lets AI assistants interact with your personal Google Calendar using the Google Calendar API and OAuth 2.0.

Connect it to **Cursor**, **Claude Desktop**, **VS Code**, or any MCP client that supports stdio transport. Each user runs a local copy with **their own** Google credentials.

> **Security:** Never commit `.env` or real OAuth tokens. See [SECURITY.md](SECURITY.md).

---

## Features

- **7 MCP tools** — list calendars, list / search / create / update / delete events, and free/busy queries
- **OAuth 2.0 with refresh tokens** — access tokens refresh automatically; you never paste a short-lived access token
- **Zod validation** on every tool input
- **Structured logging** to stderr (tool name, duration, API errors)
- **Graceful errors** — 401, 403, 404, 429, network failures, and expired refresh tokens return readable MCP errors without crashing
- **stdio transport** — works with local MCP hosts out of the box

---

## Architecture overview

```
┌─────────────────────┐     stdio (JSON-RPC)      ┌──────────────────────────┐
│  MCP Client         │ ◄───────────────────────► │  google-calendar-mcp     │
│  Cursor / Claude /  │                           │  McpServer + tools       │
│  VS Code            │                           │  auth.ts → OAuth2        │
└─────────────────────┘                           │  calendar.ts → API calls │
                                                  └────────────┬─────────────┘
                                                               │
                                                               ▼
                                                  ┌──────────────────────────┐
                                                  │  Google Calendar API v3  │
                                                  │  (googleapis + refresh)  │
                                                  └──────────────────────────┘
```

1. The MCP client **spawns** this server as a child process.
2. Credentials are loaded from `.env` (or the client `env` block).
3. `googleapis` refreshes access tokens using your refresh token as needed.
4. Tools in `src/tools/` call shared helpers in `src/calendar.ts` (no duplicated API logic).

This is a **single-user, local** server—not a multi-tenant cloud service.

---

## Screenshots

> Place screenshots under `docs/images/` after publishing if desired.

| Cursor MCP connected | Example tool result |
| -------------------- | ------------------- |
| ![Cursor MCP](docs/images/cursor-mcp.png) | ![List events](docs/images/list-events.png) |

*(Image paths are placeholders until you add screenshots.)*

---

## Tech stack

| Technology | Role |
| ---------- | ---- |
| TypeScript | Strict-mode application language |
| Node.js 18+ | Runtime |
| [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) | MCP server (`McpServer`, `registerTool`, `StdioServerTransport`) |
| [googleapis](https://www.npmjs.com/package/googleapis) | Official Google Calendar client |
| [zod](https://www.npmjs.com/package/zod) | Tool input schemas |
| [dotenv](https://www.npmjs.com/package/dotenv) | Local environment loading |
| tsx | Development runner |

**Docker is not provided.** This server uses stdio; MCP clients typically start a local Node process. Packaging it in Docker is unnecessary for the supported setup.

---

## Requirements

- Node.js **18** or later
- A [Google Cloud](https://console.cloud.google.com/) project
- Google Calendar API enabled
- OAuth 2.0 client ID and secret
- A refresh token with Calendar access
- An MCP-compatible client (Cursor, Claude Desktop, VS Code, etc.)

---

## Installation

```bash
git clone https://github.com/[your-org]/google-calendar-mcp.git
cd google-calendar-mcp
npm install
```

Copy the environment template:

```bash
cp .env.example .env
```

Fill in your credentials (see [Google Cloud OAuth setup](#google-cloud-oauth-setup) below). **Never commit `.env`.**

---

## Environment variables

| Variable | Required | Description |
| -------- | -------- | ----------- |
| `GOOGLE_CLIENT_ID` | Yes | OAuth 2.0 client ID from Google Cloud Console |
| `GOOGLE_CLIENT_SECRET` | Yes | OAuth 2.0 client secret |
| `GOOGLE_REFRESH_TOKEN` | Yes | Long-lived refresh token from the OAuth consent flow |
| `DEFAULT_CALENDAR_ID` | No | Default calendar when a tool omits `calendarId` (default: `primary`) |

Example `.env` (placeholders only):

```env
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REFRESH_TOKEN=your-refresh-token
DEFAULT_CALENDAR_ID=primary
```

---

## Google Cloud OAuth setup

Follow these steps if you have never used Google Cloud. Screens and labels change occasionally; use the current Console navigation that matches each step.

### 1. Create a Google Cloud project

1. Open [Google Cloud Console](https://console.cloud.google.com/).
2. Click the project selector at the top → **New Project**.
3. Enter a name (for example `google-calendar-mcp`) → **Create**.
4. Select the new project.

### 2. Enable the Google Calendar API

1. Go to **APIs & Services** → **Library**.
2. Search for **Google Calendar API**.
3. Open it → click **Enable**.

### 3. Configure the OAuth consent screen

1. Go to **APIs & Services** → **OAuth consent screen**.
2. Choose **External** (unless you use Google Workspace Internal) → **Create**.
3. Fill in **App name**, **User support email**, and **Developer contact**.
4. Save and continue through Scopes / Test users as prompted.
5. Under **Test users** (while the app is in **Testing**), add the Google account whose calendar you will use.

> **Important:** In **Testing** mode, refresh tokens may expire after **7 days**. For personal long-term use, keep yourself as a test user or publish the app when eligible. Revoking access or resetting the OAuth client also invalidates tokens.

### 4. Create an OAuth client ID

1. Go to **APIs & Services** → **Credentials**.
2. Click **Create credentials** → **OAuth client ID**.
3. Application type:
   - **Web application** is easiest with the OAuth Playground (recommended below), or
   - **Desktop app** if you prefer another flow later.
4. Name the client (for example `calendar-mcp-local`).
5. If using **Web application**, under **Authorized redirect URIs** add:

   ```
   https://developers.google.com/oauthplayground
   ```

6. Click **Create**. Copy the **Client ID** and **Client secret** into your `.env` file.

### 5. Required OAuth scope

This server needs:

```
https://www.googleapis.com/auth/calendar
```

That scope allows reading and writing calendar data for the authorized account.

### 6. Obtain a refresh token (OAuth 2.0 Playground)

1. Open [Google OAuth 2.0 Playground](https://developers.google.com/oauthplayground/).
2. Click the **gear icon** (top right).
3. Check **Use your own OAuth credentials**.
4. Paste your **Client ID** and **Client secret** → close the dialog.
5. In the left list, expand **Calendar API v3** and select  
   `https://www.googleapis.com/auth/calendar`  
   (or paste that scope in the custom scope box).
6. Click **Authorize APIs** and sign in with your test user account. Grant the permissions.
7. Click **Exchange authorization code for tokens**.
8. Copy the **`refresh_token`** value (not the access token) into `.env` as `GOOGLE_REFRESH_TOKEN`.

If you do not see a refresh token, revoke the app at [Google Account permissions](https://myaccount.google.com/permissions) and repeat with your own credentials and “offline” access (Playground does this when configured as above).

### 7. First-time authentication check

From the project root:

```bash
npm run build
npm run dev
```

On success, stderr should include structured logs such as:

- `OAuth2 client initialized`
- `Authentication validated successfully`
- `google-calendar-mcp server connected and ready`

Then stop with `Ctrl+C`. For day-to-day use, configure an MCP client so it starts the server for you (next sections). You do **not** need to keep a terminal open after the client is configured.

---

## Running locally

### Build and start

```bash
npm run build
npm start
```

### Development (no build step)

```bash
npm run dev
```

### Type-check only

```bash
npm run typecheck
```

The process listens on **stdio**. Running it alone in a terminal will look idle after startup—that is expected. Use an MCP client or the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):

```bash
npx @modelcontextprotocol/inspector node dist/index.js
```

---

## Configuration

Use **absolute paths**. Prefer built `dist/index.js` after `npm run build`. Secrets can live in `.env` (loaded by the server) and/or in the client `env` block.

### Cursor

Project file: `.cursor/mcp.json` (or Cursor global MCP settings). See also [`examples/mcp-cursor.json`](examples/mcp-cursor.json).

```json
{
  "mcpServers": {
    "google-calendar": {
      "command": "node",
      "args": ["/absolute/path/to/google-calendar-mcp/dist/index.js"],
      "env": {
        "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
        "GOOGLE_CLIENT_SECRET": "your-client-secret",
        "GOOGLE_REFRESH_TOKEN": "your-refresh-token",
        "DEFAULT_CALENDAR_ID": "primary"
      }
    }
  }
}
```

Restart Cursor and confirm the server is connected under **Settings → MCP**.

### Claude Desktop

Config file:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "google-calendar": {
      "command": "node",
      "args": ["/absolute/path/to/google-calendar-mcp/dist/index.js"],
      "env": {
        "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
        "GOOGLE_CLIENT_SECRET": "your-client-secret",
        "GOOGLE_REFRESH_TOKEN": "your-refresh-token",
        "DEFAULT_CALENDAR_ID": "primary"
      }
    }
  }
}
```

### VS Code

Add under your MCP / Copilot MCP server settings (exact key may vary by extension):

```json
{
  "mcp": {
    "servers": {
      "google-calendar": {
        "command": "node",
        "args": ["/absolute/path/to/google-calendar-mcp/dist/index.js"],
        "env": {
          "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
          "GOOGLE_CLIENT_SECRET": "your-client-secret",
          "GOOGLE_REFRESH_TOKEN": "your-refresh-token"
        }
      }
    }
  }
}
```

---

## Available MCP tools

| Tool | Description |
| ---- | ----------- |
| `list_calendars` | Returns every calendar available to the authenticated user |
| `list_events` | Lists events in a time range (optional filters) |
| `search_events` | Free-text search via Google’s `q` parameter |
| `create_event` | Creates an event; returns created event details including id |
| `update_event` | Partially updates an event (only supplied fields) |
| `delete_event` | Deletes an event |
| `free_busy` | Returns busy periods for one or more calendars |

---

## Example prompts

Ask your AI client natural language such as:

- “List my calendars.”
- “What meetings do I have tomorrow?”
- “Search my calendar for dentist.”
- “Create a 1-hour event called Team Sync tomorrow at 10am.”
- “Am I free Friday afternoon?”

See [`examples/prompts.md`](examples/prompts.md) for more.

### Sample tool request

```json
{
  "name": "list_events",
  "arguments": {
    "timeMin": "2026-07-14T00:00:00Z",
    "timeMax": "2026-07-21T00:00:00Z",
    "maxResults": 5,
    "orderBy": "startTime"
  }
}
```

### Sample tool response (illustrative only)

```json
[
  {
    "id": "abc123example",
    "summary": "Team Sync",
    "start": { "dateTime": "2026-07-15T10:00:00-07:00" },
    "end": { "dateTime": "2026-07-15T11:00:00-07:00" },
    "status": "confirmed",
    "htmlLink": "https://www.google.com/calendar/event?eid=example"
  }
]
```

More samples: [`examples/sample-responses.md`](examples/sample-responses.md).

---

## Project structure

```
google-calendar-mcp/
├── src/
│   ├── index.ts              # Entry — stdio transport
│   ├── server.ts             # McpServer factory and tool registration
│   ├── auth.ts               # OAuth2 client and token refresh
│   ├── calendar.ts           # Google Calendar API wrapper
│   ├── types.ts              # Shared TypeScript types
│   ├── utils.ts              # Logging, error handling, helpers
│   └── tools/
│       ├── listCalendars.ts
│       ├── listEvents.ts
│       ├── searchEvents.ts
│       ├── createEvent.ts
│       ├── updateEvent.ts
│       ├── deleteEvent.ts
│       └── freeBusy.ts
├── examples/                 # Sample configs and prompts (no secrets)
├── .env.example
├── package.json
├── tsconfig.json
├── LICENSE
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── CHANGELOG.md
└── README.md
```

---

## Development workflow

```bash
npm install
cp .env.example .env   # add your credentials
npm run typecheck
npm run build
npm run dev            # or connect via MCP Inspector / Cursor
```

Contributions welcome—see [CONTRIBUTING.md](CONTRIBUTING.md).

---

## Troubleshooting

| Problem | What to try |
| ------- | ----------- |
| Missing environment variables | Copy `.env.example` → `.env` and set all three Google variables |
| Authentication failed (401) / invalid refresh token | Re-run OAuth Playground; ensure Client ID matches the token; revoke and re-authorize if needed |
| Permission denied (403) | Enable Google Calendar API; confirm scope includes calendar; ensure you are a consent screen test user |
| Rate limit (429) | Wait and retry; lower `maxResults` |
| `redirect_uri_mismatch` in Playground | Add `https://developers.google.com/oauthplayground` to authorized redirect URIs |
| MCP client cannot connect | Use absolute paths; run `npm run build` first; ensure Node 18+ is on `PATH` |
| Server seems frozen after start | Normal for stdio—clients must spawn and speak the protocol |
| Refresh token stops working after ~7 days | App is in Testing mode—see OAuth consent screen notes above |

Logs are written as JSON lines to **stderr**. Do not log secrets.

---

## FAQ

**Do I need to keep a terminal open for Cursor?**  
No. Once MCP is configured, Cursor starts and stops the server automatically.

**Is this multi-user?**  
No. One process uses one set of credentials and one Google account.

**Can I publish my `.env`?**  
Never. Treat client secret and refresh token like passwords.

**Can I use a service account?**  
This project is built for user OAuth with a refresh token, not service accounts.

**Why no Docker?**  
MCP stdio clients spawn a local command. Running Node directly is the supported path.

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).

---

## License

This project is licensed under the [MIT License](LICENSE).

---

## Acknowledgements

- [Model Context Protocol](https://modelcontextprotocol.io/) and the [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Google Calendar API](https://developers.google.com/calendar) and the [googleapis](https://github.com/googleapis/google-api-nodejs-client) Node.js client
- Contributor Covenant for the Code of Conduct template