# google-calendar-mcp: giving an AI assistant a real Google Calendar

> This MCP server exposes Google Calendar to AI assistants over the Model Context Protocol, with multi-account and multi-calendar queries, cross-account conflict detection, free-busy checks, natural language date parsing and import of events from images, PDFs or links. The setup cost is a Google Cloud project and an OAuth client, and the default OAuth mode expires tokens after seven days.

**nspady/google-calendar-mcp** — MCP integration for Google Calendar to manage events.

- Repository: https://github.com/nspady/google-calendar-mcp
- Stars: 1,212 · Forks: 335
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/nspady-google-calendar-mcp

## What the tool actually exposes to the model

The feature list is the best description of the server's scope, and it is broader than a plain calendar reader.

Multi-account support: connect multiple Google accounts, for example work and personal, and query them simultaneously. Multi-calendar support: list events from multiple calendars in a single request. Cross-account conflicts: detect overlapping events across any combination of calendars. Event management: create, update, delete and search calendar events. Recurring events, with advanced modification capabilities. Free-busy queries to check availability across calendars. Smart scheduling, meaning natural language understanding for dates and times. And intelligent import, adding calendar events from images, PDFs or web links.

Three of those are worth pausing on.

Cross-account conflict detection is the one that is genuinely hard to do by hand. If your work calendar and your personal calendar are separate Google accounts, no single Calendar API call tells you whether a meeting overlaps a dentist appointment, and the assistant has to hold both accounts' data at once to answer that.

The natural language date handling is the second. An assistant that says next Tuesday afternoon has to be understood by something that can parse a relative date against a timezone, and a mistake there produces a meeting on the wrong day with no error message.

The import from images, PDFs or links is the third, and it is the one with the widest blast radius. An assistant that can read an event out of a PDF and put it on a calendar is also an assistant that can read a wrong date out of a PDF and put it on a calendar, and a wrong event on a shared calendar is visible to other people.

The project is MIT licensed, written in TypeScript, and the last push was on 2026-10-13.

## Three install routes, and npx is the recommended one

The installation section offers three options, and the first is marked recommended.

Option 1 is npx, used by adding the server to your Claude Desktop configuration.

```json
{
  "mcpServers": {
    "google-calendar": {
      "command": "npx",
      "args": ["@cocal/google-calendar-mcp"],
      "env": {
        "GOOGLE_OAUTH_CREDENTIALS": "/path/to/your/gcp-oauth.keys.json"
      }
    }
  }
}
```

The config file lives at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. The package name in the args array is @cocal/google-calendar-mcp, which is the npm name in package.json, and the bin is registered as google-calendar-mcp pointing at build/index.js.

The warning attached to this route is not optional. When using npx, you must specify the credentials file path using the GOOGLE_OAUTH_CREDENTIALS environment variable.

Option 2 is a local install from a clone.

```bash
git clone https://github.com/nspady/google-calendar-mcp.git
cd google-calendar-mcp
npm install
npm run build
```

Option 3 is Docker.

```bash
git clone https://github.com/nspady/google-calendar-mcp.git
cd google-calendar-mcp
cp /path/to/your/gcp-oauth.keys.json .
docker compose up
```

The Docker deployment guide covers the detailed configuration options including HTTP transport mode.

Node 20 or newer is required, per the engines field in package.json.

## The seven day token problem, and the two ways out

The authentication section is where most of the friction lives, and it is worth reading carefully because the default is a trial mode that expires.

While an app is in test mode the auth tokens will expire after 1 week and need to be refreshed. The README notes that the OAuth consent will not allow you to proceed until the test user has propagated, and that it might take a few minutes for the test user to be added.

The first run requires asking the assistant to authenticate, for example by saying authenticate with Google Calendar, and the step is required before using any calendar tool, because without it requests will fail with a -32600 error. The same steps apply for Claude Code, just from a CLI session.

When the tokens do expire, there is a re-authentication command.

```bash
export GOOGLE_OAUTH_CREDENTIALS="/path/to/your/gcp-oauth.keys.json"
npx @cocal/google-calendar-mcp auth
```

