Skip to main content
13 toolsOAuth bearer
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

This pack takes a Google OAuth bearer token, and configure() is optional when $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? Your own account 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.
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 is not: it wants singleEvents, maxResults, timeMin, orderBy. Send single_events=true and Google does not complain. It returns 200 with a correct-looking list of events — recurring ones unexpanded, because the parameter that asked for expansion was a name it did not recognise and dropped. The agent then reasons over a wrong answer that arrived with every sign of being right. 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.