Google Docs MCP
Google Docs MCP
A Model Context Protocol (MCP) server that lets MCP-compatible AI clients (Claude Desktop, Claude Code, VS Code, Cursor and others) work with your Google Docs. It can create, read, edit, format, organize and search documents through the official Google Docs API and Google Drive API.
You sign in once with Google OAuth 2.0. After that, you can say things like "Create a document called FYP Proposal and add the following content…", and the AI client calls this server's tools to do it. You don't have to open the Google Docs UI.
It is written in strict TypeScript on Node.js and uses the official MCP TypeScript SDK (v2) over the stdio transport.
Features
Google OAuth 2.0 sign-in. Uses the authorization-code flow with PKCE (S256), a CSRF
statecheck and a loopback redirect. Access tokens refresh automatically. Tokens are stored locally in a file only you can read, and re-authentication is guided when needed.Document management. Create, read, list, copy and delete documents. Delete moves a document to the Drive trash; nothing is ever permanently deleted.
Content editing. Append, insert at an index, find-and-replace, and delete a range. A delete can include an optional
expectedTextsafety check.Formatting. Bold, italic, underline, strikethrough, font size and family, text and highlight colors, named paragraph styles (Title, Subtitle, Heading 1–6), and alignment.
Structure. Page breaks, tables, hyperlinks, and bulleted, numbered or checkbox lists.
Search. Find Docs by file name and/or full-text content (
search_documents), or get exact indexes of a phrase inside a document (find_text).MCP resources.
google-docs://document/{documentId}exposes a document's plain text.MCP prompts.
summarize_document,rewrite_document,format_document,create_meeting_notes.Predictable results. Every tool returns JSON
{ "success": true, "data": … }or{ "success": false, "error": { "code", "message", "retryable" } }. Error codes are stable.Friendly inputs. Every tool accepts either a document ID or a full Google Docs URL.
Secure by default. Least-privilege scope option, strict input validation, redaction of secrets in logs and errors, and index validation before any write.
Architecture
MCP Client (Claude Desktop, Claude Code, VS Code, Cursor, ...)
│
│ MCP (JSON-RPC over stdio)
▼
MCP Server ── McpServer from @modelcontextprotocol/server v2, served with serveStdio
│
├── Authentication src/auth
│ ├── GoogleAuthManager OAuth 2.0 + PKCE, loopback redirect, token refresh
│ └── TokenManager tokens.json (0600, atomic writes)
│
├── MCP Tools / Resources / Prompts src/tools, src/resources, src/prompts
│ │ (zod-validated input, uniform { success, data | error } results)
│ ▼
│ Services (business logic) src/services
│ │ index validation, Drive query building, document parsing
│ ▼
└── Google API Clients src/google (DocsClient, DriveClient)
│
┌─────┴─────┐
▼ ▼
Google Docs Google DriveTools only declare schemas and descriptions, then delegate to services.
Services hold the business logic and depend on small
DocsClient/DriveClientinterfaces. This lets unit tests replace Google entirely.Google API clients are thin wrappers around
@googleapis/docsand@googleapis/drive. They get an authorized OAuth2 client from the auth layer for every call.stdout is reserved for the MCP protocol. All logs go to stderr as JSON lines.
Requirements
Node.js 22.12 or newer (
node --version).A Google Cloud project (free) in the Google Cloud Console.
Google Docs API enabled in that project.
Google Drive API enabled in that project.
An OAuth 2.0 client ID of type "Desktop app", with its client secret. See Google OAuth Setup.
An MCP-compatible client.
Installation
git clone https://github.com/ammarqaisar11a55/google-docs-mcp.git
cd google-docs-mcp
npm install
npm run buildThe build writes the compiled server to dist/index.js. MCP clients need the absolute path to this file.
Configuration
The server is configured with environment variables. Copy the example file and fill in your OAuth client:
cp .env.example .envGOOGLE_CLIENT_ID=123456789012-abc123.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret
GOOGLE_REDIRECT_URI=http://127.0.0.1:53682/oauth2callback
# GOOGLE_TOKEN_PATH=
# GOOGLE_DRIVE_SCOPE=drive
# LOG_LEVEL=infoVariable | Required | Default | Description |
| yes | – | OAuth client ID of your Desktop app client. |
| yes | – | OAuth client secret of that client. |
| no |
| Loopback address where the one-time sign-in redirect is received. It must be |
| no |
| Where OAuth tokens are stored. |
| no |
| Drive permission level. |
| no |
|
|
| no | – | Tests only. Set to |
| no | – | Tests only. Token file of the Google test account used by the integration tests. |
Notes:
The server reads a
.envfile from the current working directory and from the package root (the folder that containsdist/). Values from.envnever override variables already set in the real environment, for example variables set in your MCP client'senvblock. This means you can keep your credentials in<repo>/.envand leave them out of client configs.Blank values (
GOOGLE_DRIVE_SCOPE=) count as unset.Invalid values (for example
LOG_LEVEL=verbose, or a non-loopback redirect URI) stop startup with aCONFIG_ERROR. Error messages never include the configured values..env,tokens.jsonandclient_secret*.jsonare git-ignored. Never commit them.
Google OAuth Setup
You only do this once. The console labels below match the current Google Cloud Console, where the OAuth consent screen now lives under Google Auth Platform.
Create a project. Open the Google Cloud Console, click the project picker in the top bar, and choose New project. Give it a name (for example
google-docs-mcp), click Create, and make sure the new project is selected.Enable the APIs. Go to APIs & Services → Library. Search for Google Docs API and click Enable. Then search for Google Drive API and click Enable. Both are required.
Configure the OAuth consent screen (Google Auth Platform). Go to APIs & Services → OAuth consent screen (this opens Google Auth Platform) and click Get started if prompted.
Branding: enter an app name (for example
Google Docs MCP), a user support email and a developer contact email, then save.Audience: choose External. This is the usual choice for a personal Gmail account; Google Workspace users may choose Internal. While the app's publishing status is Testing, open Test users, click Add users, and add your own Google account. Only test users can sign in.
Data access: click Add or remove scopes and add:
https://www.googleapis.com/auth/documentshttps://www.googleapis.com/auth/drive, orhttps://www.googleapis.com/auth/drive.fileif you setGOOGLE_DRIVE_SCOPE=drive.file
Then save.
Create the OAuth client ID. Go to Google Auth Platform → Clients (or APIs & Services → Credentials → Create credentials → OAuth client ID). Click Create client, set Application type to Desktop app, give it a name, and click Create.
Copy the credentials. Copy the Client ID and Client secret into
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETin your.env. Store the secret safely right away: the console may not show it again, and you may have to create a new secret.
Good to know:
"Google hasn't verified this app." While your app is unverified, Google shows this warning during sign-in. Because it is your own app, click Advanced → Go to App name (unsafe) and continue. Personal use does not need verification.
Refresh tokens expire after 7 days in Testing status. If the publishing status is Testing and you request the scopes above, Google expires the refresh token after 7 days. After that, tools fail with
AUTH_EXPIREDand you need to sign in again. To avoid weekly re-authentication, switch the app to In production under Audience. Unverified production apps still show the warning screen and are limited in the number of users, which is fine for personal use.A Desktop app client is required. A Web application client needs every redirect URI registered exactly and causes
redirect_uri_mismatch.
Authentication
Sign in once. The server stores a refresh token and renews access tokens automatically.
From a terminal (recommended the first time):
npm run auth # runs the TypeScript sources through tsx
# or, after `npm run build`:
node dist/index.js authThis prints a Google sign-in URL and tries to open it in your browser. Approve access, and the browser redirects to the local loopback address, which completes the sign-in. The link is valid for 5 minutes.
Other commands:
node dist/index.js status # show sign-in status, granted scopes and token expiry (never prints tokens)
node dist/index.js logout # revoke access at Google and delete the local token file
node dist/index.js --helpIf you install the package globally or with npm link, the same commands are available as google-docs-mcp auth, google-docs-mcp status and google-docs-mcp logout.
From the AI client: ask your assistant to sign in to Google. It calls the authenticate tool, which returns an authUrl (and tries to open it in your browser). Open the URL on the same computer that runs the server, approve access, then have the assistant call get_auth_status to confirm. The sign_out tool revokes access and deletes the stored tokens.
Where tokens are stored: in GOOGLE_TOKEN_PATH, which defaults to ~/.config/google-docs-mcp/tokens.json (or %APPDATA%\google-docs-mcp\tokens.json on Windows). The file is created with mode 0600 inside a 0700 directory and written atomically. If you change GOOGLE_DRIVE_SCOPE from drive.file to drive, sign in again: tools report NOT_AUTHENTICATED with details.missingScopes until you do.
Running
# Development: run the TypeScript sources with automatic restarts
npm run dev
# Production: compile once, then run the compiled server
npm run build
npm start # = node dist/index.jsThe server speaks MCP over stdio. It is normally started by your MCP client (see below), not by hand. When you run it manually it waits silently for an MCP client on stdin, and its logs appear on stderr. To explore the tools interactively, you can use the MCP Inspector:
npx @modelcontextprotocol/inspector node dist/index.jsMCP Client Configuration
Replace /abs/path/to/google-docs-mcp with the absolute path of your clone. On Windows, use forward slashes or escaped backslashes in JSON (C:/Users/you/google-docs-mcp/dist/index.js).
If your credentials are in <repo>/.env, you can leave out the env blocks below: the server loads .env from its package root.
Claude Desktop
Edit claude_desktop_config.json. Open it from Settings → Developer → Edit Config. It is located at:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"google-docs": {
"command": "node",
"args": ["/abs/path/to/google-docs-mcp/dist/index.js"],
"env": {
"GOOGLE_CLIENT_ID": "123456789012-abc123.apps.googleusercontent.com",
"GOOGLE_CLIENT_SECRET": "GOCSPX-your-client-secret"
}
}
}
}Restart Claude Desktop after saving.
Claude Code
claude mcp add google-docs \
-e GOOGLE_CLIENT_ID=123456789012-abc123.apps.googleusercontent.com \
-e GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret \
-- node /abs/path/to/google-docs-mcp/dist/index.jsAdd --scope project to share the server with your team through a project-level .mcp.json, or --scope user to make it available in all your projects. You can also write .mcp.json in the project root by hand:
{
"mcpServers": {
"google-docs": {
"type": "stdio",
"command": "node",
"args": ["/abs/path/to/google-docs-mcp/dist/index.js"],
"env": {
"GOOGLE_CLIENT_ID": "${GOOGLE_CLIENT_ID}",
"GOOGLE_CLIENT_SECRET": "${GOOGLE_CLIENT_SECRET}"
}
}
}
}${VAR} placeholders are expanded from your shell environment, so secrets stay out of the committed file. Check the server with claude mcp list or /mcp inside Claude Code.
VS Code (GitHub Copilot agent mode)
Create .vscode/mcp.json in your workspace. VS Code uses a top-level servers key:
{
"servers": {
"google-docs": {
"type": "stdio",
"command": "node",
"args": ["/abs/path/to/google-docs-mcp/dist/index.js"],
"envFile": "/abs/path/to/google-docs-mcp/.env"
}
}
}You can use "env": { ... } instead of envFile, or keep secrets out of the file with VS Code inputs ("type": "promptString", "password": true).
Cursor
Edit ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"google-docs": {
"command": "node",
"args": ["/abs/path/to/google-docs-mcp/dist/index.js"],
"env": {
"GOOGLE_CLIENT_ID": "123456789012-abc123.apps.googleusercontent.com",
"GOOGLE_CLIENT_SECRET": "GOCSPX-your-client-secret"
}
}
}
}Any other client that supports stdio MCP servers works the same way: run node /abs/path/to/google-docs-mcp/dist/index.js.
Available Tools
Every documentId parameter also accepts a full Google Docs URL (https://docs.google.com/document/d/<ID>/edit). Optional parameters are marked with ?.
Tool | Description | Key parameters |
| Report whether the server is signed in and has the required scopes. Never returns tokens. | – |
| Start the Google OAuth sign-in and return the |
|
| Revoke access at Google and delete the stored tokens. | – |
| Create a new, empty Google Doc and return its ID, title and URL. |
|
| Read a document: title, URL, plain text, |
|
| List Google Docs, most recently modified first, optionally filtered by name. |
|
| Move a document to the Drive trash (restorable for 30 days). Only Google Docs files are accepted. |
|
| Copy a document, with its content and formatting, under a new title. |
|
| Append text to the end of a document. |
|
| Insert text at a specific index. |
|
| Replace every occurrence of a text in the document. |
|
| Delete the text in |
|
| Apply character formatting to a range. Only the attributes you specify are changed. |
|
| Apply a named paragraph style to the paragraphs in a range. |
|
| Set paragraph alignment for a range. |
|
| Insert a page break at an index. |
|
| Insert an empty table at an index. |
|
| Turn a range of existing text into a hyperlink ( |
|
| Turn the paragraphs in a range into a list. |
|
| Search Google Docs by file name, full-text content, or both. |
|
| Find a phrase inside a document and return the exact start/end index of each match. |
|
Colors (foregroundColor, backgroundColor) are hex strings such as #1A73E8 or #fff.
Results and errors
Successful calls return:
{
"success": true,
"data": {
"documentId": "1AbC…",
"title": "FYP Proposal",
"url": "https://docs.google.com/document/d/1AbC…/edit"
}
}Failed calls set the MCP isError flag and return:
{
"success": false,
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "The Google Docs document could not be found or you do not have access to it.",
"retryable": false
}
}Some errors include a details object, for example the valid index range for INVALID_INDEX, or missingScopes.
Code | Meaning | Retryable |
| Not signed in, sign-in was cancelled, or the stored grant lacks required scopes. Call | no |
| The access token or refresh token expired or was revoked ( | no |
|
| no |
| Invalid configuration, a Google API not enabled in the Cloud project, the callback port in use, or an unreadable token file. | no |
| The document ID or URL is malformed. It is rejected before any request is sent. | no |
| The document doesn't exist, or you don't have access to it. | no |
| You can't perform this operation on the document, or the granted scopes don't allow it. | no |
| An index or range lies outside the document body. | no |
| Invalid tool arguments: wrong type or range, bad color or URL, not a Google Doc, and so on. | no |
| Google rejected the request (HTTP 400). The message includes Google's explanation. | no |
| Google API rate limit or quota exceeded. Wait and retry. | yes |
| Google APIs could not be reached, or the request timed out. | yes |
| Google returned an unexpected or server-side (5xx) error. | yes |
| An unexpected error inside the server. Details are only in the server logs. | no |
Resources and Prompts
Resource template
URI | Content |
| The document's current plain text ( |
Prompts. These are reusable instructions. The AI does the writing, and the server only provides Google Docs access.
Prompt | Purpose |
| Read a document and produce a summary. |
| Rewrite a document's content (for example in a different tone), applying the edits with the content tools. |
| Give a document a clean structure: headings, lists and consistent alignment. |
| Create a new meeting-notes document from a template. |
Understanding indexes
Index-based tools (insert_text, delete_text, format_text, set_paragraph_style, set_alignment, insert_page_break, insert_table, insert_link, create_bulleted_list) use Google Docs indexes:
The document body starts at index 1. Index 0 is a section break.
Indexes are UTF-16 code units, the same as JavaScript string offsets. Most characters count as 1, but emoji and other astral-plane characters count as 2. Tables, images and other objects also take up index positions.
Ranges are half-open,
[startIndex, endIndex). Valid insertion indexes are1 … bodyEndIndex - 1. The final newline of the body can never be deleted.Every insert or delete shifts all indexes after it. When you make several index-based edits, work from the end of the document towards the beginning, or re-read the document between edits.
Use
get_document(thestructureoutline andbodyEndIndex) orfind_textto get exact indexes. Don't guess them.append_textandreplace_textdon't need indexes at all.Indexes are validated against the current document before anything is sent to Google (
INVALID_INDEX). Writes use the document'srevisionIdastargetRevisionId. If a collaborator edits the document at the same moment, Google adjusts the indexes for you instead of applying them to the wrong text.
Search limitations
Google Drive has two different search modes, and they behave differently:
Mode | Used by | Behaviour |
Name search |
| Drive |
Full-text search |
| Drive |
Both |
| Either condition matches. |
Search results only include Google Docs that are not in the trash.
With
GOOGLE_DRIVE_SCOPE=drive.file, Drive only returns documents created or opened by this app.To locate text inside a known document, with exact indexes for editing, use
find_text.
Security
Least-privilege scopes. The server requests
https://www.googleapis.com/auth/documentsplus one Drive scope. SetGOOGLE_DRIVE_SCOPE=drive.fileto limit Drive operations (list, search, copy, trash) to files created or opened by this app. The defaultdrivescope is only needed to discover and manage all your existing documents.Secure token storage. Tokens live in a local JSON file created with mode
0600in a0700directory. The file is written atomically, never sent anywhere except Google, and deleted bysign_out/logout, which also revoke the grant at Google.PKCE + state. Sign-in uses the OAuth 2.0 authorization-code flow with PKCE (S256) and a random
statevalue that is compared in constant time. Forged callbacks are rejected.Loopback-only redirect. The redirect URI must be
http://127.0.0.1,localhostor[::1]with an explicit port. The temporary callback server only listens on that address, only for the duration of the sign-in (at most 5 minutes), and shuts down afterwards.No secret logging. Logs go to stderr only. Sensitive fields (
access_token,refresh_token,client_secret, authorization codes,Authorizationheaders and similar) are redacted. Token patterns inside messages are scrubbed too. Stack traces of library errors are never logged.Safe error messages. Tool errors carry a stable code and a human-readable message. They never include tokens, secrets, stack traces or file-system paths, and Google's messages are redacted before they are returned.
Trash, never delete.
delete_documentonly moves Google Docs files to the Drive trash, and refuses other file types. Nothing is permanently deleted.Concurrent-edit safety. Index-based writes are validated against the current document and sent with
targetRevisionId.Input validation. Every tool has a strict zod schema, and unknown arguments are rejected. Document IDs and URLs are checked against a strict pattern, which blocks path or query injection. Drive search input is escaped so it can't break out of the query. Link URLs must be
http,httpsormailto. Index ranges are checked before writing.No hard-coded credentials. Client ID and secret come only from the environment or
.env.
Testing
npm test # all unit tests (Google APIs are mocked; no credentials or network needed)
npm run test:watch # watch modeUnit tests replace the Google API clients and the OAuth client with fakes, and run the MCP tools through a real MCP client over an in-memory transport. Test runs point GOOGLE_TOKEN_PATH at a non-existent file, so they never touch your real tokens.
Optional integration tests against a real Google account. Use a dedicated test account, because these tests create and modify real documents.
# 1. Sign the test account in and store its tokens in a separate file
GOOGLE_TOKEN_PATH=/abs/path/test-account-tokens.json npm run auth
# 2. Run the integration tests with that token file
RUN_GOOGLE_INTEGRATION_TESTS=true \
GOOGLE_INTEGRATION_TOKEN_PATH=/abs/path/test-account-tokens.json \
npm run test:integrationWithout RUN_GOOGLE_INTEGRATION_TESTS=true, the integration tests are skipped. They also need GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (from the environment or .env). If GOOGLE_INTEGRATION_TOKEN_PATH is not set, they use the default token path. Every document they create is moved to the Drive trash afterwards.
Development scripts
Script | What it does |
| Compile TypeScript to |
| Run the server from source with |
| Run the compiled server ( |
| Sign in with Google from the terminal (runs from source). |
| Type-check sources and tests without emitting. |
| Run ESLint (strict, type-aware rules). |
| Run ESLint and fix what it can. |
| Format the code with Prettier. |
| Check formatting without writing. |
| Run the unit tests with Vitest. |
| Run Vitest in watch mode. |
| Run the optional real-account integration tests. |
Troubleshooting
Problem | Cause and fix |
| The OAuth client is not a Desktop app client (Web clients need exact registered redirect URIs), or |
| Your account is not a test user of an app in Testing status. Add it under Google Auth Platform → Audience → Test users. If you clicked Cancel on the consent screen, run the sign-in again. |
| The refresh token expired or was revoked. In Testing status, refresh tokens expire after 7 days. Changing your password or removing the app's access also revokes them. Run |
| Enable both APIs in APIs & Services → Library for the project that owns your OAuth client, wait a minute or two, and retry. |
| Another process (or another sign-in) is using the port. Close it, or pick another port, for example |
|
|
| The stored grant lacks a required scope, for example after switching |
Documents are missing or | With |
| The index is outside |
| Google API quota exceeded (per-minute limits). Read requests are retried automatically with backoff; wait a moment before retrying writes. You can see quotas under APIs & Services → Quotas. |
The client shows "invalid JSON" / the connection drops | stdout is reserved for MCP protocol messages. Don't add |
Server doesn't start | Check |
The sign-in link doesn't work | The |
Project structure
google-docs-mcp/
├── src/
│ ├── index.ts # CLI entry point: stdio server, `auth`, `status`, `logout`
│ ├── server.ts # Builds the McpServer and registers tools, resources, prompts
│ ├── config/config.ts # Environment configuration and validation
│ ├── auth/
│ │ ├── google-auth.ts # OAuth 2.0 (PKCE, loopback flow, refresh, sign-out)
│ │ └── token-manager.ts # Secure token file storage
│ ├── google/
│ │ ├── docs-client.ts # Google Docs API wrapper
│ │ ├── drive-client.ts # Google Drive API wrapper
│ │ ├── document-parser.ts # Index-accurate document parsing
│ │ └── retry.ts # Retry policy for idempotent requests
│ ├── services/ # Business logic (documents, content, formatting, structure, search)
│ ├── tools/ # MCP tool definitions (auth, documents, content, formatting, structure, search)
│ ├── resources/ # MCP resources (google-docs://document/{documentId})
│ ├── prompts/ # MCP prompts
│ ├── schemas/ # Shared zod schemas
│ ├── types/ # Shared TypeScript types
│ └── utils/ # Errors, validation, logging, URLs
├── tests/
│ ├── unit/ # Unit tests (mocked Google APIs)
│ ├── integration/ # Optional real-account tests
│ └── helpers/ # Fakes and the in-memory MCP test harness
├── .env.example
├── eslint.config.js
├── prettier.config.js
├── tsconfig.json
├── vitest.config.ts
├── package.json
├── LICENSE
└── README.mdLicense
MIT © Muhammad Ammar Qaisar