Connect Google Calendar to Claude: Setup and Scopes
TL;DR: Connecting Google Calendar to Claude means running an OAuth flow against Google's identity system and handing the resulting token to an MCP server. The work splits into three parts: create a Google Cloud project with the Calendar API enabled, configure an OAuth consent screen and client credentials, and wire the token into your connector. The decisions that matter are scope and audience. Use the read-only calendar scope unless you genuinely need Claude to create or modify events, because read-only is a much smaller thing to get wrong. Keep the consent screen in testing mode with your own account as a test user for personal use, which avoids Google's verification process entirely at the cost of a refresh token that expires every seven days. Most failures are redirect URI mismatches, unenabled APIs, or scope changes that were never re-consented. Timezone handling deserves attention because Calendar events carry timezone data that is easy to lose.
What can Claude do with your calendar?
With read access, Claude can list your calendars, read events across a date range, see attendees and locations, and check free or busy blocks. That covers most of what people actually want: summarising the week ahead, finding a gap, answering "when did I last meet this person", or feeding schedule context into some other piece of work.
With write access it can create events, update them, and delete them. That is genuinely useful for scheduling work, and it is also the point at which a mistake becomes visible to other people. A read-only connector that misunderstands your week produces a wrong summary. A read-write connector that misunderstands your week sends meeting invitations to your colleagues.
Start read-only. Add write later, deliberately, once you know what you want it for.
How do I set up the Google Cloud side?
Google's Calendar API sits behind a Cloud project. You need one even for purely personal use.
- Create a project in the Google Cloud console, or select an existing one. Give it a name you will recognise in six months.
- Enable the Google Calendar API for that project. Find it in the API library and enable it explicitly. An unenabled API produces a permission error that looks like an auth problem but is not.
- Configure the OAuth consent screen. Choose "External" as the user type unless you are inside a Google Workspace organisation and only your own users need access, in which case "Internal" is simpler and skips verification.
- Fill in the required fields: app name, support email, developer contact. These appear on the consent screen you will see when you authorise.
- Add the scopes you need. For read-only calendar access, add
https://www.googleapis.com/auth/calendar.readonly. For full access,https://www.googleapis.com/auth/calendar. Add only what you will use. - Add your own Google account as a test user while the app is in testing mode.
- Create OAuth client credentials. Choose the client type that matches how your MCP server will run, typically a desktop or web application client.
- Download the client credentials JSON and store it outside your repository.
The credentials file contains a client ID and a client secret. The secret is a real secret. It should live alongside your other credentials and never appear in a commit.
Which scopes should I ask for?
Google's Calendar scopes form a ladder. Climb it only as far as you need.
calendar.readonlygrants read access to calendars and events. This is the right starting point.calendar.events.readonlyis narrower still, covering events without full calendar metadata.calendar.eventsgrants read and write on events but not on calendar settings.calendargrants full access to calendars and events, including creating and deleting calendars.
The narrower the scope, the smaller the consent screen warning and the smaller the consequence of a mistake. Google also treats some Calendar scopes as sensitive or restricted, which means an app in production mode needs verification before external users can consent. For a personal connector, staying in testing mode with yourself as the only test user sidesteps that entirely.
The trade-off of testing mode is refresh token lifetime. Refresh tokens issued by an app in testing mode expire after roughly a week, so you will re-authorise regularly. For personal use that is a mild annoyance. If it becomes intolerable, publishing the app is the fix, and verification is the price.
How does the OAuth flow actually work?
Understanding the flow makes the errors legible. The authorisation code flow goes like this:
- Your MCP server builds an authorisation URL containing your client ID, the scopes you want, and a redirect URI, then opens it in a browser.
- You sign in to Google and approve the consent screen.
- Google redirects your browser to the redirect URI with a short-lived authorisation code attached.
- Your server exchanges that code, together with the client secret, for an access token and a refresh token.
- The access token is used to call the Calendar API. It expires within about an hour.
- When it expires, the server uses the refresh token to obtain a new access token without human involvement.
The refresh token is the durable credential and the one that matters. Access tokens are short-lived and low value. A leaked refresh token grants ongoing access until it is revoked, so treat it with the same care as a password.
Google only issues a refresh token on the first authorisation for a given client and account, unless the request explicitly asks for offline access and forces the consent prompt. If your server authenticates once and then loses access an hour later, this is why: it never received a refresh token.
The general shape of this flow is the same one covered from a different angle in supabase-auth-google-oauth, and the deeper mechanics apply across every OAuth-backed connector you will set up.
How do I wire it into the MCP connector?
Once you have client credentials, the connector needs three things: the client ID, the client secret, and somewhere to persist the token after the first authorisation.
{
"mcpServers": {
"google-calendar": {
"command": "/absolute/path/to/python",
"args": ["/absolute/path/to/calendar_server.py"],
"env": {
"GOOGLE_CLIENT_ID": "YOUR_GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET": "YOUR_GOOGLE_CLIENT_SECRET",
"GOOGLE_TOKEN_PATH": "/absolute/path/to/token-store.json"
}
}
}
}
Use absolute paths for everything. The client launches your server without your shell environment, so a relative path or a ~ will resolve somewhere you did not intend, or nowhere at all. This is the single most common cause of a server that runs perfectly in your terminal and fails under the client, and it is covered in more depth in mcp-connector-troubleshooting.
The token store file will contain a refresh token after your first authorisation. That file is a credential. It belongs in a directory that is not your repository, with restrictive file permissions:
# Keep the token store outside any repo and lock it down
mkdir -p ~/.config/claude-connectors
chmod 700 ~/.config/claude-connectors
chmod 600 ~/.config/claude-connectors/token-store.json
If you are building the server yourself rather than using an existing one, the FastMCP patterns in build-mcp-server-python-fastmcp give you the structure, and the Calendar-specific work is mostly token lifecycle and timezone correctness.
Why does my redirect URI keep failing?
Because Google matches it exactly, character for character. This is not a fuzzy comparison.
http://localhost:8080andhttp://localhost:8080/are different URIs.http://localhostandhttp://127.0.0.1are different URIs.httpandhttpsare different URIs.- A different port is a different URI.
Whatever your server sends as the redirect URI must appear verbatim in the authorised redirect URIs list for that OAuth client. If your server picks a random free port at runtime, either register a fixed port or register the loopback pattern Google supports for desktop clients.
When you see redirect_uri_mismatch, the error page usually shows the URI that was sent. Copy that exact string into the Cloud console. Do not retype it.
How do I handle timezones without breaking things?
Calendar events are timezone-aware and the API reflects that. An event has a start and end, each of which is either a date (for all-day events) or a dateTime with an offset, plus an associated timezone.
Two failure patterns are worth knowing:
Dropping the offset. If you parse a dateTime and discard the timezone, every subsequent calculation is wrong by however many hours you are away from UTC. This surfaces as events appearing at plausible but incorrect times, which is worse than an obvious failure because you may not notice.
Assuming the calendar timezone. A calendar has a default timezone, but individual events can override it, and attendees in other regions see their own. When you query a range, be explicit about the timezone you mean rather than relying on a default that might be the server's rather than yours.
For anything scheduled or recurring, be especially careful. Daylight saving transitions mean that "every Tuesday at 9am" is not a fixed number of hours apart, and naive arithmetic drifts. If you are building recurring automation on top of calendar data, the scheduling considerations in schedule-recurring-claude-tasks apply directly.
What are the security rules for calendar access?
Your calendar is one of the more revealing data sources you own. It contains who you meet, when, where, and often why. Treat access to it seriously.
- Use the narrowest scope that does the job.
calendar.readonlyfor anything that only reads. - Never commit the client secret or the token store. Add both paths to
.gitignorebefore they exist. - Never paste a real token into a chat, an issue, or a screenshot. Use placeholders like
YOUR_GOOGLE_CLIENT_SECRETandsk-REPLACE_MEin anything you share. - Revoke rather than delete. If a refresh token is exposed, revoke it in your Google account's third-party access settings. Deleting the local file does nothing to invalidate the credential.
- Review third-party access to your Google account periodically and remove the entries you no longer recognise.
- If you share a calendar with others, remember that read access to your calendar may include their event details too. The scope you grant is not only about your own data.
The general principle from skill-security-best-practices holds here: assume any credential that leaves a controlled environment is compromised, and design so that rotation is cheap.
Frequently Asked Questions
Why does my connector stop working after about a week?
Because your OAuth consent screen is in testing mode. Google expires refresh tokens issued by unpublished apps after roughly seven days, so the connector re-authenticates fine on day one and dies quietly a week later. The options are to re-authorise weekly, publish the app and go through Google's verification for sensitive scopes, or, if you are inside a Workspace organisation, set the consent screen to Internal, which avoids the limit.
What is the difference between the calendar and calendar.readonly scopes?
calendar.readonly lets Claude read your calendars and events but change nothing. calendar grants full read and write, including creating and deleting events and calendars. Start with readonly, because a read-only mistake produces a wrong answer while a read-write mistake can send invitations to real people. Move up only when you have a specific workflow that needs to write, and consider calendar.events as an intermediate step that allows event changes without calendar-level control.
I keep getting redirect_uri_mismatch. What am I missing?
Google compares the redirect URI as an exact string. A trailing slash, a different port, localhost versus 127.0.0.1, or http versus https all count as different URIs. The error page normally shows the exact URI your application sent. Copy that string directly into the authorised redirect URIs for your OAuth client rather than retyping it, and if your server chooses a port dynamically, pin it to a fixed value instead.
Where does the refresh token live and how do I protect it?
Wherever your MCP server persists its token store, which you should place outside any repository, in a directory with restrictive permissions such as chmod 700, with the file itself at chmod 600. The refresh token is the durable credential and grants ongoing access to your calendar until revoked. Never commit it, never include it in a backup that syncs to a shared location, and if it is ever exposed, revoke it through your Google account's third-party access settings rather than just deleting the local file.
Can Claude see calendars that others have shared with me?
Yes, if the scope allows it and the sharing permissions on those calendars grant your account access. The Calendar API returns the calendars in your list, which includes shared ones. This is worth thinking about before granting access, because a colleague who shared their calendar with you did not consent to an automated system reading it. If that matters in your context, consider restricting which calendars the connector queries rather than relying on the scope alone.