Skip to main content
17 toolsOAuth bearer
gsheets_example.py
Read a range, write or append rows, clear cells, create a spreadsheet, read its structure, and apply structural or formatting edits in a batch. Ranges are A1 notation — Sheet1!A1:C10 — and on get, update, append and clear they travel in the URL path. Sheets speaks protobuf JSON, which is a poor thing to hand a model. Two Format markers absorb that: the model sends a plain 2-D array and a list of field paths, and the API receives the encodings it expects.

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 17 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.
gsheets_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.
gsheets_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.
gsheets_server.py
The scope covers every spreadsheet the account can open; Sheets has no per-file scope, so narrowing access is a matter of which account you delegate, not which scope you ask for.

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.

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. Every tool declares quota_cost 1. Sheets meters requests per minute per user rather than charging different amounts per call, so a uniform 1 is the honest number; the link in the wire table is where the real limits live.

The two transforms

ValueRange.values is marked Format("proto_json"). The model sends what anyone would write by hand:
gsheets_append_rows.py
and the transform produces the protobuf-JSON encoding Sheets requires on the way out, and reverses it on the way back. fields on the batchUpdate requests is marked Format("field_mask"). A FieldMask is a comma-joined string on the wire and a list of paths in the schema, so the model sends ["userEnteredValue", "userEnteredFormat"] and Sheets receives userEnteredValue,userEnteredFormat. See transforms.

Gotchas

RAW writes =SUM(A1:A2) as the literal nine characters. USER_ENTERED parses it the way typing it into the cell would — which also means 1/2 becomes a date and a leading + becomes a formula. The argument is required on spreadsheets_values_append, spreadsheets_values_update and the batch writes, deliberately: there is no safe default, and the wrong one is silent.
The range you pass to spreadsheets_values_append is a search hint: Sheets finds the table that overlaps it and appends below that table’s last row. Passing Sheet1!A1 does not mean “write at A1”. To write at an exact address, use spreadsheets_values_update.
Sheets has no cursor. A large sheet is read by asking for a narrower A1 range, which is why no tool here declares a Pagination — and why next_page_args on these tools would have nothing to return.
Sheet1!A1:C10 goes into the path, and a sheet name containing a space or an apostrophe needs Sheets’ own quoting — 'My Sheet'!A1:C10. The escaping for the URL is handled; the quoting for Sheets’ grammar is yours.
Its one argument is spreadsheet, the same resource spreadsheets_get returns. Titles, sheet tabs and initial data all go inside it; there is no flat title shortcut.
  • Google — consent screen, scopes, refresh
  • Transformsproto_json and field_mask in detail