For a local install, the equivalent is npm run auth, which runs node build/auth-server.js.

The way to avoid weekly re-authentication is to publish the app. Go to the OAuth consent screen, click PUBLISH APP and confirm. Your tokens will no longer expire after 7 days but Google will show a warning about the app being unverified.

That trade is the whole story of this server's setup. Unverified means every user sees a warning screen and, more importantly, that the app is not reviewed by Google, so the security model rests entirely on your own configuration. The authentication guide at docs/authentication.md covers the details.

The second route out is the service account option, described as optional. A service account carries its own long-lived key, so there is no browser consent step and no refresh token to expire. The README frames it as the alternative to publishing your OAuth app when the weekly re-authentication is impractical, naming headless servers, containers, CI, or an app you would rather leave in Testing status.

## Service accounts and the calendar sharing model

The service account route is not a drop-in replacement for OAuth, and the README is explicit about the difference through a table of which calendars it works for.

For a personal @gmail.com calendar, access is granted by sharing the calendar with the service account's e-mail address. That is the key mechanic. A service account is a separate identity, so a shared personal calendar has to be explicitly shared with it, and it can only see what has been shared with it.

The alternative for the OAuth route is the opposite model. The user consents, the token belongs to the user, and the assistant acts with the user's own permissions across every calendar that user can see, including the ones they did not explicitly share with a robot.

For an assistant that reads your week and puts things in it, the second model is usually the right one, because it matches the mental model of asking an assistant about your own calendar. The first model is right for a headless job that needs to write to one specific shared calendar without a browser in the loop.

The repository also carries a gcp-oauth.keys.example.json file, which is the shape of the credentials file you are expected to produce in the Google Cloud console, and the .env.example documents the environment variable names the server reads, including GOOGLE_OAUTH_CREDENTIALS and the optional GOOGLE_CALENDAR_MCP_TOKEN_PATH for a custom token storage location.

There is also an account-manager script for the multi-account case, invoked as node scripts/account-manager.js auth test-primary in the example configuration, which is how more than one Google account gets authenticated.

## HTTP mode has no authentication layer, and the file says so

The .env.example file contains the clearest warning in the repository, and it is the first thing to check before deploying this anywhere.

TRANSPORT=stdio is marked recommended for Claude Desktop integration. The HTTP settings block says WARNING: HTTP mode is for local development only - no authentication layer. PORT is 3000, and HOST is 127.0.0.1 with the comment that 0.0.0.0 exposes to the network, not recommended.

So there are two transports. Stdio is the standard MCP transport where the client spawns the server as a child process and talks to it over pipes, which means the server is only reachable by the client that spawned it. HTTP is a network transport where the server listens on a port and any client that can reach that port can call it.

An MCP server that reads and writes a real calendar over HTTP with no authentication layer is an open invitation to anyone who can reach the port. The default binds to 127.0.0.1, which limits it to the local machine, but the compose file publishes the ports, and the .env.example warns against changing the host to 0.0.0.0.

The docker-compose.yml shows the port set in practice: 3000 for the HTTP mode MCP server, and 3500 through 3505 for OAuth authentication. The healthcheck is written so that it only probes when the transport is http, curling the health endpoint on the port, and exits successfully otherwise since a stdio server has nothing to curl.

The compose file also applies resource limits of 512 megabytes of memory and 1.0 CPU with reservations of 256 megabytes and 0.5 CPU, and sets no-new-privileges:true as a security option.

## A container that runs as a non-root user with a pruned build

The Dockerfile is a compact and carefully ordered build, and it is worth reading for the security choices rather than the mechanics.

It starts from node:22-alpine, creates a group and a user with the same uid and gid, 1001, and switches to that user at the end so the server does not run as root.

The layer order is deliberate. The package files are copied first for dependency caching, then the build scripts and sources, then npm ci runs with the dev dependencies included because the build needs them. After npm run build, npm prune --production removes the dev dependencies to reduce the image size. Then the config directory is created and owned by the non-root user, and the application directory is chowned as well.

