google-workspace-mcp
Send real emails, create drafts, and list drafts through the Gmail API.
List events, create, update, and delete calendar events, optionally with attendees.
Create files in Google Drive (limited to files the server created itself).
Read and write spreadsheet ranges by A1 notation.
Search videos, get video/channel metadata, manage playlists, post comments and replies, rate videos, and manage subscriptions.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
google-workspace-mcp
An MCP server that gives Claude write access to a personal Google account — Gmail, Drive, Calendar, Sheets, and YouTube — backed by a self-owned Google Cloud OAuth client rather than a hosted third-party connector. It can send real email, not just draft it.
23 tools across 5 Google APIs. Runs locally over stdio.
You: email me a summary of today's calendar
Claude: [calendar_list_events] → 4 events
[gmail_send_email] → deliveredWhy own the OAuth client
The hosted Google connectors are read-mostly. Making Claude act — send a real email, create a real invite, write a real spreadsheet cell — means holding the OAuth client yourself. That single decision drives most of what's interesting below.
Related MCP server: gsheets-mcp
Design notes
Least-privilege scopes, chosen to avoid a paid security review
Google classifies gmail.readonly and drive.readonly as Restricted scopes. Shipping an app that uses them requires a CASA security assessment — a paid, recurring third-party audit. That is not a sensible cost for a single-user personal tool, so this server requests none of them. For Gmail and Drive, that means it can act but not read:
Scope | Why |
| Send and draft. Never reads the inbox. |
| Can only touch files it created itself — cannot read pre-existing Drive content. |
| Calendar is not a restricted scope. |
| Read and write sheets by ID. |
| Playlists, subscriptions, comments, ratings. |
Reading arbitrary Gmail and Drive content is left to the existing claude.ai connectors. The two halves compose: read there, act here.
Arguments are untrusted, even though the caller is your own assistant
Every argument to these tools is model output, and a model that has just read a web page or an email can be steered by text it found there. A server that sends mail and writes spreadsheets has to treat its inputs the way a web server treats a form post:
Header injection. Subjects, addresses and attachment filenames are interpolated into the MIME message. A line break in any of them would start a new header —
Subject: Hi\r\nBcc: someone@example.comquietly copies the mail to a stranger — so line breaks are refused with an error rather than stripped. Non-ASCII subjects and filenames are encoded per RFC 2047 and RFC 2231 instead of being written raw.Formula injection. Sheets'
USER_ENTEREDmode parses a string as if it were typed, so=IMPORTDATA("https://…"&A1)becomes a live formula that can post the sheet's contents to any URL.sheets_write_rangewritesRAWby default; formula parsing is an explicitvalueInputOptionthe tool description tells the model never to use for text it read elsewhere.Bodies survive transport. Generated HTML is often one very long line, and SMTP caps lines at 998 octets. Every body part is base64-encoded so no relay has a reason to break it inside a tag.
Each of these has a test that builds the hostile input and asserts on the bytes that would go to Google.
Irreversible tools say so in their own descriptions
A tool description is the only thing the model reads before deciding to call it, so the warnings live there rather than in a confirmation layer the model can route around. From src/gmail.js:
Sends an email immediately from the authorized Gmail account, optionally with file attachments. This is a real send with no draft/review step and cannot be undone.
Every tool also declares MCP tool annotations — readOnlyHint, destructiveHint, idempotentHint — so a client can auto-approve the reads and prompt for the rest.
Several tools are externally visible under your real identity: gmail_send_email delivers to real inboxes, calendar_create_event emails invitations, and the YouTube comment tools post publicly. They are ordinary tools — but they're the ones where a mistake is visible to other people.
Calendar invitations are actually sent
Google's Calendar API emails no one unless the request says otherwise, so "invite alice@example.com" used to produce an event Alice never heard about unless she happened to use Google Calendar. The create, update and delete tools now send sendUpdates: "all" by default — matching what their descriptions promise — and take sendUpdates: "none" when a change shouldn't notify anybody.
The refresh token expires every ~7 days — this is expected
The OAuth client sits in Google's Testing publishing mode, and Google invalidates refresh tokens for testing-mode apps after 7 days of inactivity. Auth breaking after a week away is normal maintenance, not a bug:
npm run check-auth # is the stored token still good?
npm run authorize # re-consent in the browser; rewrites credentials/token.jsonMoving to "In production" would remove the expiry but triggers exactly the verification review the scope choices above were made to avoid. For a single-user setup, periodic re-authorization is the cheaper trade.
The consent flow uses PKCE and a state check, and its callback listener binds to loopback only.
Refreshed tokens are persisted
google-auth-library emits a tokens event when it silently refreshes an access token. src/auth.js listens and merges the result back to disk, so a long-lived session doesn't lose its refresh token across restarts.
Setup
Requires Node 22+.
1. Create the OAuth client
In the Google Cloud Console, create a project.
Enable the Gmail, Google Calendar, Google Drive, Google Sheets, and YouTube Data v3 APIs.
Under APIs & Services → OAuth consent screen: External, publishing status Testing, and add your own Google account under Test users.
Under Credentials, create an OAuth client ID of type Desktop app.
Download the JSON to
credentials/client_secret.json.
credentials/ is git-ignored. See credentials/client_secret.example.json for the expected shape.
2. Authorize
npm ci
npm run authorizeOpens a loopback listener on localhost:53682, prints a consent URL, and writes the refresh token to credentials/token.json. Google shows an "unverified app" warning — expected for a Testing-status client only you can use.
3. Register with your client
Claude Code:
claude mcp add google-workspace --scope user -- node /absolute/path/to/google-workspace-mcp/src/index.jsClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"google-workspace": {
"command": "node",
"args": ["/absolute/path/to/google-workspace-mcp/src/index.js"]
}
}
}Tools
Gmail
Tool | Effect | Purpose |
| writes | Send a real message — delivered immediately, not a draft |
| writes | Compose without sending |
| read-only | List existing drafts |
Calendar
Tool | Effect | Purpose |
| read-only | Events in a time range |
| writes | Create an event; emails attendees unless |
| writes | Modify an existing event |
| destructive | Remove an event and send cancellations |
Drive / Sheets
Tool | Effect | Purpose |
| writes | Create a file in Drive |
| read-only | Read an A1-notation range |
| destructive | Overwrite an A1-notation range ( |
YouTube
Tool | Effect | Purpose |
| read-only | Search videos, channels, playlists |
| read-only | Metadata and statistics |
| read-only | Playlist contents |
| read-only | Comment threads, subscriptions |
| writes | Build playlists |
| writes | Post publicly |
| writes | Like / dislike / clear, subscribe |
The exact schemas the model sees are in mcp-surface.json.
Development
npm test # unit tests — no network, no credentials
npm run lint # ESLint
npm run surface # start the server and diff its tool surface against mcp-surface.json
npm run surface:update # re-record mcp-surface.json after an intended changeThe tests register every tool, assert the MIME builder's output byte-for-byte where it matters (header injection, encoded words, line lengths, boundaries), and check the requests that would go to Google — sendUpdates, valueInputOption, timeouts — by handing the API clients an auth object that records each request instead of sending it.
npm run surface runs mcp-surface against the real server, the way a client connects. CI fails if the advertised tools differ from the committed snapshot in any way, so a renamed tool, a reworded description or a dropped annotation has to show up as a reviewed diff. It also holds the definitions to a 16 KB budget, since they are resent on every request.
Layout
src/auth.js OAuth client construction, token load/refresh persistence
src/shared.js version, request timeout, tool-result helpers
src/gmail.js MIME assembly (headers, encoding, attachments) + gmail_* tools
src/calendar.js calendar_* tools
src/drive.js drive_* tools
src/sheets.js sheets_* tools
src/youtube.js youtube_* tools
src/index.js McpServer construction + stdio transport
scripts/authorize.js OAuth consent flow (PKCE, state, loopback-only listener)
scripts/check-token.js token health check
scripts/smoke-test.js live end-to-end send to the account's own address
test/tools.test.js offline tests
mcp-surface.json the committed tool surface CI compares againstAuth files
Both live in credentials/, which is git-ignored and checked by CI:
client_secret.json— the OAuth client, downloaded from Google Cloud Consoletoken.json— access + refresh tokens, written bynpm run authorize
Neither has ever been committed. If credentials/ is lost, re-download the client secret and re-run npm run authorize.
Notes
Each Google API comes from its own
@googleapis/*package rather than the monolithicgoogleapis, which is now over 240 MB because it bundles every Google API there is. Five APIs need about 7 MB;node_moduleswent from 282 MB to 45 MB.Every Google request has a 30-second timeout, so a stalled connection fails the tool call with an error instead of hanging it.
Scopes are requested up front at authorize time. Adding a new API later means re-running
npm run authorizeso the new scope is granted.
Privacy policy
Google requires a published policy on the consent screen even for a Testing-status app: brettadams0.github.io/google-workspace-mcp-policy (source).
License
MIT — see LICENSE. Changes are recorded in CHANGELOG.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Hosted YouTube MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Your Gmail, Calendar, Drive, GitHub, Oura, wallet and confirmed profile facts in any MCP client.
Hosted X (Twitter) MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA self-hosted MCP server providing Claude with authenticated access to Google Calendar, Gmail, Drive, Docs, Sheets, and Slides via 43 tools and secure OAuth2.1,945 npmMIT
- AlicenseNot gradedqualityDmaintenanceA local MCP server that lets Claude read and write Google Sheets through the Google Sheets API v4, using OAuth2 authentication with your own Google account.23 npm3MIT
- AlicenseBqualityDmaintenanceMCP server that gives Claude full read-write access to Google Drive, Docs, Sheets, and Slides using your own Google OAuth credentials and hosted server.25MIT
- FlicenseNot gradedqualityCmaintenanceA local MCP server that connects multiple Google accounts to Claude Desktop, enabling unified email search and send, calendar events, and full CRUD on Google Docs, Sheets, and Forms.-