Skip to main content
13 toolsOAuth
gcalendar_example.py
Create, list and delete events on one calendar. Pass "primary" as calendar_id for the authenticated account’s own calendar, or the calendar’s address for a shared one. calendar_list_list is where an address comes from when you do not already hold one. Four tools is the whole pack, and the interesting part is not the surface. It is that events_list takes seventeen filter parameters beside the calendar id, and every one of them fails silently if it goes out under the wrong name.

Authenticating

Before the first call, Google Calendar needs a Google OAuth client. Google issues no API key for it: credentials come only from an app you register in the Cloud console. Set up Google walks through every screen — about ten minutes, done once, and the same client serves all six Google packs. This pack takes a Google OAuth bearer token, and configure() is optional when a renewable grant or $GOOGLE_ACCESS_TOKEN is set. Which credential provider you hand it depends on whose account the calls run as.
Refer to Google’s provider page for the “GOOGLE” constant the snippets below name, the scopes these 13 tools ask for, and this server’s refresh behaviour.

A token you hold

For a script, or a notebook. EnvTokenProvider re-reads the variable on every call, so a token rotated beside the process is picked up without a restart; StaticTokenProvider takes one you already hold as a string. Neither renews anything, so the calls stop when the token expires.
gcalendar_script.py

One account, refreshed

For an agent or a server acting as you. OAuth2Client turns a client registration and a stored refresh token into an access token, and renews it before it lapses.
gcalendar_agent.py
No refresh token yet? Set up Google is the one-time consent flow that hands you one.

Many end users

For a product whose users each connect their own account. SubjectProvider builds one credential per user through a factory you write, and use_subject names the user a call acts for. Your users’ accounts is the consent route inside your app; serving many users is the per-subject cache and its eviction. The client is handed a function that reads the stored grant, not a copy of the refresh token. A server runs several workers, and a copy goes stale the first time another worker refreshes: More than one process.
gcalendar_server.py
calendar.events covers events on calendars the account can already reach. It does not cover reading which calendars those are, so calendar_list_list asks for calendar.readonly instead and consent has to name both.

The client

oauth_tool_factory is the whole client: a thin wrapper over httpx that attaches your token and these endpoint constants to each request. google-api-python-client and google-auth do not enter your dependency tree.
credentials is whichever of the three you built in Authenticating. A pack takes it through configure(); a client you build takes the same object as credential_provider, and has no configure() of its own.

Paging through a list

A cursor belongs to the tool that returns it, so it is declared on that tool’s builder call:
gcalendar_pagination.py
Pagination is declared on events_list, events_instances and calendar_list_list. The other 10 take no cursor.

Tools

Each is a Tool, called with ainvoke as in the snippet above. The name links to its parameters, its response and what it costs.

Why query casing is the load-bearing line

Charter’s default query casing is snake, because most REST APIs are snake. Google documents camelCase: singleEvents, maxResults, timeMin, iCalUID. Calendar answers 200 to a query parameter it does not recognise, and ignores it. It does recognise some snake_case spellings, the ones that match its own internal field names: single_events and max_results both took effect when tested on 2026-10-24. i_cal_uid did not. Filtering by i_cal_uid= an id that matches nothing returned all 94 of the test calendar’s events, where iCalUID= returned none. The agent then reasons over a wrong answer that arrived with every sign of being right, and which spellings happen to work is nowhere documented. So the factory declares query_case="camel", and the key case cascade converts every parameter on its way out. The schema stays snake, which is what a Python caller expects; the wire gets camel, which is what Google requires. The conformance suite checks this for every Google pack, because it is the kind of mistake that never surfaces as an error.

Gotchas

events_insert takes the event as a single event argument, with the query parameters (send_updates, conference_data_version, max_attendees) beside it rather than inside it.
time_min, time_max and an event’s start.dateTime are RFC 3339 timestamps with a UTC offset — 2026-09-02T10:00:00+02:00 or ...Z. Google rejects a naive timestamp. An all-day event uses date instead of dateTime, and the two are mutually exclusive.
Without single_events=true, a weekly standup is one event with a recurrence rule, not the instances an agent is usually asking about. With it, Google expands the series and order_by="startTime" becomes legal — it is rejected otherwise.
There is no restore tool in this pack: once an agent calls events_delete, undoing it is a job for the Calendar UI. send_updates decides whether attendees are notified of the cancellation, and it is a query parameter on the delete call rather than a property of the event.