Home / Guides / Google Sheets MCP Server Setup: OAuth and Config
MCP Server Architecture

Google Sheets MCP Server Setup: OAuth and Config

Reproducible 2026 engineering guide to configuring a least-privilege Google Sheets Model Context Protocol (MCP) server using the official Python mcp SDK (v1.2+) and Google Sheets API v4 service accounts, with sanitized claude_desktop_config.json, MCP Inspector verification, and error troubleshooting.

By FoxyData Infrastructure Team • Updated November 8, 2026 • 7 min read
Google Sheets MCP Server Setup: OAuth and Config
Affiliate & Verification Disclosure: This technical setup guide is verified against the official Model Context Protocol Specification (2025-03-26) and Google Sheets API v4 OAuth 2.0 Scope Documentation as of October 28, 2026. When you provision cloud servers through our verified infrastructure partner links, FoxyData may earn a commission at no additional cost to you.

1. Architectural Overview & Official SDK Reference

Connecting an LLM client (such as Claude Desktop, Cursor, or an internal agent orchestrator) to Google Sheets via the Model Context Protocol (MCP) frequently fails in production for two reasons: developers either grant overly broad full-Drive OAuth permissions (https://www.googleapis.com/auth/drive) that expose every corporate document to prompt-injection exfiltration, or they hardcode raw JSON private keys inside client config files where newline escaping breaks JWT signing.

Rather than relying on unverified third-party npm wrappers, this guide demonstrates a self-contained, auditable setup using:

  • Official Protocol & SDKs: Official MCP Python SDK (mcp >= 1.2.0) using FastMCP over stdio, paired with Google’s official google-api-python-client (>= 2.150.0) and google-auth (>= 2.35.0).
  • Authentication Mechanism: A dedicated Google Cloud Service Account with zero project-wide IAM roles, granted explicit per-sheet viewer (or editor) access only on an allowlisted set of spreadsheetId values.

2. Prerequisites & Least-Privilege Google Cloud Setup

Before configuring your MCP client, verify the following runtime and Google Cloud Console prerequisites:

  1. Local Runtime: Python 3.11+ with uv (uv >= 0.5.0) installed, plus Node.js 20.18+ LTS if you plan to run the interactive MCP Inspector CLI (npx @modelcontextprotocol/inspector).
  2. Enable Only the Google Sheets API: In the Google Cloud Console, enable sheets.googleapis.com (Google Sheets API v4). Do not enable drive.googleapis.com unless you specifically require filename search across Drive folders; keeping Drive disabled ensures the credential can only access spreadsheets addressed by their explicit 44-character spreadsheetId.
  3. Create a Zero-Role Service Account: Under IAM & Admin → Service Accounts, create a service account (for example, mcp-sheets-reader@your-project.iam.gserviceaccount.com). Skip granting any GCP project IAM roles (it does not need Project Viewer or Editor). Download the JSON key file to a restricted local path outside any Git repository:
    mkdir -p ~/.config/mcp-secrets
    mv ~/Downloads/your-project-sa-key.json ~/.config/mcp-secrets/gcp-sheets-sa.json
    chmod 600 ~/.config/mcp-secrets/gcp-sheets-sa.json
  4. Share the Target Spreadsheet Explicitly: Open your target Google Sheet in the browser, click Share, and add mcp-sheets-reader@your-project.iam.gserviceaccount.com as a Viewer (for read-only telemetry) or Editor (only if write tools are required).

3. Least-Privilege Google Sheets API v4 Scope Matrix

Google Sheets API v4 OAuth 2.0 Scopes for MCP Servers (Verified October 28, 2026)
OAuth 2.0 Scope URI Access Level MCP Production Recommendation
https://www.googleapis.com/auth/spreadsheets.readonly Read-only access to sheet metadata, tab names, and cell values (values.get, values.batchGet) Recommended Default: Prevents accidental row deletion or formula overwrites by an LLM tool call
https://www.googleapis.com/auth/spreadsheets Read and write access to cell ranges, tabs, and formatting on shared spreadsheets Use Only With Allowlist: Restrict writes to an explicit ALLOWED_SPREADSHEET_IDS environment allowlist and append-only ranges
https://www.googleapis.com/auth/drive Full read/write/delete access to all files in the account’s Google Drive Avoid in MCP Configs: Violates least privilege; unnecessary when addressing sheets by spreadsheetId

4. Reproducible Read-Only MCP Server (sheets_mcp_server.py)

Save the following 55-line script as /opt/mcp-sheets/sheets_mcp_server.py. It enforces both the read-only scope (spreadsheets.readonly) and an optional comma-separated ALLOWED_SPREADSHEET_IDS allowlist so the agent cannot query unauthorized sheet IDs:

# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "mcp>=1.2.0",
#   "google-api-python-client>=2.150.0",
#   "google-auth>=2.35.0",
# ]
# ///
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
from mcp.server.fastmcp import FastMCP

SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
KEY_PATH = os.environ["GOOGLE_APPLICATION_CREDENTIALS"]
ALLOWED_IDS = {
    s.strip() for s in os.environ.get("ALLOWED_SPREADSHEET_IDS", "").split(",") if s.strip()
}

mcp = FastMCP("foxydata-google-sheets-readonly")

def _sheets_client():
    creds = service_account.Credentials.from_service_account_file(KEY_PATH, scopes=SCOPES)
    return build("sheets", "v4", credentials=creds, cache_discovery=False).spreadsheets()

def _check_allowed(spreadsheet_id: str) -> None:
    if ALLOWED_IDS and spreadsheet_id not in ALLOWED_IDS:
        raise ValueError(f"Spreadsheet ID {spreadsheet_id} is not in ALLOWED_SPREADSHEET_IDS.")

@mcp.tool()
def list_sheet_tabs(spreadsheet_id: str) -> list[dict]:
    """Return tab titles and grid dimensions for an allowlisted Google Sheet."""
    _check_allowed(spreadsheet_id)
    meta = _sheets_client().get(
        spreadsheetId=spreadsheet_id,
        fields="sheets(properties(sheetId,title,gridProperties(rowCount,columnCount)))",
    ).execute()
    return [s["properties"] for s in meta.get("sheets", [])]

@mcp.tool()
def read_sheet_range(spreadsheet_id: str, a1_range: str = "Sheet1!A1:Z100") -> list[list[str]]:
    """Read a 2D array of formatted cell values from an A1 notation range."""
    _check_allowed(spreadsheet_id)
    res = _sheets_client().values().get(
        spreadsheetId=spreadsheet_id,
        range=a1_range,
        valueRenderOption="FORMATTED_VALUE",
    ).execute()
    return res.get("values", [])

if __name__ == "__main__":
    mcp.run(transport="stdio")

5. Sanitized Client Configuration (claude_desktop_config.json)

Add the server entry to your MCP client configuration (~/Library/Application Support/Claude/claude_desktop_config.json on macOS or ~/.config/Claude/claude_desktop_config.json on Linux). Notice that no private keys or tokens appear in the JSON config—only the file path in GOOGLE_APPLICATION_CREDENTIALS:

{
  "mcpServers": {
    "google-sheets-readonly": {
      "command": "uv",
      "args": [
        "run",
        "/opt/mcp-sheets/sheets_mcp_server.py"
      ],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "/Users/your_username/.config/mcp-secrets/gcp-sheets-sa.json",
        "ALLOWED_SPREADSHEET_IDS": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
      }
    }
  }
}

