@simon-bouchard/google-sheets-mcp
v0.3.0
Published
A local, stdio-based MCP server for reading, editing and formatting existing Google Sheets.
Maintainers
Readme
google-sheets-mcp
Status: early. Tools are unit-tested and live-verified against a real Google Sheet, but expect rough edges.
A local, stdio-based MCP server that lets Claude Code (or any MCP client) read, edit and format existing Google Sheets. It authenticates as a Google Cloud service account, or, once the Koios OAuth client exists, as you after a one-time browser sign-in. Sibling project of google-docs-mcp, with the same auth model.
Setup
Create a Google Cloud project (or reuse the one from google-docs-mcp).
Enable the Google Sheets API for that project (APIs & Services -> Library -> "Google Sheets API" -> Enable). Enabling the Docs API does not cover Sheets. If you skip this, every call fails with a
SERVICE_DISABLED403, which reads like "the sheet isn't shared with me" but is not.Create a service account (IAM & Admin -> Service Accounts), or reuse an existing one. It needs no project roles: its access comes entirely from sharing.
Install its JSON key:
mkdir -p ~/.config/google-sheets-mcp cp <your-key>.json ~/.config/google-sheets-mcp/service-account.json chmod 600 ~/.config/google-sheets-mcp/service-account.jsonSet
GOOGLE_SHEETS_MCP_CONFIG_DIRto use a different directory.Share each spreadsheet with the service account's email (
<name>@<project>.iam.gserviceaccount.com) as Editor (or Viewer for read-only use).Send that email to the maintainer so it can be added as an editor of the shared feedback spreadsheet (used by both servers). Without it, automatic issue reports from your sessions fail silently.
Signing in with your Google account (not yet available)
A second auth mode is built in: sign in once in the browser and the server acts as you, seeing exactly the files you can see, with no key file or sharing step. It waits on a Koios Workspace admin creating the Internal OAuth client; until then, use the service account above. Both modes can stay installed side by side.
Get the OAuth client (a Desktop app client in a Koios-owned Cloud project) and save its JSON at
~/.config/google-workspace-mcp/oauth-client.json, or setGOOGLE_WORKSPACE_MCP_CLIENT_ID(andGOOGLE_WORKSPACE_MCP_CLIENT_SECRET). Once the Koios client exists it will ship with the package and this step goes away.Sign in:
npx -y @simon-bouchard/google-sheets-mcp loginThe browser opens Google's consent page; the token is saved at
~/.config/google-workspace-mcp/token.json(mode 600). The sign-in is shared with google-docs-mcp, so signing in through either server covers both.Restart Claude Code so the server picks up the sign-in.
Which mode is used: a saved sign-in wins over a service account key. Set
GOOGLE_SHEETS_MCP_AUTH=service_account (or oauth) to force one, for example to go back to the
service account without signing out. npx -y @simon-bouchard/google-sheets-mcp logout deletes the
saved token; to also revoke access, remove the app at
https://myaccount.google.com/connections. When signed in, issue reports are written as you, so
the feedback spreadsheet must be editable by you rather than by a service account.
GOOGLE_WORKSPACE_MCP_CONFIG_DIR moves the shared directory.
Installing as a Claude Code plugin
claude plugin marketplace add https://bitbucket.org/koiosemployee/google-sheets-mcp.git
claude plugin install google-sheets-mcpThis registers the server and adds the /google-sheets-mcp:feedback command. Or register only
the server:
claude mcp add --scope user google-sheets -- npx -y @simon-bouchard/google-sheets-mcpTool permissions go in ~/.claude/settings.json. Tool names are mcp__<server name>__<tool>,
so with the command above (server name google-sheets):
- To be prompted before every change to your sheets, add
mcp__google-sheets__write_cells,mcp__google-sheets__append_rows,mcp__google-sheets__clear_rangeandmcp__google-sheets__edit_spreadsheettopermissions.ask. - For automatic issue reports to really happen in the background,
add
mcp__google-sheets__report_issuetopermissions.allow; otherwise Claude Code stops to ask for permission the first time a report is filed. Run/mcpto see the exact server name if you installed it as a plugin.
{
"permissions": {
"allow": ["mcp__google-sheets__report_issue"],
"ask": ["mcp__google-sheets__edit_spreadsheet"]
}
}Tools
get_spreadsheet: the spreadsheet's title and its tabs (title, sheetId, grid size, frozen row and column counts).read_range: cell values of an A1-notation range, row-major.render: "formula"returns each cell's formula instead of its displayed value; read this way before overwriting cells that might hold formulas.include_format: truealso returnsformats(each cell's format, informat_cells' attribute names,nullwhen unformatted),merges,validations(each cell's dropdown), andconditional_formats(every rule touching the range, in priority order). It reads at most 2,000 cells and needs a range that names its tab.write_cells: overwrite one or more ranges in a single call. Each update can carry anexpectedgrid (the valuesread_rangereturned): if any guarded range has changed since, the whole call is refused withstale_valueand nothing is written. The check and the write are two API calls, so a change landing between them isn't caught; the guard is for "someone edited the sheet since Claude last read it", not for high-contention writes.clear_range: clear the values of one or more ranges, keeping their formatting.append_rows: append rows after the last row of the table in a range. Inserts new rows, never overwrites.edit_spreadsheet: structure and formatting, applied atomically (all or nothing). Operations run in order and each sees the ones before it: afterinsert_rowsbefore row 5, the old row 5 is row 6 for later operations. Every range names its tab ("Tab!B2:D10", rows"Tab!3:5", columns"Tab!C:D", whole tab"Tab"); quote titles with spaces or that look like a cell ("'My Tab'!A1","'Q1'!A1").- Structure:
add_sheet,rename_sheet,delete_sheet,duplicate_sheet,insert_rows,insert_columns,delete_rows,delete_columns,freeze. - Formatting:
format_cells(bold, italic, underline, strikethrough, font, size, text and background color, alignment, wrap, number format; sets only what you pass),clear_format,set_borders,set_column_width,set_row_height,auto_resize,merge_cells,unmerge_cells. - Dropdowns:
set_dropdown(a fixed list ofoptions, or anoptions_rangewhose cells hold the choices;strictby default) andclear_validation. Strictness only applies to people editing in the UI: Google does not check API writes against it. - Conditional formatting:
add_conditional_format(a condition such astext_eq,number_less,date_beforeorcustom_formula, formatting with bold, italic, strikethrough, text or background color),add_color_scale(min / optional mid / max colors), andclear_conditional_formats, which removes rules lying entirely inside a range and reports (innotes) any that extend outside it. New rules go on top, so they win where rules overlap. A clear must come before row/column inserts or deletes on the same tab in one call. - Takes the same optional
expectedguard aswrite_cells, checked before anything is sent. - Not supported yet: editing or reordering one conditional rule, other data validation (numbers, dates, checkboxes), charts, filters, sorting, protected ranges, alternating colors.
- Structure:
report_issue: files a bug or limitation of this server to the maintainers' feedback spreadsheet, in the background (see Automatic issue reports).
write_cells and append_rows default to RAW input, so a value starting with = is stored as
text, not evaluated. Pass value_input_option: "USER_ENTERED" to have formulas and dates parsed
as if typed into the UI. In write_cells it can also be set on a single update, so one call can
write formulas in some ranges while keeping untrusted text literal in the others; the call stays
atomic. Check formulas with read_range: the default render shows the result (or an error such
as #REF!), and render: "formula" shows the formula itself.
Errors come back as <code>: <message> with one of auth_required, not_found,
invalid_range, invalid_request, stale_value, rate_limited, or internal_error.
Automatic issue reports
When the server hits one of its own bugs (an unexpected internal_error), the error tells
Claude to file it in the background with report_issue, and the server attaches what Claude
can't see: its version, the failed call's operations and the stack trace. Claude also files,
following the server's instructions, when Google rejects a batch it checked was valid, when a
call succeeded but did something other than what was asked (a read-back shows it, or you point
it out; Claude reports even when unsure whose fault it is), or when you ask for a Google Sheets
task that none of this server's tools can do (a limitation). Those reports carry the server's
last three calls, reduced the same way as below, so the maintainers see what was actually sent.
Never reported: errors that are working as designed (the stale_value guard, fixable input
errors), setup problems (auth, sharing, rate limits), network problems and Google outages, and
requests meant for other tools or servers.
Reports go to the maintainers' feedback spreadsheet, shared with google-docs-mcp (Bugs and
Limitations tabs, Source column auto, Server column google-sheets-mcp), at most once
per problem per session, and Claude mentions each one in a single line. What gets written is
kept to what's needed to reproduce the problem:
- The spreadsheet id is removed; cell values, dropdown options and condition values become
their size (
[3x2 values],[4 values]), and new tab titles their length. Ranges keep their tab names. - Claude describes cell or tab content instead of quoting it.
- Credentials are scrubbed from every field before anything is written (tokens, keys,
passwords,
Authorizationheaders, OAuth client secrets, private keys, long random strings).
To turn it off, set GOOGLE_SHEETS_MCP_AUTO_REPORT=off. To let reports go through without a
permission prompt, allow report_issue (see Installing).
Reporting feedback yourself
/google-sheets-mcp:feedback [bug|limitation] [notes] drafts a report from the conversation,
shows it to you, and files it through report_issue after you confirm, even when automatic
reports are turned off. Anything you write after the kind word is kept verbatim in
Reporter notes, and the row's Source is manual with notes (or manual without notes).
Triaging reports
Both servers file into one spreadsheet. Each row stays there for good; its status is tracked in two columns maintainers fill in:
Status: a dropdown within progress,fixed,won't fixandduplicate. Empty means new; those rows are tinted so they stand out.Resolution: free text, e.g.fixed in 0.3.0 (ae34555)orduplicate of row 4.
To remove a report, delete its row rather than clearing its cells: a blank row splits the table, and later reports would then be inserted after the first block instead of at the end.
Maintainer setup (once): a spreadsheet with a Bugs tab whose first row is
Date | Source | Summary | Details | Steps to reproduce | Expected | Actual | Relevant errors | Reporter notes | Server | Status | Resolutionand a Limitations tab whose first row is
Date | Source | Summary | What was attempted | Workaround used | Details | Server context | Reporter notes | Server | Status | Resolutionshared as Editor with every user's service account (for both servers); then set
DEFAULT_FEEDBACK_SHEET_ID in src/reporting.ts here and in google-docs-mcp. Users can override
it with GOOGLE_SHEETS_MCP_FEEDBACK_SHEET_ID.
Roadmap
- Ship the Koios OAuth client with the package. The
sign-in mode is built; it needs a
Koios Workspace admin to create an Internal OAuth client in a Koios-owned Cloud project. Once it
exists, it goes into
BUNDLED_OAUTH_CLIENT(src/oauth.ts, here and in google-docs-mcp) so signing in needs no client file. The service-account mode stays as a fallback.
Developing
npm install
npm run build
npm testnpm run dev runs the server from TypeScript source via tsx.
npm run smoke-test (with SHEETS_MCP_TEST_SPREADSHEET_ID set to a spreadsheet the service
account can edit) runs every tool live inside a scratch tab it creates and then deletes.
Set SHEETS_MCP_TEST_REPORT_SPREADSHEET_ID to a scratch spreadsheet (never the real feedback
one) to also exercise report_issue: the smoke test creates temporary Bugs and Limitations
tabs there, files into them, and deletes them. It refuses if those tabs already exist.
