·

Google Sheets MCP With Claude Code CLI and VS Code

Set up Google Sheets MCP in Claude Code CLI and VS Code so your AI agent can read and write spreadsheet data right from your editor.

Claude Code is the client where a Google Sheets MCP integration tends to feel most natural, because the tool-approval model and the CLI's ability to chain reads, local computation, and writes in a single turn map well onto "pull data → reason about it → push results back." This topic walks through getting it installed, then focuses on the two workflows that matter most: reading ranges for analysis, and writing results back without corrupting anything.

Installing and Connecting Google Sheets MCP to Claude Code

Register the server with the CLI's built-in MCP manager rather than hand-editing config where you can avoid it — it validates the entry immediately:

claude mcp add google-sheets -- npx -y @gongrzhe/server-gsheets-mcp

If you're running a Python-based server instead (mcp-google-sheets by xing5), the equivalent looks like:

claude mcp add google-sheets -- uvx mcp-google-sheets

Both forms write an entry into your Claude Code MCP config. For a project-scoped setup you want checked into the repo (so teammates get the same server), add it to .mcp.json at the repo root instead:

{
  "mcpServers": {
    "google-sheets": {
      "command": "npx",
      "args": ["-y", "@gongrzhe/server-gsheets-mcp"],
      "env": {
        "GOOGLE_CREDENTIALS_PATH": "${GOOGLE_SHEETS_CREDENTIALS_PATH}",
        "GOOGLE_TOKEN_PATH": "${HOME}/.config/google-sheets-mcp/token.json"
      }
    }
  }
}

Note the ${GOOGLE_SHEETS_CREDENTIALS_PATH} env-var substitution — never hardcode the path to credentials.json in a file that gets committed, since the path itself isn't sensitive but a careless copy-paste of the file into the repo is a real failure mode teams hit. Set the actual value in your shell profile or a local .env that's gitignored.

Verify the connection from inside Claude Code:

/mcp

This lists connected servers and their tool counts. If google-sheets shows zero tools or a connection error, the almost-always cause is the first-run OAuth consent flow never completed — run the server binary directly once from your terminal (outside Claude Code) to trigger the browser consent screen, then retry.

In VS Code with the Claude Code extension, the same .mcp.json is picked up automatically when you open the workspace; there's no separate registration step. The one VS Code-specific wrinkle: the extension's integrated terminal sometimes runs under a different shell profile than your regular terminal, so if env-var substitution fails there but works in a plain terminal, check that the extension's terminal actually sources the file where GOOGLE_SHEETS_CREDENTIALS_PATH is defined.

Tips
- Run claude mcp list after adding the server to confirm the exact command Claude Code will execute — a typo in the npx package name fails silently as "0 tools" rather than a clear error in some client versions.
- Commit .mcp.json with env-var placeholders, never with literal credential paths or tokens, and add token.json / credentials.json to .gitignore explicitly even if they live outside the repo — belt and suspenders.
- If you manage multiple Google accounts, name the server entry with the account context (google-sheets-personal, google-sheets-work) rather than a bare google-sheets — token caches are per-server-instance, and ambiguity here causes agents to write to the wrong account's spreadsheet.


Reading Ranges and Producing Analysis from Spreadsheet Data

The pattern that works reliably: name the spreadsheet ID (or ask the agent to resolve it from a URL you paste), name the exact range, and state the analysis goal in one prompt rather than splitting it across turns — Claude Code holds enough context in one turn to read, reason, and summarize without re-fetching.

Read range 'Raw Data'!A1:H1200 from spreadsheet
1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms.
Row 1 is the header. Column F is order_value (USD), column G is region,
column H is order_date (YYYY-MM-DD).

Give me: total order_value by region for orders where order_date is in
Q1 2026, sorted descending, plus a call-out of any row where order_value
is negative or blank.

Claude Code will call get_sheet_data for the range, then reason over the returned JSON directly rather than writing a temp script for a dataset this size — for larger ranges (five figures of rows) it's worth explicitly nudging it toward code:

Read Sheet1!A2:J50000 in batches of 5000 rows if the tool truncates
the response. Then write a short Python script to compute the
month-over-month growth rate per region and run it — don't do this
arithmetic by hand in your reasoning.

That second prompt matters because MCP responses have practical size ceilings — a 50,000-row read can exceed a single tool-call's response budget depending on the server and client, and asking the agent to paginate explicitly (rather than discovering the truncation itself, potentially silently) avoids analysis run against a partial dataset without anyone noticing.

For genuinely large sheets, an underused move is to have the agent write the raw range to a local CSV via a script first, then analyze the CSV with pandas:

python3 -c "
from googleapiclient.discovery import build
result = sheet.values().get(spreadsheetId=SHEET_ID, range='Sheet1!A1:J50000').execute()
import csv
with open('/tmp/export.csv', 'w') as f:
    csv.writer(f).writerows(result['values'])
"

This sidesteps MCP response limits entirely and gives you a reproducible, diffable snapshot of what the analysis was actually run against — worth it for anything you'll need to defend the numbers on later.

