# MCP server

> Let Claude, ChatGPT, Cursor or any MCP client work in your Google Sheets, Drive, Docs, PDFs, BigQuery and Gmail: setup in minutes, permissions and all 41 tools.

HTML version: https://developers.sheetgo.com/mcp/

Model Context Protocol

## Sheetgo MCP server

Connect an AI assistant to your Google Sheets, Drive, Docs, PDFs, BigQuery and Gmail with one URL and your Sheetgo API key.

Endpoint: `https://api.sheetgo.com/mcp` · Transport: Streamable HTTP · Auth: `Authorization: Bearer sg_…` · Tools: 41

### What MCP is, and what this server does

The **Model Context Protocol (MCP)** is an open standard that lets AI applications use outside tools. An *MCP client* (the assistant or IDE) connects to an *MCP server*, asks it which *tools* it has (`tools/list`), and calls them (`tools/call`) with JSON arguments while it works on your request. Each tool has a name, a description, a JSON Schema for its inputs, and hints that say whether it only reads, changes data, or destroys data. The assistant decides which tool to call; many clients ask you to approve calls, especially the ones that change data.

The **Sheetgo MCP server** is a remote MCP server. It works as a **credential broker**:

- Your client sends your Sheetgo API key. The server finds the Sheetgo user the key belongs to.
- The server calls Google with **that user's Google connection, stored in Sheetgo** and refreshed automatically. You never give the assistant a Google password or token, and the server never forwards your Sheetgo key to Google.
- Every call can reach exactly the Google files *your* Google account can reach, limited further by the key's scopes.
- The server is **stateless**: there is no session to keep. Each request carries the key and stands alone.
- It does **not** manage Sheetgo workflows or automations. Use the [REST API](/api.md#api) for that.

### Requirements

| You need | Why |
| --- | --- |
| A Sheetgo account with Google connected | The tools act with your Google connection stored in Sheetgo. If that connection expires or is revoked, tools answer with a "reconnect Google in Sheetgo" error. |
| A Sheetgo API key (`sg_…`) | The only accepted credential. A missing or wrong key gets HTTP `401` with an empty body. |
| An MCP client that supports **remote servers over Streamable HTTP** and lets you set a **request header** | The key travels in the `Authorization` header. Clients that only start local (stdio) servers can use the `mcp-remote` bridge (below). |
| For `bigquery_query`: BigQuery authorised in Sheetgo, and IAM access to the project | Without the BigQuery permission the tool answers "authorize BigQuery in the web app". Queries are billed to the project you name. |
| For the `gmail_*` tools and `sheets_append_gmail_messages`: Gmail read access given to Sheetgo | Without it the tools answer "Sheetgo does not have permission to read this Gmail account". The tools only read mail; they never send, change or delete it. |
| Network access to `api.sheetgo.com` over HTTPS | The server is remote; nothing is installed on your machine. |

**Not supported yet:** connector screens that only offer an OAuth sign-in and no custom header (some web chat apps) cannot connect, because the server accepts only an API key. Microsoft files (OneDrive, SharePoint, Excel) are not available through MCP; use the REST API for them.

### Connect a client

Replace `sg_YOUR_KEY` with your key. After connecting, call the `whoami` tool: it returns the email of the Sheetgo user the key belongs to, which proves the connection works.

#### Claude Code

```
claude mcp add --scope user --transport http sheetgo \
  https://api.sheetgo.com/mcp \
  --header "Authorization: Bearer sg_YOUR_KEY"
```

`--scope user` makes the server available in every project; leave it out for the current project only. Check it with `claude mcp list`, or `/mcp` inside a session. Tools then appear as `mcp__sheetgo__<tool>`. A server added while a session is open loads after a reconnect or in a new session.

#### VS Code (`.vscode/mcp.json`)

```
{
  "inputs": [
    { "type": "promptString", "id": "sheetgo-key", "description": "Sheetgo API key", "password": true }
  ],
  "servers": {
    "sheetgo": {
      "type": "http",
      "url": "https://api.sheetgo.com/mcp",
      "headers": { "Authorization": "Bearer ${input:sheetgo-key}" }
    }
  }
}
```

#### Cursor (`~/.cursor/mcp.json`) and other clients with a JSON config

```
{
  "mcpServers": {
    "sheetgo": {
      "url": "https://api.sheetgo.com/mcp",
      "headers": { "Authorization": "Bearer sg_YOUR_KEY" }
    }
  }
}
```

Field names differ a little between clients (some want `"type": "http"` or `"transport": "streamable-http"`). What matters is the URL and the `Authorization` header. Check your client's documentation.

#### Clients that only run local servers (for example Claude Desktop's config file)

Use the open-source `mcp-remote` bridge, which runs locally with Node.js and forwards to the remote server:

```
{
  "mcpServers": {
    "sheetgo": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.sheetgo.com/mcp",
               "--header", "Authorization:${SHEETGO_AUTH}"],
      "env": { "SHEETGO_AUTH": "Bearer sg_YOUR_KEY" }
    }
  }
}
```

#### Test with curl (no client)

The server speaks JSON-RPC 2.0. Send `Accept: application/json, text/event-stream`; replies come as a server-sent event whose `data:` line holds the JSON.

```
curl -s https://api.sheetgo.com/mcp \
  -H "Authorization: Bearer $SHEETGO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

curl -s https://api.sheetgo.com/mcp \
  -H "Authorization: Bearer $SHEETGO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"whoami","arguments":{}}}'
```

### How the tools work

- **Ids.** Tools take Google Drive ids (`file_id`, `folder_id`). The id is the long part of the file's URL: `docs.google.com/spreadsheets/d/<id>/edit`. If you only have a name or a pasted link, call `drive_search_files` first.
- **Hints.** Each tool is marked Read-only, Writes or Destructive, and says whether it is *idempotent* (repeating the same call lands the same result). Not-idempotent tools, such as `sheets_append_rows` or `drive_copy_file`, create something new every time: do not retry them blindly.
- **Dry runs.** The destructive tools, plus `docs_replace_text`, `docs_append_content`, `pdf_merge` and `bigquery_query`, accept `dry_run: true` to preview the effect without changing anything.
- **Paging.** Large reads come back in windows. Pass the `cursor`, `page_token` or `next_start_page` from one reply into the next call; `null` means you reached the end. For example `sheets_get_values` returns at most 1000 rows per call (200 by default).
- **Values.** Sheet values are read *formatted*, as people see them (dates as text). Writes take a rectangular list of rows, each a list of cells.
- **Heavy file tools.** `pdf_extract_text`, `pdf_extract_tables`, `pdf_ocr`, `pdf_get_metadata`, `export_to_pdf`, `pdf_merge` and `spreadsheet_read_versions` download whole files. Each key runs one of them at a time; if the server is busy you get a retryable "busy" error.
- **Rate limit.** 120 calls per minute per API key (default). Space out bulk work.
- **Privacy.** The server logs which tool ran, the file id and how long it took, never cell values or document text.

### Errors

A failed tool call comes back as an MCP result with `isError: true` and a JSON body that tells you what to do next:

```
{
  "isError": true,
  "isRetryable": false,
  "code": 103,
  "kind": "reconnect",
  "message": "Your Google connection is no longer valid. Reconnect Google in Sheetgo and retry.",
  "messageId": "…",
  "messageValues": {},
  "suggestedAction": "…"
}
```

| What you see | Meaning | What to do |
| --- | --- | --- |
| HTTP `401`, empty body | Missing, wrong or revoked API key. | Check the `Authorization: Bearer sg_…` header, or create a new key. |
| `code: 103`, "Reconnect Google in Sheetgo" | The Google connection stored in Sheetgo expired or was revoked. | Sign in to Sheetgo and reconnect Google, then retry. Not retryable before that. |
| "This Sheetgo API key is not permitted to …" | The key's scopes do not allow this tool. | Ask the key owner to add the scope named in the message (for example `sheets.write`). |
| "authorize BigQuery in the web app" | The Google connection has no BigQuery permission. | Authorise BigQuery in Sheetgo. Reconnecting Google alone does not add it. |
| "Sheetgo does not have permission to read this Gmail account" | The Google connection has no Gmail read permission. | Reconnect Google in Sheetgo and approve Gmail access, then retry. |
| `isRetryable: true`, "The server is busy…" | A heavy file tool is already running for this key, or the server is at capacity. | Wait a few seconds and retry, asking for fewer pages or files. |
| `isRetryable: true`, other errors | A temporary problem reaching Google. | Retry with backoff. |
| `isRetryable: false`, other errors | Bad input (wrong id, invalid range, non-SELECT query…) or a limit. | Read `message` and fix the arguments. Do not repeat the same call. |

### Permissions (API key scopes)

An unscoped key can use every tool. A scoped key is checked on every call against the tool's **resource** and **action**:

| Resource | Tools | Actions |
| --- | --- | --- |
| `sheets` | `sheets_*` tools | `read` for read-only tools, `write` for tools that create or change, `delete` for destructive tools. `full` = all three. |
| `drive` | `drive_*`, `docs_*`, `pdf_*`, `export_to_pdf`, `spreadsheet_read_versions` |
| `bigquery` | `bigquery_query` (read only) |
| `gmail` | `gmail_*` tools (read only) |

A key with a scope on *everything* (for example "View everything") covers these resources too. Google file ids are not Sheetgo items, so these scopes cannot be limited to specific files. A tool that touches two resources needs both: `sheets_append_gmail_messages` reads Gmail and writes a spreadsheet, so it needs `sheets.write` *and* `gmail.read`. `ping` and `whoami` are never restricted. The badges on each tool below show every scope it needs.

### Recipes

From your own code: [Call MCP tools from Python](/examples.md#recipe-mcp-client) (connect, page through a tab with `cursor`, retries and errors) and [Give an AI agent the Sheetgo tools](/examples.md#recipe-mcp-agent). Inside an AI client, these sequences work well:

| Goal | Tool calls, in order |
| --- | --- |
| Read a spreadsheet the user named | `drive_search_files` (name or pasted URL) → `sheets_list_tabs` → `sheets_get_schema` → `sheets_get_values` (follow `cursor`) |
| Add rows to a log sheet | `sheets_get_schema` (check the column order) → `sheets_append_rows` once (it is not idempotent) |
| Replace a block of cells | `sheets_set_values` with `a1_range` = the top-left cell (safe to repeat) |
| Clear data but keep the header | `sheets_describe_structure` (grid `rows` and `columns`) → `sheets_clear_range` with `a1_range` from `A2` to the last column and row (for example `"A2:K1001"`) and `dry_run: true` → confirm with the user → same call without `dry_run` |
| Fill a Docs template and send a PDF | `docs_list_tags` → `docs_create_from_template` → `docs_replace_text` → `export_to_pdf` → `drive_share_file` |
| Read an invoice PDF | `pdf_get_metadata` (check `text_layer`) → `pdf_extract_tables` / `pdf_extract_text`, or `pdf_ocr` when the text layer is `none` |
| See what changed in a sheet | `spreadsheet_read_versions` (list) → again with two `revision_ids`, a `tab` and `diff: true` |
| Query BigQuery | `bigquery_query` with `dry_run: true` (check bytes) → the same query without `dry_run` |

### Tool reference

Every tool the server offers. Types and defaults come from each tool's input schema; the notes explain what each parameter means.

| Tool | Group | Access | Key scope | Summary |
| --- | --- | --- | --- | --- |
| [`ping`](#tool-ping) | Server | read-only | — | Liveness check for the Sheetgo MCP server. |
| [`whoami`](#tool-whoami) | Server | read-only | — | Return the authenticated Sheetgo user's identity (email and id). |
| [`sheets_describe_structure`](#tool-sheets_describe_structure) | Google Sheets | read-only | sheets.read | Summarize a spreadsheet's structure (tabs and named ranges) from metadata only, without reading cell values. |
| [`sheets_get_schema`](#tool-sheets_get_schema) | Google Sheets | read-only | sheets.read | Describe a tab's columns with naive type inference, plus the spreadsheet's named ranges. |
| [`sheets_get_values`](#tool-sheets_get_values) | Google Sheets | read-only | sheets.read | Read a bounded, paginated window of a tab's FORMATTED values (cursor-based, capped at 1000 rows per call). |
| [`sheets_list_tabs`](#tool-sheets_list_tabs) | Google Sheets | read-only | sheets.read | List a spreadsheet's tabs (id, name, index). |
| [`spreadsheet_read_versions`](#tool-spreadsheet_read_versions) | Google Sheets | read-only | drive.read | Read a file's Drive revision history: when each stored version was written and by whom, and optionally the cell values a given version held (pass revision\_ids, plus tab and a1\_range), with diff=true for a row-count comparison between them. |
| [`sheets_add_tab`](#tool-sheets_add_tab) | Google Sheets | writes | sheets.write | Add a new tab to a spreadsheet. |
| [`sheets_append_gmail_messages`](#tool-sheets_append_gmail_messages) | Google Sheets | writes | sheets.write + gmail.read | Append the user's Gmail messages to a spreadsheet tab, one row each, under the header Date, From, To, Cc, Bcc, Subject, Body, Body (HTML), Attachments, Message ID, Labels (Body is plain text; Attachments lists file names) — the same layout a Sheetgo email automation writes. |
| [`sheets_append_rows`](#tool-sheets_append_rows) | Google Sheets | writes | sheets.write | Append rows after a tab's last data row. |
| [`sheets_create_spreadsheet`](#tool-sheets_create_spreadsheet) | Google Sheets | writes | sheets.write | Create a new spreadsheet, optionally placing it in a folder. |
| [`sheets_set_values`](#tool-sheets_set_values) | Google Sheets | writes | sheets.write | Overwrite a fixed A1 range with the given values (idempotent). |
| [`sheets_clear_range`](#tool-sheets_clear_range) | Google Sheets | DESTRUCTIVE | sheets.delete | DESTRUCTIVE: clear cell values (formatting is kept). |
| [`sheets_delete_tab`](#tool-sheets_delete_tab) | Google Sheets | DESTRUCTIVE | sheets.delete | DESTRUCTIVE: delete a tab from a spreadsheet (irreversible). |
| [`drive_get_file_metadata`](#tool-drive_get_file_metadata) | Google Drive | read-only | drive.read | Fetch a single Drive file's metadata. |
| [`drive_list_files`](#tool-drive_list_files) | Google Drive | read-only | drive.read | List one page of a Drive folder's files; accepts a folder id or the sentinels 'root', 'shared-with-me', 'team-drives'. |
| [`drive_search_files`](#tool-drive_search_files) | Google Drive | read-only | drive.read | Search Drive by file NAME only (not file contents), or resolve a pasted Drive URL to that item directly. |
| [`drive_copy_file`](#tool-drive_copy_file) | Google Drive | writes | drive.write | Copy a Drive file to a new file, optionally into a folder. |
| [`drive_create_folder`](#tool-drive_create_folder) | Google Drive | writes | drive.write | Create a new Drive folder, optionally inside a parent folder. |
| [`drive_move_file`](#tool-drive_move_file) | Google Drive | writes | drive.write | Move a Drive file into a folder — a TRUE move that removes the file's current parents (idempotent). |
| [`drive_rename_file`](#tool-drive_rename_file) | Google Drive | writes | drive.write | Rename a Drive file to a new name (idempotent). |
| [`drive_share_file`](#tool-drive_share_file) | Google Drive | writes | drive.write | Share a Drive file with recipients as writer/reader/commenter. |
| [`drive_delete_file`](#tool-drive_delete_file) | Google Drive | DESTRUCTIVE | drive.delete | DESTRUCTIVE: delete a Drive file. |
| [`docs_get_content`](#tool-docs_get_content) | Google Docs | read-only | drive.read | Read a Google Doc's text with its formatting runs (bold, italic, links, fonts) preserved, across every tab, header, footer and footnote. |
| [`docs_list_tags`](#tool-docs_list_tags) | Google Docs | read-only | drive.read | List a Google Doc's {{smart tags}} and flag broken ones: a tag split across formatting runs (its value loses the mixed formatting; a split image tag is missed by the merge) or with spaces inside the braces (never replaced). |
| [`docs_append_content`](#tool-docs_append_content) | Google Docs | writes | drive.write | Append paragraphs (optionally headings or bullet/numbered lists) to the end of a Google Doc's body. |
| [`docs_create_from_template`](#tool-docs_create_from_template) | Google Docs | writes | drive.write | Copy a Google Docs template to a new Doc with the given name, optionally into a folder (default: your own My Drive root). |
| [`docs_insert_image`](#tool-docs_insert_image) | Google Docs | writes | drive.write | Insert an image from a public URL into a Google Doc, replacing every occurrence of a placeholder text (the placeholder is removed) or at a body index. |
| [`docs_replace_text`](#tool-docs_replace_text) | Google Docs | writes | drive.write | Overwrite {{smart tags}} with values in one batch across the whole Doc (every tab, header, footer, footnote); case-insensitive by default, like Sheetgo's merge. |
| [`pdf_extract_tables`](#tool-pdf_extract_tables) | PDF | read-only | drive.read | Detect tables in a PDF in Drive and return each as rows of cell text, with page and position. |
| [`pdf_extract_text`](#tool-pdf_extract_text) | PDF | read-only | drive.read | Extract the text layer of a PDF in Drive, page by page (no OCR). |
| [`pdf_get_metadata`](#tool-pdf_get_metadata) | PDF | read-only | drive.read | Describe a PDF in Drive: page count, page sizes, encryption / password status, document properties, and whether it has a text layer (if not, use pdf\_ocr). |
| [`pdf_ocr`](#tool-pdf_ocr) | PDF | read-only | drive.read | Read text from an image-only (scanned) PDF or an image file (PNG/JPEG/TIFF/GIF/BMP/WebP) in Drive with Tesseract OCR, a few pages per call. |
| [`export_to_pdf`](#tool-export_to_pdf) | PDF | writes | drive.write | Export a Google Doc or Google Sheet to PDF and create it as a new Drive file (default: your own My Drive root). |
| [`pdf_merge`](#tool-pdf_merge) | PDF | writes | drive.write | Merge 2-20 PDFs from Drive, in order, and create one new PDF file whose page count is checked to equal the inputs' total. |
| [`bigquery_query`](#tool-bigquery_query) | BigQuery | read-only | bigquery.read | Run a read-only BigQuery SQL query (SELECT only) and return a bounded result window. |
| [`gmail_batch_get_messages`](#tool-gmail_batch_get_messages) | Gmail | read-only | gmail.read | Read up to 50 Gmail messages in full in one call (same shape as gmail\_get\_message), in the order of message\_ids. |
| [`gmail_get_attachment`](#tool-gmail_get_attachment) | Gmail | read-only | gmail.read | Download one attachment of a Gmail message as base64, with its filename, content type and size. |
| [`gmail_get_message`](#tool-gmail_get_message) | Gmail | read-only | gmail.read | Read one Gmail message in full: date, from, to, cc, bcc, subject, Message-ID, labels (with names), the body (HTML when the message has it, plus the plain-text version) and its attachments' metadata. |
| [`gmail_list_messages`](#tool-gmail_list_messages) | Gmail | read-only | gmail.read | List one page of the user's Gmail messages in a label (default INBOX; a label name like 'Invoices' or an id; null for all mail), newest first, up to `limit` (1-100, default 25). |
| [`gmail_search_messages`](#tool-gmail_search_messages) | Gmail | read-only | gmail.read | Search the user's Gmail with Gmail search syntax (e.g. |

#### Server · 2 tools · Check the connection and the identity behind the key.

#### `ping`Ping

Read-onlyNot idempotent

Liveness check for the Sheetgo MCP server. Returns {"ok": true}.

No parameters.

**Returns:** {ok: true}

#### `whoami`Who am I

Read-onlyNot idempotent

Return the authenticated Sheetgo user's identity (email and id).

No parameters.

**Returns:** {email, user\_id} of the Sheetgo user the API key belongs to.

#### Google Sheets · 12 tools · Read, write and inspect spreadsheets.

#### `sheets_describe_structure`Describe spreadsheet structure

Read-onlyIdempotentscope sheets.read

Summarize a spreadsheet's structure (tabs and named ranges) from metadata only, without reading cell values. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab` | string | optional, default `null` | Omit to describe ALL tabs; a name or sheet id selects one. |

**Returns:** {file\_id, tabs: [{id, name, index, rows, columns, hidden, tab\_type}], named\_ranges}. Reads metadata only, no cell values; rows/columns are the grid size, not the used range.

#### `sheets_get_schema`Get spreadsheet schema

Read-onlyIdempotentscope sheets.read

Describe a tab's columns with naive type inference, plus the spreadsheet's named ranges. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab` | string | optional, default `null` | Tab name (case-insensitive) or numeric sheet id. Omit for the first tab. |

**Returns:** {file\_id, tab, headers: [{name, inferred\_type: int|float|date|str}], named\_ranges}. Types are inferred from the first 20 data rows.

#### `sheets_get_values`Read spreadsheet values

Read-onlyIdempotentscope sheets.read

Read a bounded, paginated window of a tab's FORMATTED values (cursor-based, capped at 1000 rows per call). Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `cursor` | string | optional, default `null` | Opaque row offset from the previous call's cursor. Omit to start at the top. null in the reply means the end was reached. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `max_rows` | integer | optional, default `200` | Rows per call, clamped to 1-1000. |
| `tab` | string | optional, default `null` | Tab name (case-insensitive) or numeric sheet id. Omit for the first tab. |

**Returns:** {file\_id, tab, headers (row 1), rows, total\_rows, cursor}. Values are FORMATTED as shown in the sheet (dates as text), never raw serial numbers.

#### `sheets_list_tabs`List spreadsheet tabs

Read-onlyIdempotentscope sheets.read

List a spreadsheet's tabs (id, name, index). Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |

**Returns:** {file\_id, tabs: [{id, name, index}]}

#### `spreadsheet_read_versions`Read spreadsheet version history

Read-onlyIdempotentscope drive.read

Read a file's Drive revision history: when each stored version was written and by whom, and optionally the cell values a given version held (pass revision\_ids, plus tab and a1\_range), with diff=true for a row-count comparison between them. Read-only: it lists and reads versions and can never restore, delete or alter one. Revision metadata is reliable; revision CONTENT export fails on large or formula-heavy spreadsheets, and those ids come back under 'unavailable' with the reason instead of raising. Google coalesces and prunes unnamed revisions, so the list samples the writes rather than recording every one.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `a1_range` | string | optional, default `null` | Range to read, e.g. 'A1:D50'. Omit for the whole tab. |
| `diff` | boolean | optional, default `false` | true = add a row-count comparison between consecutive fetched revisions (never cell values). |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `revision_ids` | array | optional, default `null` | Up to 10 revision ids whose values to fetch. Omit to list revision metadata only (fast). |
| `tab` | string | optional, default `null` | Tab to read from each revision. May be omitted only when the file has one tab. |

**Returns:** {file\_id, revisions, next\_page\_token, truncated\_oldest, values, unavailable, diff}. Read-only. Large or formula-heavy files often cannot be exported by Google: those ids come back under 'unavailable' with the reason.

#### `sheets_add_tab`Add spreadsheet tab

WritesNot idempotentscope sheets.write

Add a new tab to a spreadsheet. NOT idempotent; the returned tab metadata (id, name, index) is read from the created tab and is authoritative. A name that collides with an existing tab is rejected by the Sheets API as an error.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `name` | string | required | Name of the new tab. A name already in use is rejected. |

**Returns:** {tab: {id, name, index}}

#### `sheets_append_gmail_messages`Export Gmail messages to a sheet

WritesNot idempotentscope sheets.writescope gmail.read

Append the user's Gmail messages to a spreadsheet tab, one row each, under the header Date, From, To, Cc, Bcc, Subject, Body, Body (HTML), Attachments, Message ID, Labels (Body is plain text; Attachments lists file names) — the same layout a Sheetgo email automation writes. Messages come from `label` (default INBOX; null for all mail) and/or a Gmail search `query`, newest first, up to `limit` (1-50). The header row is written only into an EMPTY tab; a tab that already has other columns is refused. NOT idempotent: each call appends new rows, so the same messages exported twice appear twice. Reads Gmail (never modifies it) and writes the spreadsheet; needs both the gmail.read and sheets.write grants.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `label` | string | optional, default `"INBOX"` | Gmail label: a system label (INBOX, SENT, STARRED, UNREAD…) or one of your labels by name (case-insensitive) or id. null = all mail. Default INBOX. Combined with query when both are given. |
| `limit` | integer | optional, default `25` | Messages exported, 1-50 (default 25), newest first. Each one is read in full. |
| `query` | string | optional, default `null` | Gmail search syntax to pick the messages, e.g. 'from:billing@acme.com newer\_than:30d'. Omit to take the newest messages of the label. |
| `tab` | string | optional, default `null` | Tab name (case-insensitive) or numeric sheet id. Omit for the first tab. It must be empty, or hold exactly the columns an earlier export wrote. |

**Returns:** {file\_id, tab, rows\_appended, header\_written, message\_ids}. NOT idempotent: each call appends the messages again. Needs both the sheets.write and gmail.read scopes.

#### `sheets_append_rows`Append spreadsheet rows

WritesNot idempotentscope sheets.write

Append rows after a tab's last data row. NOT idempotent: each call inserts brand-new rows, so repeating duplicates the data.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab` | string | required | Tab name (case-insensitive) or numeric sheet id. |
| `values` | array | required | Rectangular list of rows, each a list of cells (string, number, boolean or null). |

**Returns:** {file\_id, tab, rows\_appended}. NOT idempotent: repeating the call adds the rows again.

#### `sheets_create_spreadsheet`Create spreadsheet

WritesNot idempotentscope sheets.write

Create a new spreadsheet, optionally placing it in a folder. NOT idempotent; each call creates another file.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `folder_id` | string | optional, default `null` | Id of a Drive folder. Omit to use your own My Drive root. |
| `name` | string | required | Name of the new spreadsheet. |

**Returns:** {id, name, folder\_id?}

#### `sheets_set_values`Overwrite spreadsheet range

WritesIdempotentscope sheets.write

Overwrite a fixed A1 range with the given values (idempotent). The range's top-left cell anchors the write and the values matrix decides how far it reaches, so 'A10' and 'A10:C12' both put a 3x3 matrix in the same cells; values must fit inside a range you give bounds for. Returns the range actually written. The grid auto-resizes.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `a1_range` | string | required | Plain A1 range WITHOUT the tab prefix, e.g. 'A10' or 'A10:C12'. The top-left cell anchors the write. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab` | string | required | Tab name (case-insensitive) or numeric sheet id. |
| `values` | array | required | Rectangular list of rows, each a list of cells (string, number, boolean or null). Up to 20000 rows. |

**Returns:** {file\_id, tab, range (the rectangle actually written), rows\_written, cols\_written}. Idempotent: the same call lands the same cells.

#### `sheets_clear_range`Clear spreadsheet range

DestructiveNot idempotentdry\_runscope sheets.delete

DESTRUCTIVE: clear cell values (formatting is kept). a1\_range clears exactly that range, e.g. 'A2:G100' keeps a row-1 header (no 'Sheet1!' prefix; pick the tab with `tab`). Legacy rows/columns clear an A1-anchored rectangle that INCLUDES row 1. Pass none of them to clear the ENTIRE tab. Supports dry\_run to preview without mutating.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `a1_range` | string | optional, default `null` | Plain A1 range cleared exactly, e.g. 'A2:G100' keeps the row-1 header. No 'Sheet1!' prefix; pick the tab with tab. |
| `columns` | integer | optional, default `null` | Legacy: with rows, clears A1:<columns><rows>, which INCLUDES row 1. |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `rows` | integer | optional, default `null` | Legacy: with columns, clears A1:<columns><rows>, which INCLUDES row 1. |
| `tab` | string | optional, default `null` | Tab name (case-insensitive) or numeric sheet id. Omit for the first tab. |

**Returns:** {cleared, tab, target}. With none of a1\_range/rows/columns the ENTIRE tab is cleared. Formatting is kept.

#### `sheets_delete_tab`Delete spreadsheet tab

DestructiveNot idempotentdry\_runscope sheets.delete

DESTRUCTIVE: delete a tab from a spreadsheet (irreversible). Supports dry\_run to preview without mutating.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab` | string | optional, default `null` | Tab name (case-insensitive) or numeric sheet id. Omit for the first tab. |

**Returns:** {deleted, tab: {id, name}}. Irreversible.

#### Google Drive · 9 tools · Find, organise, copy, share and delete files and folders.

#### `drive_get_file_metadata`Get Drive file metadata

Read-onlyIdempotentscope drive.read

Fetch a single Drive file's metadata. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |

**Returns:** {file: {id, name, mimeType, parents, owners, trashed, size, modifiedTime}}

#### `drive_list_files`List Drive folder files

Read-onlyIdempotentscope drive.read

List one page of a Drive folder's files; accepts a folder id or the sentinels 'root', 'shared-with-me', 'team-drives'. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `folder_id` | string | optional, default `"root"` | Folder id, or one of 'root', 'shared-with-me', 'team-drives'. |
| `page_token` | string | optional, default `null` | Opaque token from the previous call's next\_page\_token. Omit for the first page. |

**Returns:** {folder\_id, files, next\_page\_token}

#### `drive_search_files`Search Drive files

Read-onlyIdempotentscope drive.read

Search Drive by file NAME only (not file contents), or resolve a pasted Drive URL to that item directly. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `page_token` | string | optional, default `null` | Opaque token from the previous call's next\_page\_token. Omit for the first page. |
| `query` | string | required | Text matched against file NAMES (not contents), or a full Drive file/folder URL to resolve that item. |

**Returns:** {query, files, next\_page\_token}

#### `drive_copy_file`Copy Drive file

WritesNot idempotentscope drive.write

Copy a Drive file to a new file, optionally into a folder. NOT idempotent; each call creates another copy.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `folder_id` | string | optional, default `null` | Id of a Drive folder. Omit to use your own My Drive root. |
| `name` | string | required | Name of the copy. |

**Returns:** {file: {...the copy's metadata}}

#### `drive_create_folder`Create Drive folder

WritesNot idempotentscope drive.write

Create a new Drive folder, optionally inside a parent folder. NOT idempotent; each call creates another folder.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `name` | string | required | Name of the new folder. |
| `parent_id` | string | optional, default `null` | Folder to create it in. Omit for your My Drive root. |

**Returns:** {folder: {id}}

#### `drive_move_file`Move Drive file

WritesIdempotentscope drive.write

Move a Drive file into a folder — a TRUE move that removes the file's current parents (idempotent).

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `folder_id` | string | required | Destination folder id. The file is removed from its current folders (a true move). |

**Returns:** {file: {id, name, parents}}

#### `drive_rename_file`Rename Drive file

WritesIdempotentscope drive.write

Rename a Drive file to a new name (idempotent).

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `new_name` | string | required | The new file name. |

**Returns:** {file: {id, name, mimeType}}

#### `drive_share_file`Share Drive file

WritesNot idempotentscope drive.write

Share a Drive file with recipients as writer/reader/commenter. BEST-EFFORT: per-recipient failures are not individually reported (the result carries confirmed=false).

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `emails` | array | required | List of email addresses to share with. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `role` | string | optional, default `"writer"` | One of writer, reader, commenter. |

**Returns:** {file\_id, requested\_emails, confirmed: false, role}. Best effort: Google does not report per-recipient failures, so confirmed is always false.

#### `drive_delete_file`Delete Drive file

DestructiveNot idempotentdry\_runscope drive.delete

DESTRUCTIVE: delete a Drive file. Trashes by default (recoverable); permanent=true hard-deletes irreversibly. Supports dry\_run to preview without mutating.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `permanent` | boolean | optional, default `false` | false (default) moves the file to the trash (recoverable). true deletes it for good. |

**Returns:** {deleted, permanent, file\_id}

#### Google Docs · 6 tools · Read documents and fill templates with smart tags.

#### `docs_get_content`Read Google Doc content

Read-onlyIdempotentscope drive.read

Read a Google Doc's text with its formatting runs (bold, italic, links, fonts) preserved, across every tab, header, footer and footnote. Bounded by characters per call with a cursor. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `cursor` | string | optional, default `null` | Token from the previous call. Omit to start at the beginning. null in the reply means the end. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `max_chars` | integer | optional, default `20000` | Characters of text per call, clamped to 1-100000. |

**Returns:** {file\_id, title, revision\_id, tabs, paragraphs: [{tab\_id, segment, style, text, runs}], total\_paragraphs, cursor, truncated}

#### `docs_list_tags`List Google Doc smart tags

Read-onlyIdempotentscope drive.read

List a Google Doc's {{smart tags}} and flag broken ones: a tag split across formatting runs (its value loses the mixed formatting; a split image tag is missed by the merge) or with spaces inside the braces (never replaced). Given column headers, also reports tags matching no header and case-only mismatches (Sheetgo's merge matches case-insensitively). Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `headers` | array of string | optional, default `null` | Optional list of spreadsheet column headers; each tag is then matched against them (exact / case\_only / none). |

**Returns:** {tags: [{tag, kind, status: valid|broken, problems, occurrences}], broken, unmatched, case\_mismatch, summary}. A tag is broken when it spans two formatting runs or has spaces inside the braces.

#### `docs_append_content`Append to Google Doc

WritesNot idempotentdry\_runscope drive.write

Append paragraphs (optionally headings or bullet/numbered lists) to the end of a Google Doc's body. NOT idempotent; each call appends again. Returns the new end of the document, read back after the write. Supports dry\_run to preview without writing.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `content` | string or array of string or object | required | A string (one paragraph) or a list of strings / {text, style?, list?} objects. style: NORMAL\_TEXT, TITLE, SUBTITLE, HEADING\_1..HEADING\_6. list: 'bullet' or 'numbered'. At most 500 blocks / 100000 characters. |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `tab_id` | string | optional, default `null` | Google Docs tab id (from docs\_get\_content). Omit for the first tab. |

**Returns:** {file\_id, tab\_id, blocks, start\_index, end\_index, verified, revision\_id}

#### `docs_create_from_template`Create Doc from template

WritesNot idempotentscope drive.write

Copy a Google Docs template to a new Doc with the given name, optionally into a folder (default: your own My Drive root). NOT idempotent; each call creates another file. Returns the new file's id and metadata.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `folder_id` | string | optional, default `null` | Id of a Drive folder. Omit to use your own My Drive root. |
| `name` | string | required | Name of the new document. |
| `template_id` | string | required | Drive id of the Google Docs template to copy (must be a Google Doc). |

**Returns:** {file: {...the copy's metadata}, template: {id, name}}

#### `docs_insert_image`Insert image into Google Doc

WritesNot idempotentscope drive.write

Insert an image from a public URL into a Google Doc, replacing every occurrence of a placeholder text (the placeholder is removed) or at a body index. NOT idempotent. Returns the inserted object ids, verified against the document afterwards.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `height_pt` | number | optional, default `null` | Optional height in points. |
| `image_url` | string | required | Public http(s) URL of a PNG/JPEG/GIF (under 50 MB / 25 megapixels). Private Drive links are rejected by Google. |
| `index` | integer | optional, default `null` | Body index to insert at. Give exactly one of placeholder or index. |
| `placeholder` | string | optional, default `null` | Text to replace with the image at every occurrence, e.g. '{{logo}}'. Give exactly one of placeholder or index. |
| `tab_id` | string | optional, default `null` | Google Docs tab id (from docs\_get\_content). Omit for the first tab. |
| `width_pt` | number | optional, default `null` | Optional width in points. |

**Returns:** {inserted: [{object\_id, tab\_id, index, verified}], skipped, revision\_id}

#### `docs_replace_text`Replace Google Doc smart tags

WritesNot idempotentdry\_runscope drive.write

Overwrite {{smart tags}} with values in one batch across the whole Doc (every tab, header, footer, footnote); case-insensitive by default, like Sheetgo's merge. Reports the replacement count Google measured per tag and the tags still present afterwards. Supports dry\_run to preview the counts without writing.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `match_case` | boolean | optional, default `false` | false (default) matches tags case-insensitively, like Sheetgo's merge. |
| `replacements` | object | required | Object mapping a tag to its value: {"Name": "Ana"} replaces {{Name}}; a key may also be the full tag "{{Name}}". Values: string, number, boolean or null (null = empty). |

**Returns:** {replaced: [{tag, occurrences\_changed}], total\_occurrences\_changed, not\_found, remaining\_tags, revision\_id}. If post\_write\_read\_error is set, the write still happened: do not retry.

#### PDF · 6 tools · Read PDFs and images stored in Drive; export and merge PDFs.

#### `pdf_extract_tables`Extract PDF tables

Read-onlyIdempotentscope drive.read

Detect tables in a PDF in Drive and return each as rows of cell text, with page and position. Text-layer PDFs only. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `max_pages` | integer | optional, default `20` | Pages per call, clamped to 1-100. |
| `start_page` | integer | optional, default `1` | First page to scan (1-based). |

**Returns:** {tables: [{page, index, row\_count, column\_count, rows}], pages\_scanned, failed\_pages, next\_start\_page}. Text layer only: tables inside scanned images are not found.

#### `pdf_extract_text`Extract PDF text

Read-onlyIdempotentscope drive.read

Extract the text layer of a PDF in Drive, page by page (no OCR). Pages without a text layer are listed so they can be sent to pdf\_ocr; an unreadable or password-protected file is an error, never empty text. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `max_pages` | integer | optional, default `50` | Pages per call, clamped to 1-200. |
| `start_page` | integer | optional, default `1` | First page to read (1-based). |

**Returns:** {file\_id, name, page\_count, pages: [{page, text, chars}], pages\_without\_text, next\_start\_page, truncated, hint}. Pages with no text layer are listed for pdf\_ocr.

#### `pdf_get_metadata`Get PDF metadata

Read-onlyIdempotentscope drive.read

Describe a PDF in Drive: page count, page sizes, encryption / password status, document properties, and whether it has a text layer (if not, use pdf\_ocr). Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |

**Returns:** {file, page\_count, encrypted, needs\_password, pdf\_metadata, page\_sizes, text\_layer: full|partial|none}. text\_layer 'none' means use pdf\_ocr.

#### `pdf_ocr`OCR a scanned PDF or image

Read-onlyIdempotentscope drive.read

Read text from an image-only (scanned) PDF or an image file (PNG/JPEG/TIFF/GIF/BMP/WebP) in Drive with Tesseract OCR, a few pages per call. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Google Drive id of the file: the long id in its URL (docs.google.com/spreadsheets/d/<id>/...). Use drive\_search\_files to find it, or to resolve a pasted Drive URL. |
| `language` | string | optional, default `"eng"` | Tesseract language code, e.g. 'eng' or 'eng+spa'. An uninstalled language is refused with the installed list. |
| `max_pages` | integer | optional, default `5` | Pages per call, clamped to 1-10. |
| `start_page` | integer | optional, default `1` | First page to read (1-based). |

**Returns:** {file\_id, name, page\_count, language, pages: [{page, text, chars}], pages\_without\_text, next\_start\_page, truncated}. Works on scanned PDFs and PNG/JPEG/TIFF/GIF/BMP/WebP images.

#### `export_to_pdf`Export to PDF

WritesNot idempotentscope drive.write

Export a Google Doc or Google Sheet to PDF and create it as a new Drive file (default: your own My Drive root). NOT idempotent; each call creates another PDF. Returns the new file's id and its measured page count.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `file_id` | string | required | Drive id of the Google Doc or Google Sheet to export. |
| `folder_id` | string | optional, default `null` | Id of a Drive folder. Omit to use your own My Drive root. |
| `name` | string | optional, default `null` | Name of the PDF. Omit for the source name plus '.pdf'. |

**Returns:** {file: {id, name, webViewLink, ...}, source, page\_count, bytes}. Google caps exports at about 10 MB.

#### `pdf_merge`Merge PDFs

WritesNot idempotentdry\_runscope drive.write

Merge 2-20 PDFs from Drive, in order, and create one new PDF file whose page count is checked to equal the inputs' total. NOT idempotent; each call creates another file. Supports dry\_run to preview the page counts without creating anything.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `dry_run` | boolean | optional, default `false` | true = preview what would happen and return {dry\_run, would}; nothing is changed. |
| `file_ids` | array of string | required | 2 to 20 Drive PDF ids, in the order to merge. Each up to 25 MB, 100 MB in total. |
| `folder_id` | string | optional, default `null` | Id of a Drive folder. Omit to use your own My Drive root. |
| `name` | string | required | Name of the merged PDF. |

**Returns:** {file, page\_count, inputs, input\_page\_total}. If the merged page count does not match the inputs, nothing is uploaded.

#### BigQuery · 1 tool · Run read-only SQL queries as your Google account.

#### `bigquery_query`Query BigQuery

Read-onlyIdempotentdry\_runscope bigquery.read

Run a read-only BigQuery SQL query (SELECT only) and return a bounded result window. Non-SELECT statements are refused; the result is capped by rows (default 1000) and by bytes scanned. The job is billed to the given project and runs as your Google account. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `dry_run` | boolean | optional, default `false` | true = validate and price the query without running it: {dry\_run, statement\_type, bytes\_processed}. |
| `max_rows` | integer | optional, default `1000` | Rows returned, clamped to 1-10000. A larger result comes back with truncated=true. |
| `project` | string | required | GCP project the query job is billed to. Required, never inferred. |
| `query` | string | required | Standard SQL. Must be SELECT (or WITH ... SELECT); anything else is refused before running. |

**Returns:** {statement\_type, columns, fields, rows, row\_count, truncated, bytes\_processed, cache\_hit}. Runs as your Google account on a read-only token; the bytes a query may scan are capped.

#### Gmail · 5 tools · Read and search your Gmail messages and attachments (read-only).

#### `gmail_batch_get_messages`Get several Gmail messages

Read-onlyIdempotentscope gmail.read

Read up to 50 Gmail messages in full in one call (same shape as gmail\_get\_message), in the order of message\_ids. An id that does not exist comes back as {id, error: 'message\_not\_found'} in its place. With include\_attachments=true (at most 10 ids) attachment bytes are included as base64, capped at max\_attachment\_bytes across the whole batch. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `include_attachments` | boolean | optional, default `false` | true = include attachment bytes as base64. Default false: only their metadata. |
| `max_attachment_bytes` | integer | optional, default `10485760` | With include\_attachments, the call is refused when the attachments of all the messages total more than this, default 10 MB. |
| `max_body_chars` | integer | optional, default `100000` | Characters of body returned, default 100000; a longer body is cut and marked truncated. |
| `message_ids` | array of string | required | Message ids to read, at most 50 (at most 10 with include\_attachments). Returned in this order. |

**Returns:** {messages}: one full message per id (same shape as gmail\_get\_message); an id that does not exist comes back as {id, error: "message\_not\_found"}.

#### `gmail_get_attachment`Get Gmail attachment

Read-onlyIdempotentscope gmail.read

Download one attachment of a Gmail message as base64, with its filename, content type and size. attachment\_id is an id from the message's `attachments` list. Files over max\_bytes (default 10 MB) are refused with their size instead of being returned. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `attachment_id` | string | required | Attachment id from the message's attachments list (attachments[].id). |
| `max_bytes` | integer | optional, default `10485760` | Largest file returned, default 10 MB; a bigger one is refused with its size. |
| `message_id` | string | required | Id of the message that has the attachment. |

**Returns:** {message\_id, id, filename, content\_type, size\_bytes, content\_base64}.

#### `gmail_get_message`Get Gmail message

Read-onlyIdempotentscope gmail.read

Read one Gmail message in full: date, from, to, cc, bcc, subject, Message-ID, labels (with names), the body (HTML when the message has it, plus the plain-text version) and its attachments' metadata. The body is cut at max\_body\_chars (default 100000; `truncated` says so). With include\_attachments=true each attachment also carries its bytes as base64, refused when they total more than max\_attachment\_bytes (default 10 MB); otherwise fetch files one at a time with gmail\_get\_attachment. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `include_attachments` | boolean | optional, default `false` | true = include every attachment's bytes as base64. Default false: only their metadata. |
| `max_attachment_bytes` | integer | optional, default `10485760` | With include\_attachments, the call is refused when the attachments total more than this, default 10 MB (10485760). |
| `max_body_chars` | integer | optional, default `100000` | Characters of body returned, default 100000; a longer body is cut and marked truncated. |
| `message_id` | string | required | Message id from gmail\_list\_messages or gmail\_search\_messages (the id field). |

**Returns:** The summary fields plus the body (HTML when the message has it, and plain text), label names and, with include\_attachments, each attachment's bytes as base64.

#### `gmail_list_messages`List Gmail messages

Read-onlyIdempotentscope gmail.read

List one page of the user's Gmail messages in a label (default INBOX; a label name like 'Invoices' or an id; null for all mail), newest first, up to `limit` (1-100, default 25). Each summary has subject, from, to, cc, dates, snippet, labels, read state and attachment metadata (ids for gmail\_get\_attachment), but no body. Pass next\_page\_token back as page\_token for the next page. Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `label` | string | optional, default `"INBOX"` | Gmail label: a system label (INBOX, SENT, STARRED, UNREAD…) or one of your labels by name (case-insensitive) or id. null = all mail. Default INBOX. |
| `limit` | integer | optional, default `25` | Messages per page, 1-100 (default 25), newest first. |
| `page_token` | string | optional, default `null` | Opaque token from the previous call's next\_page\_token. Omit for the first page. |

**Returns:** {messages, next\_page\_token, result\_size\_estimate}. Each message is a summary: id, thread\_id, subject, from, from\_name, to, cc, bcc, received\_at, sent\_at, preview, has\_attachments, is\_read, labels, web\_link and attachments (id, filename, content\_type, size\_bytes, is\_inline). No body: use gmail\_get\_message.

#### `gmail_search_messages`Search Gmail messages

Read-onlyIdempotentscope gmail.read

Search the user's Gmail with Gmail search syntax (e.g. 'from:ana@acme.com has:attachment newer\_than:7d', 'subject:invoice', 'label:invoices is:unread') and return one page of message summaries, same shape as gmail\_list\_messages, up to `limit` (1-100, default 25). Read-only.

| Parameter | Type | Required | Meaning |
| --- | --- | --- | --- |
| `limit` | integer | optional, default `25` | Messages per page, 1-100 (default 25), newest first. |
| `page_token` | string | optional, default `null` | Opaque token from the previous call's next\_page\_token. Omit for the first page. |
| `query` | string | required | Gmail search syntax, as in the Gmail search box: 'from:ana@acme.com has:attachment newer\_than:7d', 'subject:invoice', 'label:invoices is:unread'. |

**Returns:** Same shape as gmail\_list\_messages:. Each message is a summary: id, thread\_id, subject, from, from\_name, to, cc, bcc, received\_at, sent\_at, preview, has\_attachments, is\_read, labels, web\_link and attachments (id, filename, content\_type, size\_bytes, is\_inline). No body: use gmail\_get\_message.
