sheets-mcp docs
Reference

Error Reference

Every error sheets-mcp can return — exact message text from source, cause, and agent recovery steps.

Note

AX note for agents. All errors return as structured JSON inside the MCP content array with isError: true. Parse the error field first. If needsAuth: true is present, surface the dashboard URL immediately — no tool call can succeed until the user re-authorizes.


Error envelope

{
  "content": [{
    "type": "text",
    "text": "{ \"error\": \"<message>\", \"stack\": [\"...\"] }"
  }],
  "isError": true
}

For auth expiry, needsAuth: true is added:

{
  "content": [{
    "type": "text",
    "text": "{ \"error\": \"Google Authentication Expired...\", \"needsAuth\": true }"
  }],
  "isError": true
}

Authentication errors

Google Authentication Expired

Exact message:

Google Authentication Expired. Your OAuth connection has expired or been revoked.
Please visit the dashboard (https://sheets-mcp-xi.vercel.app/) and sign in again
to refresh your connection.

Cause: The Google OAuth refresh token was revoked (user removed app access from Google Account settings), or the googleapis client returned invalid_grant / Invalid Credentials.

Agent fix: Set needsAuth: true flag triggers this path. Tell the user to visit https://sheets-mcp-xi.vercel.app/dashboard, sign out, and re-authorize with Google.


No session found

Cause: withMcpAuth could not resolve a session from the Bearer JWT or x-api-key header.

Agent fix: Re-authenticate. For OIDC clients, restart the MCP client and complete OAuth consent. For API key clients, verify x-api-key header is present and valid.


Google access token not found in DB. User must re-authenticate with Google.

Cause: The better-auth session exists but the account table has no Google OAuth token for this user — the Google OAuth callback was interrupted or the account row was deleted.

Agent fix: User must sign out from the dashboard and re-authorize Google. Same as needsAuth: true recovery flow.


Write safety errors

Formula overwrite blocked

Exact message pattern (from formulaOverwriteBlockedMessage()):

<toolName> blocked because the target range contains formula cells (): <cell list>.
Pass allowFormulaOverwrite: true only if you intend to replace formulas with static values.

Example:

write_range blocked because the target range contains formula cells (3): Sheet1!B2, Sheet1!B3, Sheet1!B4.
Pass allowFormulaOverwrite: true only if you intend to replace formulas with static values.

Cause: Target range contains cells starting with =. Detected by fetching the range with valueRenderOption: FORMULA before writing.

Agent fix: Check _schema.arrayFormulaColumns and _schema.protectedRanges from a prior read_range call. Adjust range to avoid formula cells, or set allowFormulaOverwrite: true if intentional.


ARRAYFORMULA spill blocked

Exact message pattern (from arrayFormulaSpillBlockedMessage()):

ARRAYFORMULA spill blocked. Formula cell(s): <cell list>.
Potential spill would overwrite occupied cells (): <cell list>.
Clear those cells, insert buffer columns, narrow the formula output,
or pass allowFormulaOverwrite: true if this overwrite is intentional.

Cause: The incoming values[][] contains an ARRAYFORMULA that would spill into cells already occupied.

Agent fix: The error message lists exact cells to clear. Either clear them first or set allowFormulaOverwrite: true.


ARRAYFORMULA column guard (schema-level)

Exact message (from runPreWriteSchemaGuards()):

⚠ A target column contains an ARRAYFORMULA. Writing static values will permanently
destroy this formula. Pass allowFormulaOverwrite: true to confirm you intend to
replace the formula with static data.

Cause: The write footprint overlaps a column where row 2 contains an ARRAYFORMULA anchor (detected from the schema cache).

Agent fix: Check _schema.warnings from read_range first. Those warnings list exactly which columns to avoid.


Protected Range — strict lock

Exact message (from runPreWriteSchemaGuards()):

Write blocked — range overlaps a Protected Range (locked). The sheet owner has
locked this range. This cannot be overridden.

Cause: warningOnly: false protected range overlaps the write target.

Agent fix: The user must unlock the range in Google Sheets UI. Not bypassable via API. Narrow the write range to avoid the protected area.


Protected Range — warning only (non-blocking)

When warningOnly: true, the write proceeds but _warnings is added to the response:

{ "updatedCells": 6, "_warnings": ["⚠ Target range has a user-set protection warning. Proceeding as requested."] }

Mid-operation protection conflict (write_range only)

Write failed — a protected range conflict was detected mid-operation.
The sheet's protection settings may have changed.
Call read_range to re-inspect the current schema before retrying.

Cause: A 403 from the Sheets API occurred after the pre-write guard passed — meaning the sheet was modified between the guard check and the write execution.

Agent fix: Call read_range on the range to get a fresh _schema, then retry.


transform_range cell limit

Too many cells (). Maximum is 50,000 per transform call.

Cause: rows × columns in the range exceeds 50,000 cells.

Agent fix: Narrow the range to fewer columns (e.g. Sheet1!A:D instead of Sheet1!A:Z), or process in multiple passes.


transform_range zero rows

SQL returned 0 rows. No changes written.

Cause: The SQL SELECT returned an empty result. The sheet was not modified — this is a safe no-op.

Agent fix: Review the WHERE clause. Use analyze_range with the same query to debug before retrying transform_range.


manage_sheets errors

Missing required fields

title is required for action=add
title is required for action=rename
sheetId is required for action=rename
sheetId is required for action=delete

Agent fix: Always call list_sheets first to get numeric sheetId values before rename or delete.


Delete blocked (non-empty sheet)

Exact message pattern:

Delete blocked: sheet "<title>" contains  rows of data.
Pass confirmDelete: true to permanently delete this sheet. THIS CANNOT BE UNDONE.

Cause: manage_sheets counted rows in column A before executing the delete. Sheet has data and confirmDelete was not true.

Agent fix: Pass confirmDelete: true to confirm. Warn the user this is irreversible — there is no restore_snapshot for sheet tab deletion.


Rename formula-ref warning (non-blocking)

After a successful rename, _warnings is always returned:

{
  "ok": true,
  "action": "rename",
  "_warnings": [
    "If other sheets contain formulas referencing the old name, they may be broken.
     Google Sheets auto-updates references on rename via the UI but NOT via the API."
  ]
}

This is not an error — the rename succeeded. But cross-sheet formula references may now be broken.


restore_snapshot errors

snapshotId is required for restore

Cause: Called with action: "restore" but no snapshotId provided.

Agent fix: Call restore_snapshot(action: "list") first, then use the UUID from the response.


Snapshot not found

Cause: The snapshotId UUID does not exist in write_history for the current user+spreadsheet combination. The snapshot may have been pruned (max 50 retained).

Agent fix: Call restore_snapshot(action: "list") to see currently available snapshots.


SQL errors

SQL injection guard (silent + error)

  • ; is stripped before the query reaches Postgres — no error, but statement chaining is silently prevented
  • DROP, DELETE, UNION patterns are rejected — results in a Postgres parse error or the keyword is stripped

Agent fix: Never pass raw user strings directly into SQL. Use only data ->>'column_name' JSONB lookups.


Google Sheets API pass-through errors

These originate from the Sheets API v4 and are returned as-is in the error field:

MessageCause
The caller does not have permissionSpreadsheet not in user's Drive, or read-only sharing
Requested entity was not foundInvalid spreadsheetId or tab deleted
Unable to parse rangeMalformed A1 notation — check sheet name and cell refs
Quota exceeded60 requests/min/user exceeded
No grid with id: sheetId number no longer exists

Tip:

When No grid with id: N occurs, sheets-mcp auto-appends the list of current valid sheet IDs and titles using formatSheetIdNotFoundMessage():

sheetId 999 not found in spreadsheet.
Use list_sheets to get the correct sheetId before retrying.
Available sheets: Sheet1 (0), Revenue (812345678), Archive (923456789).

Agents can parse the Available sheets suffix to self-correct without an extra list_sheets call.

On this page