# Google Sheets MCP [Health: Active]

**Category:** 🏢 Workplace & Productivity  
**Repository:** https://github.com/A1-x-Tech/mcp-google-sheets  
**GitHub Stars:** 1  
**npm Downloads (last month):** 1055  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/google-sheets-mcp

## Description
MCP server for the Google Sheets API: read and write ranges, sheets, formatting, charts, sharing.

## Tools
Capabilities this server exposes over MCP:

- **auth_status** — Shows whether this server is connected to Google: token presence and source (env variables or a stored in-chat login), expiry, the Google account email, granted vs missing OAuth scopes, where the credentials file lives and where the OAuth client comes from. Makes no network calls and never returns the token itself. Call it first when other tools report the server is not connected.
- **setup_instructions** — Step-by-step checklist for connecting this server to Google: creating a Google Cloud project and a Desktop-app OAuth client, publishing the consent screen (mandatory — Testing-mode refresh tokens die after 7 days), downloading the client JSON and handing its PATH to set_client. Works without any credentials; the checklist shortens to 'enable the API + log in' when an OAuth client is already configured (one client serves the whole mcp-google-* line). Never asks the user to paste secrets into the chat.
- **set_client** — Saves the OAuth client credentials from the JSON file downloaded from Google Cloud Console ('Download JSON' on a Desktop-app client). Pass the file PATH — the secret must never be pasted into the chat. The client is stored once in the shared ~/.config/mcp-google-auth/client.json (owner-only) and reused by every mcp-google-* server; tokens stay per-server. After this, call start_login.
- **start_login** — First step of connecting from the chat, without editing config files or restarting the client. Returns authorizeUrl — show it to the user as a clickable link and ask them to open it in the browser ON THIS MACHINE, pick the Google account and approve access. A one-shot listener on 127.0.0.1 catches Google's redirect; the code is exchanged locally and never passes through the chat. Does not open the browser itself. The attempt lives 10 minutes; when the browser shows the success page, call finish_login.
- **finish_login** — Second step: waits up to 20 s for the browser consent to finish, saves the tokens to an owner-only file and verifies the login with a read-only identity call, returning the account email and the granted scopes. After success every tool works immediately — no client restart. If the user granted only part of the requested permissions, the login is still saved and missingScopes lists what will not work. Logging in under a different Google account replaces the previous login (its refresh token is revoked best-effort) and the response carries previousAccountEmail so the change never goes unnoticed.
- **logout** — Revokes the stored token at Google (oauth2.googleapis.com/revoke) and deletes the local credentials file. Tokens supplied via env variables are NOT touched — remove them from the MCP client config manually; envTokenStillSet in the response says whether any are still in effect.
- **create_spreadsheet** — Creates a new Google Sheets spreadsheet and returns it: spreadsheetId, spreadsheetUrl, properties (title, locale, timeZone) and sheets[] with each sheet's numeric sheetId. sheet_titles creates one tab per title in order (omitted = a single default "Sheet1"). The file lands in the authorized user's My Drive root — moving it into a folder needs the Drive API, which this server does not cover. Save the returned spreadsheetId: the Sheets API has no list endpoint of its own (search_spreadsheets exists, but it needs a Drive scope on the token).
- **get_spreadsheet** — Returns the spreadsheet's structure: properties (title, locale, timeZone), sheets[] with properties (sheetId, title, index, gridProperties incl. rowCount/columnCount and frozenRowCount/frozenColumnCount), plus each sheet's protectedRanges, conditionalFormats, tables and charts, and the spreadsheet's namedRanges. Call this FIRST whenever a structural tool needs a sheetId, protectedRangeId, tableId, chartId or a conditional-format rule index — titles are not addresses. By default no cell data is returned; include_grid_data=true (optionally limited to ranges) embeds cells but is heavy — prefer read_values for data. fields is a partial-response mask to trim the payload, e.g. "sheets.properties".
- **search_spreadsheets** — Finds Google Sheets spreadsheets the authorized user can open (own files and shared drives; trashed files are excluded): id, name, createdTime, modifiedTime, owners and webViewLink per file, newest-modified first by default. name_contains filters by name substring; omit it to list everything. Paginate with page_token from nextPageToken. This is the one read that goes through the Drive API internally, so the OAuth token needs a Drive scope (drive, drive.readonly or drive.file for app-created files) — with only the spreadsheets scope it fails with 403 while every other tool still works.
- **read_values** — Reads one or more A1 ranges in a single call (one request against the quota, however many ranges) and returns valueRanges[] — each with its resolved range and a 2-D values array (outer = rows unless major_dimension=COLUMNS). Trailing empty rows/columns are omitted; a fully empty range has no values key at all. value_render_option: FORMATTED_VALUE (default, strings as displayed, honoring the cell's number format and locale), UNFORMATTED_VALUE (raw numbers/booleans), FORMULA (the formula text, e.g. "=SUM(A1:A10)" — the way to read formulas). With UNFORMATTED_VALUE, dates arrive as serial numbers unless date_time_render_option=FORMATTED_STRING.
- **write_values** — Overwrites one A1 range with a 2-D values matrix (rows first) and returns updatedRange/updatedRows/updatedColumns/updatedCells. The matrix is anchored at the range's top-left corner; cells beyond the matrix keep their old content, and a null entry skips (does not clear) that cell — use clear_values to empty cells. value_input_option USER_ENTERED (default) parses input like typing in the UI: "=SUM(A1:A10)" becomes a live formula, "1,234" and "2026-01-15" become number/date per the spreadsheet locale; RAW stores everything as literal values. For several ranges use batch_write_values (one quota unit instead of N).
- **batch_write_values** — Overwrites several A1 ranges in ONE call — one write against the per-minute quota instead of one per range, so always prefer this over looping write_values. data is a list of {range, values} pairs; all are written with the same value_input_option (USER_ENTERED parses formulas/numbers/dates, RAW stores literally). Returns totalUpdatedCells and a per-range responses[] breakdown. Like write_values, null entries skip cells rather than clearing them.
- **append_values** — Appends rows after the last row of the data table that contains the given range — pass the table's region (e.g. "Sheet1!A1:D1" or just "Sheet1") and the API finds the first free row itself; the response's updates.updatedRange shows where the rows actually landed. insert_data_option INSERT_ROWS pushes existing data below down; OVERWRITE (default behaviour) writes into the free rows after the table. Never retried after an ambiguous failure — re-appending would duplicate the rows, so check the sheet first (read_values) before re-sending.
- **clear_values** — Empties the VALUES of one or more A1 ranges in a single call — cell contents and formulas are gone (no undo through the API), while formatting, data validation, notes, conditional formats and merges all stay. To also remove formatting use format_cells or raw_request; to delete whole rows/columns (not just their content) use manage_dimensions action=delete.
- **manage_sheets** — Manages the sheets (tabs) of a spreadsheet. action=add creates a tab (title required; optional index position and row_count/column_count — default 1000×26). action=duplicate copies a tab within the same spreadsheet (sheet_id; optional title for the copy and index). action=rename changes a tab's title (sheet_id + title; the numeric sheetId never changes, so other tools keep working). action=delete removes the tab AND all its data — irreversible through the API, and deleting the last remaining sheet fails. action=copy_to copies a tab into ANOTHER spreadsheet (sheet_id + destination_spreadsheet_id; the copy arrives named "Copy of ..." — rename it there). Get sheet_id values from get_spreadsheet; every action except add returns batchUpdate replies with the affected sheet's properties.
- **format_cells** — Applies cell formatting to a range: background_color, text color/bold/italic/strikethrough/underline/font_size/font_family, horizontal/vertical alignment, wrap_strategy, and number format (number_format_type NUMBER/PERCENT/CURRENCY/DATE/TIME/DATE_TIME/SCIENTIFIC/TEXT with an optional number_format_pattern like "#,##0.00" or "dd.mm.yyyy"). Only the provided properties are touched — the update mask is computed automatically, so existing formatting outside it survives; at least one formatting field is required. The range is a grid rectangle addressed by sheet_id + 0-based indexes (get sheet_id from get_spreadsheet). Colors are "#RRGGBB" hex strings.
- **set_frozen** — Freezes the first N rows and/or columns of a sheet so they stay visible while scrolling (typical: frozen_rows=1 pins the header). 0 unfreezes. At least one of frozen_rows / frozen_columns is required; the other stays as it is. You cannot freeze all rows or all columns of a sheet — at least one unfrozen row/column must remain.
- **set_borders** — Draws borders around and/or inside a grid range. top/bottom/left/right are the range's outer edges; inner_horizontal/inner_vertical are the grid lines between cells inside it. Each side takes {style, color?} — styles SOLID, SOLID_MEDIUM, SOLID_THICK, DOTTED, DASHED, DOUBLE, or NONE to remove that side's border. Only the provided sides change; at least one is required. Colors are "#RRGGBB" hex (default black).
- **manage_dimensions** — Row/column operations on a run of rows (dimension=ROWS) or columns (dimension=COLUMNS), addressed by sheet_id + 0-based start_index (inclusive) and end_index (exclusive) — e.g. columns A-C = start 0, end 3. action=resize sets an exact pixel_size; auto_resize fits to content; insert adds empty rows/columns at start_index (inherit_from_before=true copies formatting from the row/column before instead of after); delete removes them WITH their data (irreversible; cell references below/right shift); hide/show toggle visibility without touching data; group/ungroup add or remove a collapsible outline group over the run (groups nest — repeat group on a subrange for a deeper level).
- **set_data_validation** — Sets — or clears — a data-validation rule on a grid range. With condition_type set, every cell in the range gets the rule: ONE_OF_LIST with condition_values plus show_custom_ui=true is the classic in-cell dropdown; ONE_OF_RANGE takes a "=Sheet1!A1:A10" formula; NUMBER_/TEXT_/DATE_ conditions restrict input; CUSTOM_FORMULA takes a formula evaluated per cell. strict=true rejects invalid input outright, strict=false only shows a warning; input_message is the help text shown on the cell. OMIT condition_type (and the other rule fields) to REMOVE validation from the range. Overwrites any previous rule on the range — one rule per cell.
- **manage_protected_ranges** — Manages protections that stop other editors from changing cells. action=add protects a grid range (or a named range via named_range_id; a range with only sheet_id protects the whole sheet): warning_only=true merely warns before edits, otherwise only the listed editor_users/editor_groups (emails) plus the owner may edit — note the calling user is NOT added automatically. Returns the new protectedRangeId in the replies. action=update changes description/warning_only/editors of an existing protection (protected_range_id required; provided fields replace the old values). action=delete removes the protection — the cells and data stay, but anyone with edit access can change them again. Find existing protectedRangeIds via get_spreadsheet.
- **manage_conditional_formats** — Manages conditional-format rules that style cells when a condition holds (boolean rules; gradient color scales need raw_request). Rules are addressed by SHEET + INDEX in that sheet's rule list — get current rules and indexes from get_spreadsheet (sheets[].conditionalFormats), and re-read after every mutation because add/delete shift later indexes. action=add inserts a rule at index (default 0 = highest priority; rules are evaluated in order and the first match wins): needs ranges, condition_type (+condition_values; CUSTOM_FORMULA with a "=..." formula is the most flexible) and at least one format field (background_color, text_color, bold, italic). action=update replaces the ENTIRE rule at sheet_id+index with the newly provided one. action=delete removes the rule at sheet_id+index.
- **manage_tables** — Manages structured tables (the "Convert to table" feature: named ranges with per-column types, filters and formatting). action=add creates a table over a grid range whose FIRST ROW becomes the header: name (must be unique in the spreadsheet) + range required; column_properties optionally types the columns — a list of raw TableColumnProperties objects, e.g. [{"columnIndex":0,"columnName":"Task","columnType":"TEXT"},{"columnIndex":1,"columnName":"Done","columnType":"BOOLEAN"}] (columnType TEXT/PERCENT/DROPDOWN/DOUBLE/CURRENCY/DATE/TIME/DATE_TIME/BOOLEAN; DROPDOWN adds dataValidationRule). The reply carries the new table with its tableId. action=update renames and/or re-ranges an existing table (table_id + name and/or range; expanding the range grows the table). action=delete removes the TABLE DEFINITION only — the cell data stays; clear the cells separately if needed. Find tableIds via get_spreadsheet (sheets[].tables).
- **manage_charts** — Manages embedded charts. action=add builds a chart from chart_type (COLUMN/BAR/LINE/AREA/STEPPED_AREA/SCATTER/PIE), domain_range (x-axis labels / pie labels), series_ranges (one grid range per data series; PIE takes exactly one) and optional title/legend_position/header_count (rows of the ranges treated as headers, default 1) — place it with anchor {sheet_id,row_index,column_index} (the cell under the chart's top-left corner) or new_sheet=true for its own chart sheet; the reply carries the new chartId. Ranges should be single columns (or rows) including the header cell. For chart kinds beyond the basic set (combo, waterfall, histogram, org …) pass a raw Sheets API ChartSpec via spec instead — it overrides the simplified fields. action=update REPLACES the whole spec of an existing chart (chart_id + the same spec-building fields; there is no partial chart update). action=delete removes the chart by chart_id. Find chartIds via get_spreadsheet (sheets[].charts).
- **manage_permissions** — Shares the spreadsheet (Drive permissions on the file — the OAuth token needs a Drive scope; the spreadsheets scope alone gets 403 here while every Sheets tool still works). action=list shows who has access: id, type, role, emailAddress/domain per permission; one page per call (shared-drive files cap a page at 100) — when the reply carries nextPageToken, pass it back as page_token for the rest. action=grant gives role reader/commenter/writer to type user/group (email_address required), domain (domain required, e.g. "example.com") or anyone (makes the link public — use deliberately); send_notification_email (default true for users) and email_message control the notification, allow_file_discovery lets domain/anyone grants surface in search. action=update changes an existing permission's role (permission_id + role). action=revoke removes a permission (permission_id) — the person loses access immediately. Ownership transfer is not supported by this server. Protecting individual ranges from co-editors is manage_protected_ranges, not this tool.
- **raw_request** — Escape hatch to call any Google Sheets API v4 path directly, for requests the typed tools don't cover — e.g. a batchUpdate with mergeCells, named ranges, banding, basic filters, slicers, sortRange, findReplace, gradient conditional-format rules, developer metadata, or several requests in one atomic call: path "v4/spreadsheets/<spreadsheetId>:batchUpdate", method POST, body {"requests":[...]}. The path may carry a query string. The Bearer token is added automatically; the method defaults to GET (values updates use PUT). Sheets API paths only — Drive paths are not reachable here.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "google-sheets-mcp": {
    "command": "npx",
    "args": ["-y","@a1-x-tech/mcp-google-sheets@latest"],
    "env": {
      "GOOGLE_SHEETS_CLIENT_ID": "",
      "GOOGLE_SHEETS_CLIENT_SECRET": "",
      "GOOGLE_SHEETS_REFRESH_TOKEN": ""
    }
  }
}
```

**Requires environment variables:** `GOOGLE_SHEETS_CLIENT_ID`, `GOOGLE_SHEETS_CLIENT_SECRET`, `GOOGLE_SHEETS_REFRESH_TOKEN` — the values above are empty placeholders; fill in real credentials before running (see the repository for what each one is for).

## Documentation

## What Google Sheets MCP does

Google Sheets MCP gives an MCP-compatible AI client tools for working with spreadsheet files and their contents. It can find spreadsheets, create new ones, inspect sheet structure, read values and formulas, write or append rows, and clear cell contents. It also covers spreadsheet presentation and organization, including sheet creation and duplication, frozen rows, formatting, borders, dimensions, data validation, protected ranges, conditional formatting, structured tables, and charts.

Access management is included for spreadsheet files. An agent can inspect existing permissions, grant read or edit access, and share a file. Google Drive is used internally for spreadsheet search and sharing, but the server does not expose generic Drive operations.

## How it works

The server runs as an npm package over MCP stdio. After it is added to an AI client, a user can ask the assistant to connect Google Sheets. The connection flow guides the user through OAuth client setup, receives Google's redirect on `127.0.0.1`, uses PKCE, and retains the resulting tokens without requiring configuration-file edits or a restart.

Value operations use A1 notation such as `'Q3'!A1:F50`. Structural operations use a numeric, zero-based `sheetId`; the spreadsheet metadata response provides those identifiers. Normal writes replace the addressed range, while appends add rows after the existing data. A `null` cell is skipped rather than cleared. Clearing values removes values and formulas while preserving formatting, validation, notes, and merges.

Batch operations can combine multiple ranges or requests and count as one quota operation. A Sheets `batchUpdate` applies all requests together or applies none of them. The server does not replay a write after an ambiguous failure, which avoids duplicating an append. Destructive operations are explicitly marked so the client can request confirmation.

## Setup and configuration

Node.js 20 or newer and a Google account are required. Install the latest package through an MCP client using `npx -y @a1-x-tech/mcp-google-sheets@latest`. The documented stdio setup works with Codex, Claude Code, Claude Desktop configurations that support local MCP servers, Cursor, and VS Code.

The server uses Google OAuth credentials represented by `GOOGLE_SHEETS_CLIENT_ID`, `GOOGLE_SHEETS_CLIENT_SECRET`, and `GOOGLE_SHEETS_REFRESH_TOKEN`. The `spreadsheets` scope covers Sheets operations. A Drive scope is needed only when searching for spreadsheets or managing sharing permissions.

## Tools and capabilities

- Search for spreadsheets and create new spreadsheet files.
- Read ranges, formulas, metadata, sheet sizes, frozen rows, and sheet objects.
- Write, append, clear, and batch-update cell values.
- Create, duplicate, rename, and manage sheets.
- Apply formatting, borders, dimensions, conditional rules, validation, protected ranges, and tables.
- Build charts and manage spreadsheet permissions.
- Send supported Sheets API requests through `raw_request` for features without dedicated tools.

`raw_request` is limited to the Sheets API. Merged cells, named ranges, banding, filters, slicers, find-and-replace, and gradient conditional-format rules may require it. New spreadsheets are created in the My Drive root, and ownership transfer is not supported through permission management.

_Full upstream README: https://allmcps.com/mcp/google-sheets-mcp/readme_