The config directory is /home/nodejs/.config/google-calendar-mcp, and that path is exactly where the compose file mounts the persistent calendar-tokens volume. So the token store lives outside the container image and survives a rebuild, which is the difference between a working deployment and one that asks for consent on every restart.

Only port 3000 is exposed, and the default command runs the built entry point directly, node build/index.js, with a comment explaining why it avoids npm so that npm's own output does not pollute the stream.

The compose file mounts the OAuth keys file read-only, which is the right call for a credential you never want the container to be able to rewrite.

## The tooling and the state of the project

The package.json scripts show a project with a real test matrix, and the test names are informative about what the server actually does.

The unit tests are vitest run src/tests/unit. The integration tests are split into direct-integration, multi-account with two separate files, claude-mcp-integration and openai-mcp-integration, so the server is exercised against more than one MCP client. The direct and multi-account variants rebuild the project first, since the integration tests run against the compiled output.

The lint script is tsc --noEmit with a separate tsconfig.lint.json, and there is a check:imports script, which suggests the project enforces its own import boundaries rather than relying on a linter plugin for that.

There is a dev script, a dev server for the auth flow through build/auth-server.js, and an account manager script.

The repository layout is more unusual than a typical server repository. Alongside the usual src/, scripts/, docs/ and examples/, there are .claude/, instructions/, future_features/ and MULTI_ACCOUNT_IMPLEMENTATION.md, plus AGENTS.md, CLAUDE.md and a .cursorignore, which says the project is set up to be worked on with AI coding assistants and to carry its own instruction files. A .release-please-manifest.json and CHANGELOG.md indicate the releases are cut by release-please.

The release history shows v2.7.0 on 2026-09-28, v2.6.3 on 2026-09-02 and v2.6.2 on 2026-06-01, with the last push on 2026-10-13, so the tree is being worked on.

## Conclusion

google-calendar-mcp suits a user who wants an AI assistant to read and write their real calendar, because it goes through the normal OAuth path rather than a service account shortcut, so the assistant acts as the person rather than as a robot with a domain-wide key. It is the wrong tool if you need unattended automation, because the default test-mode OAuth expires tokens weekly and the only two answers are publishing the app unverified or moving to a service account. Before you deploy it anywhere beyond your own machine, read the HTTP mode warning in .env.example, which states that HTTP mode is for local development only with no authentication layer.

## FAQ

### How do I install google-calendar-mcp for Claude Desktop?

Add it to the Claude Desktop config as an npx server, with the args array pointing at @cocal/google-calendar-mcp and the GOOGLE_OAUTH_CREDENTIALS environment variable set to the path of your credentials JSON. The README marks the npx route as recommended.

### Why do my calendar requests fail with a -32600 error?

The README states that asking the assistant to authenticate with the Google Calendar MCP server is required before using any calendar tool, and that without it requests will fail with a -32600 error.

### How do I stop the OAuth tokens from expiring every seven days?

Publish the app. Go to the OAuth consent screen in the Google Cloud Console, click PUBLISH APP and confirm. Tokens then no longer expire after 7 days, but Google will show a warning that the app is unverified.

### When should I use a service account instead of OAuth?

The README presents it as the alternative when weekly re-authentication is impractical, such as headless servers, containers, CI, or an app you would rather leave in Testing status. A service account carries its own long-lived key, so there is no browser consent step and no refresh token to expire.

### Is the HTTP transport mode safe to expose on a network?

The .env.example states that HTTP mode is for local development only with no authentication layer, and warns that 0.0.0.0 exposes to the network and is not recommended. The default host is 127.0.0.1.

## Sources

- [Issues](https://github.com/nspady/google-calendar-mcp/issues)
- [License: MIT](https://github.com/nspady/google-calendar-mcp/blob/main/LICENSE)
- [nspady/google-calendar-mcp on GitHub](https://github.com/nspady/google-calendar-mcp)
- [README](https://github.com/nspady/google-calendar-mcp/blob/main/README.md)
- [Releases](https://github.com/nspady/google-calendar-mcp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nspady-google-calendar-mcp