Tips
- State column meanings explicitly in the prompt even when the header row is included in the read — the agent shouldn't have to infer that "col F" is a dollar amount from formatting alone.
- For ranges over roughly 10,000 rows, prefer "export to CSV then analyze" over "analyze the MCP read result directly" — it's more reliable and leaves you an artifact to check.
- Ask for the agent's row count and a spot-check of three specific rows before trusting an aggregate — "show me the three highest order_value rows you included" catches header-row-included-in-sum bugs immediately.


Writing Results, Summaries, and Generated Columns Back to Sheets

Writing back is where you enforce the guardrails from the first topic. The reliable pattern is: compute in one step, show the result in chat, get confirmation (explicit or via your own read of the output), then write in a second step — don't fuse "analyze and write" into one unsupervised instruction on a sheet you care about.

Step 1: Read 'Raw Data'!A1:H1200, compute total order_value by region
for Q1 2026, and show me the table in chat. Don't write anything yet.

Then, after reviewing:

Step 2: Write that table to a new tab called 'Q1-Summary', starting at
A1, with headers "Region" and "Total Order Value". Use update_cells
with valueInputOption RAW since these are computed numbers, not formulas.
Create the tab first if it doesn't exist.

Splitting these matters more than it looks like it should — in a single fused prompt, an agent under time pressure (or a subtly ambiguous instruction) will sometimes write directly into the source range instead of a new tab, because "put the summary in the sheet" is genuinely ambiguous about location.

For a generated column pattern — say, a validation flag column added to existing data — target the destination precisely and confirm it doesn't collide with existing content:

Read Sheet1!A1:A500 (the header) and Sheet1!I1 specifically.
If I1 is empty, write "validation_status" to I1 and then write
per-row validation results to I2:I500 based on the rule: flag "MISSING_EMAIL"
if column E is blank, otherwise "OK". If I1 is not empty, stop and tell me
what's already there instead of overwriting it.

That "stop and tell me" clause is doing real work — it converts a potential silent overwrite into a visible failure you can react to.

Tips
- Never combine "compute a novel aggregate" and "write it back" in one unreviewed prompt on a production or shared spreadsheet — split into a dry-run turn and a commit turn.
- Have the agent check the destination cell/range is empty (or matches your expectation) before writing, and instruct it to abort with a message rather than proceeding on mismatch.
- After any write, ask for a read-back confirmation (get_sheet_data on the exact range just written) in the same turn — this catches off-by-one row/column errors before you close the session.


Prompting Patterns for Reliable Range and Header Handling

Most failures in this integration trace back to one of three prompting gaps, all fixable with habit rather than tooling.

1. Missing the header offset. "Write to row 2 onward" is unambiguous; "write the results starting after the header" is not, because the agent has to infer where the header ends, and a multi-row merged header will break that inference. State the literal starting row.

2. Sheet name ambiguity in multi-tab spreadsheets. Always qualify ranges with the sheet name, even for the first/default tab:

Sheet1!A2:F100        # explicit — always resolves the same tab
A2:F100               # ambiguous if there's more than one tab, or if "active tab" concept differs by server

3. Assuming stable column positions across sessions. Spreadsheets used by humans get columns inserted. A prompt hardcoded to "column F" from last week's session can silently point at the wrong data this week. The fix is a standing instruction pattern:

Before doing anything else, read row 1 of 'Raw Data' and confirm which
column letter currently holds order_value, region, and order_date.
Use those confirmed letters for the rest of this task — don't assume
they match a previous session.

This costs one extra tool call and eliminates an entire class of wrong-column bugs. For recurring reports against the same sheet, it's worth codifying this as a project-level instruction in CLAUDE.md so every session re-derives column positions rather than trusting memory:

## Google Sheets Convention
Before reading or writing 'Weekly Metrics' spreadsheet, always re-read
row 1 to confirm column letters. Never hardcode column positions from
a prior session's output.

Tips
- Put sheet-specific conventions (tab names, header row number, key columns) into a CLAUDE.md snippet once you've used a spreadsheet more than twice with the agent — it turns tribal prompting knowledge into an enforced default.
- Prefer named-range-style explicitness ('Sheet Name'!A2:F100) over relative phrasing ("the second sheet", "the data range") in every prompt, even when it feels verbose.
- When a report is genuinely recurring (weekly, monthly), keep the exact working prompt in a repo file (e.g., docs/prompts/weekly-report.md) rather than reconstructing it from memory each time — spreadsheet structure drift is easier to catch as a diff against a saved prompt than as a vague sense that "the output looks off."


Tips

Claude Code's strength with Google Sheets MCP is holding read, analysis, and write together in one coherent session — use that by front-loading context (column meanings, header rows, tab names) once per session rather than re-explaining it turn by turn, and by splitting "compute" from "commit" whenever the destination is a sheet other people rely on.

Tips
- Keep a short CLAUDE.md section per frequently used spreadsheet documenting its tab names, header row, and key column letters — it pays for itself after the second session.
- Default to a dry-run-then-write two-step pattern for any write beyond a single confirmed cell.
- Re-verify column letters at the start of every session against a sheet that humans also edit — never trust a prior session's column mapping to still be valid.