Local MCP server that lets AI assistants read and write Google Sheets
Strong tool definitions with clear naming (verb_noun pattern), comprehensive parameter schemas using Zod, and detailed descriptions. All 9 tools have explicit input schemas with type definitions and descriptions. Error handling is present but generic. Output schemas are documented in descriptions but not formally declared. Tool composition is excellent, each tool has a single responsibility and outputs include IDs for chaining (e.g., find_rows returns row numbers usable by update_row). Main gaps: no tool annotations (readOnlyHint/destructiveHint), output schemas not formally specified, and error messages could be more actionable.
Appends one or more rows to the end of a table. The first row must contain headers matching the table's column names.
Creates a new spreadsheet with the given title and optional tabs.
Deletes one or more rows from a sheet by their row numbers.
Finds rows matching one or more criteria and returns only those, along with their absolute row numbers. Prefer this over read_range on long tables: it avoids pulling hundreds of irrelevant rows into the conversation. The row numbers it returns can be passed straight to update_row.
Lists the tabs of a spreadsheet with their dimensions and sheetId, plus the file title.
Reads the values of a range in A1 notation. Passing only a tab name (e.g. 'Sheet1') returns all data in that tab.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Destructive tools like delete_rows and write_range lack explicit destructiveHint markers, forcing LLMs to infer risk from descriptions alone.
Output schemas not formally documented. Tools return text responses (via ok() helper) without declaring structured output schema. LLMs cannot parse response structure programmatically.
Error messages are generic ('Error: <description>') without actionable recovery guidance. When a tab is not found or a range is invalid, the LLM receives no hint about next steps (e.g., 'Try list_tabs() first').
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | 2026-07-28+ | v2 |
Changes the value of a single cell. Prefer this over write_range when updating an existing record: it only touches the cell you name.
Updates one or more cells in a row by header name or column letter. Only the named columns are touched; other cells in the row are left alone.
Writes a grid of values to a range. Overwrites all cells in the range. For updating a single cell or a few cells in an existing record, prefer update_cell or update_row: they only touch the cells you name.
No confirmation/dry-run pattern for irreversible operations. delete_rows and write_range execute immediately without a confirmation step, risking accidental data loss.
Parameter descriptions lack explicit constraints. 'limit' in find_rows has min/max in schema (1 - 200) but description does not state the range. 'headerRow' defaults to 1 but description does not explain what happens if omitted.