6. Step-by-Step Verification & Troubleshooting

Step 1: Verify Handshake & Tools in MCP Inspector

Before restarting your desktop client, test the server directly from your terminal using the official MCP Inspector:

GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/mcp-secrets/gcp-sheets-sa.json" \
ALLOWED_SPREADSHEET_IDS="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" \
npx @modelcontextprotocol/inspector uv run /opt/mcp-sheets/sheets_mcp_server.py

In the Inspector web UI (default http://127.0.0.1:6274), click Connect, open the Tools tab, and invoke list_sheet_tabs with your spreadsheet_id. A valid response returns an array of sheet tab objects (sheetId, title, gridProperties) in under 400 ms.

Step 2: Common Error Codes & Fixes

Google Sheets MCP Troubleshooting Matrix
Error / Symptom Root Cause Deterministic Fix
HttpError 403: PERMISSION_DENIED (The caller does not have permission) Either the target Google Sheet was not shared with the Service Account email, or sheets.googleapis.com is disabled in the GCP project. Copy the client_email from gcp-sheets-sa.json, share the Google Sheet with that address as Viewer, and verify gcloud services enable sheets.googleapis.com.
HttpError 404: Requested entity was not found Passing the full browser URL instead of the 44-character spreadsheetId, or referencing a tab name with unquoted spaces. Extract only the ID between /d/ and /edit, and wrap multi-word sheet names in single quotes in A1 notation (e.g., 'Q4 Revenue'!A1:F50).
google.auth.exceptions.RefreshError: invalid_grant: Invalid JWT Signature The service account key was revoked in GCP IAM, or system clock drift on the host exceeds 5 minutes. Verify system NTP sync (timedatectl status) and confirm the key ID (private_key_id) is still active in GCP IAM.
HttpError 429: RESOURCE_EXHAUSTED (Quota exceeded for quota metric 'Read requests') Google Sheets API v4 enforces a default limit of 300 read requests per minute per project and 60 per minute per user. Fetch larger contiguous ranges (e.g., A1:Z500) in a single call instead of looping cell-by-cell, or cache read results in memory for 60 seconds.

Host Remote MCP Gateways and Automation Workers on Unmetered NVMe VPS

Deploying remote Streamable HTTP MCP servers, workflow workers, or private internal API gateways? Provision an isolated Ubuntu 24.04 LTS NVMe VPS with full root SSH, Docker, and unmetered bandwidth.

Deploy Unmetered NVMe VPS →
Disclosure: FoxyData may earn a commission via partner links at no additional cost to you.