Gmail Streamable MCP Server
Provides tools for managing Gmail inbox, including searching threads, reading messages, managing drafts, modifying labels, and sending emails.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Gmail Streamable MCP ServerGive me an overview of my inbox"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Gmail MCP Server
Fetch-native MCP server for Gmail — search threads, read messages, manage drafts, and organize your inbox on Bun or Cloudflare Workers.
Author: overment
This branch targets thecandidate 2026-07-28 protocol using exact @modelcontextprotocol/server@2.0.0-beta.5 and @modelcontextprotocol/client@2.0.0-beta.5. These packages are prereleases. Do not claim final 2026-07-28 conformance until the dated specification and stable packages are published and the final-delta validation passes.
You connect this server to your MCP client at your own responsibility. Language models can make mistakes or perform unintended actions. Review tool outputs, verify writes in Gmail, and prefer small, incremental changes. Remote deployments still need TLS, rate limiting, audit logging, secret management, and compliance with Google OAuth policies.
The same tool and OAuth product behavior is available in two runtimes:
Bun with the MCP resource server on
PORTand OAuth proxy onPORT + 1Cloudflare Workers with MCP, discovery, and OAuth proxy routes on one origin
Motivation
Gmail's API is powerful but not LLM-friendly out of the box. This server focuses on:
Let LLMs understand inbox state in a single action (
inbox_overview) instead of multiple queriesProvide enriched search results with subject, sender, date — not just thread IDs
Support batch operations (
modify_threadhandles up to 100 threads at once)Map API responses into human-readable feedback useful for both LLM and user
Safer write flow: drafts first, send explicitly
In short, it's not a direct mirror of Gmail's API — it's tailored so AI agents know exactly how to use it effectively.
Related MCP server: Gmail MCP Server
Features
✅ Overview — Get inbox stats + highlights (unread, starred, recent threads)
✅ Search — Find threads with Gmail query syntax, enriched results
✅ Read — Get full threads and messages with body content
✅ Labels — Discover label IDs for filtering and organizing
✅ Modify — Batch archive, star, mark read/unread (up to 100 threads)
✅ Drafts — Create, update, and send drafts with reply threading
✅ OAuth 2.1 — CIMD + PKCE proxy with opaque MCP resource-token mapping
✅ Credential separation — MCP bearer tokens are never sent to Gmail; provider refresh tokens stay in storage
✅ Dual Runtime — Bun and Cloudflare Workers
✅ MCP v2 candidate — Fetch-native
2026-07-28with SDK stateless legacy fallback
Design Principles
LLM-friendly: Tools are simplified, not 1:1 Gmail API mirrors
Discovery-first:
inbox_overviewandlist_labelshelp avoid guessingBatch-first:
modify_threadaccepts arrays to minimize tool callsSafer writes: Drafts first, send explicitly
Clear feedback: Summaries with structured content and next steps
Installation
Prerequisites: Bun 1.2+, a Google account, and a Gmail-enabled Google Cloud project. Cloudflare deployment also requires a Cloudflare account and Wrangler 4.
Ways to Run (Pick One)
Local + OAuth (recommended)
Cloudflare Worker (wrangler dev) — Local Worker testing
Cloudflare Worker (deploy) — Remote production
1. Local + OAuth (Recommended)
Go to Google Cloud Console
Create a project and enable the Gmail API
Create OAuth 2.0 Client ID (Web application)
Set redirect URIs:
http://127.0.0.1:3001/oauth/callback alice://oauth/callbackCopy Client ID and Secret
cd gmail-mcp
bun install
cp env.example .envEdit .env:
PORT=3000
AUTH_ENABLED=true
AUTH_STRATEGY=oauth
PROVIDER_CLIENT_ID=your-client-id.apps.googleusercontent.com
PROVIDER_CLIENT_SECRET=your-client-secret
PROVIDER_ACCOUNTS_URL=https://accounts.google.com
OAUTH_AUTHORIZATION_URL=https://accounts.google.com/o/oauth2/v2/auth
OAUTH_TOKEN_URL=https://oauth2.googleapis.com/token
OAUTH_REVOCATION_URL=https://oauth2.googleapis.com/revoke
OAUTH_SCOPES=https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.compose https://www.googleapis.com/auth/gmail.modify
OAUTH_REDIRECT_URI=alice://oauth/callback
OAUTH_REDIRECT_ALLOWLIST=alice://oauth/callback,http://127.0.0.1:3001/oauth/callback
OAUTH_EXTRA_AUTH_PARAMS=access_type=offline&prompt=consentRun:
bun dev
# MCP: http://127.0.0.1:3000/mcp
# OAuth: http://127.0.0.1:3001Tip: The Authorization Server runs on PORT + 1.
2. Cloudflare Worker (Local Dev)
Create an ignored .dev.vars for local Worker secrets:
PROVIDER_CLIENT_ID=your-client-id.apps.googleusercontent.com
PROVIDER_CLIENT_SECRET=your-client-secret
RS_TOKENS_ENC_KEY=your-base64url-32-byte-keyThen run:
bun run dev:workerEndpoint: http://127.0.0.1:8787/mcp
3. Cloudflare Worker (Deploy)
Create KV namespace:
bun x wrangler kv namespace create TOKENSReplace the placeholder URLs, host allowlist, and KV namespace ID in
wrangler.jsonc.Set secrets:
bun x wrangler secret put PROVIDER_CLIENT_ID
bun x wrangler secret put PROVIDER_CLIENT_SECRET
# Generate encryption key (32-byte base64url):
openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'
bun x wrangler secret put RS_TOKENS_ENC_KEYNote:
RS_TOKENS_ENC_KEYencrypts OAuth tokens stored in KV (AES-256-GCM).
Update the redirect URI and allowlist in
wrangler.jsonc.Add Workers URL to your Google OAuth app's redirect URIs
Deploy:
bun x wrangler deployEndpoint: https://<worker-name>.<account>.workers.dev/mcp
Client Configuration
MCP Inspector (quick test):
bunx @modelcontextprotocol/inspector
# Connect to: http://localhost:3000/mcpClaude Desktop / Cursor:
{
"mcpServers": {
"gmail": {
"command": "bunx",
"args": ["mcp-remote", "http://127.0.0.1:3000/mcp", "--transport", "http-only"],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}For Cloudflare, replace URL with https://<worker-name>.<account>.workers.dev/mcp.
Token and request flow
OAuth authorization state is product state, not an MCP transport session. It remains in TokenStore implementations while every MCP request receives a fresh SDK server.
sequenceDiagram
participant C as MCP client
participant AS as Gmail MCP OAuth proxy
participant G as Google OAuth
participant S as TokenStore (file/KV + memory)
participant R as /mcp resource server
participant API as Gmail API
C->>AS: GET /authorize (CIMD client_id, PKCE challenge)
AS->>S: Save short-lived authorization transaction
AS->>G: Redirect to Google authorization
G->>AS: GET /oauth/callback (provider code)
AS->>G: Exchange code for Google access + refresh tokens
AS->>S: Keep provider tokens in authorization transaction
AS->>C: Redirect with one-time proxy authorization code
C->>AS: POST /token (code + PKCE verifier)
AS->>S: Store opaque MCP access/refresh -> provider-token record
AS-->>C: Opaque MCP access + refresh tokens
C->>R: MCP request with opaque MCP bearer
R->>S: Validate record; refresh provider access if near expiry
S-->>R: Provider access token only
R->>API: Gmail request with provider access token
API-->>R: Gmail response
R-->>C: MCP tool resultTrust boundary rules:
The inbound MCP bearer is retained only as
AuthInfo.tokenby the SDK auth boundary; tools cannot read or forward it. OAuth mode always requires a valid opaque RS record; the older permissiveAUTH_REQUIRE_RSandAUTH_ALLOW_DIRECT_BEARERtoggles were removed.The current Gmail access token is exposed to tools only as
AuthInfo.extra.gmailAccessTokenthrough the project-specific context adapter.Google refresh tokens never enter
AuthInfo, tool context, MCP content, or structured output./authorize,/oauth/callback,/token,/revoke,/register, and discovery routes are dispatched before and outside MCP handling.Modern requests and legacy fallback are stateless and never create
Mcp-Session-Id.
Tools
get_profile
Get the connected Gmail account email. Call to confirm which account is active.
// Input
{}
// Output
{ email: "user@gmail.com" }inbox_overview
Get inbox stats + highlights for a time range. Call this first for a quick summary.
// Input
{
days?: number; // 1-365, default: 7
}
// Output
{
period: "last 7 days",
counts: { total, unread, inbox, sent, starred, important? },
highlights?: {
recentUnread: Array<{ id, subject?, from? }>,
starred: Array<{ id, subject?, from? }>
},
meta?: { nextSteps? }
}list_labels
Discover label IDs and names. Use before filtering by labelIds.
// Input
{}
// Output
{
items: Array<{ id, name, type?, messagesTotal?, threadsTotal? }>,
meta?: { nextSteps?, relatedTools? }
}search_threads
Search threads with Gmail query syntax. Returns enriched results.
// Input
{
query?: string; // Gmail search: "from:alice newer_than:7d"
labelIds?: string[];
includeSpamTrash?: boolean;
limit?: number; // 1-50, default: 25
cursor?: string;
}
// Output
{
items: Array<{
id, subject?, from?, date?, snippet?,
messageCount?, isUnread?, webUrl?
}>,
pagination?: { hasMore, nextCursor?, itemsReturned, limit },
meta?: { nextSteps?, hints?, relatedTools? }
}get_thread
Get a full thread with all messages.
// Input
{
threadId: string;
format?: "minimal" | "metadata" | "full" | "raw";
metadataHeaders?: string[];
maxBodyChars?: number;
}
// Output
{
thread: { id, historyId?, messageCount, messages: [...], webUrl? },
meta?: { nextSteps?, relatedTools? }
}get_message
Fetch a single message with full content.
// Input
{
messageId: string;
format?: "minimal" | "metadata" | "full" | "raw";
metadataHeaders?: string[];
maxBodyChars?: number;
}
// Output
{
message: { id, threadId?, snippet?, headers?, body?, webUrl? },
meta?: { nextSteps?, relatedTools? }
}modify_thread
Batch add/remove labels on threads (up to 100). Supports convenience actions.
// Input
{
threadIds: string[]; // 1-100 thread IDs
addLabelIds?: string[];
removeLabelIds?: string[];
actions?: {
archive?: boolean; // Remove INBOX
unarchive?: boolean; // Add INBOX
markRead?: boolean; // Remove UNREAD
markUnread?: boolean; // Add UNREAD
star?: boolean; // Add STARRED
unstar?: boolean; // Remove STARRED
trash?: boolean;
untrash?: boolean;
};
}
// Output
{
results: Array<{ threadId, success, error? }>,
summary: { total, succeeded, failed },
applied: { addLabelIds?, removeLabelIds? },
meta?: { nextSteps?, relatedTools? }
}create_draft
Create a draft from structured fields or raw MIME.
// Input
{
to?: string | string[]; // Required unless raw provided
cc?: string | string[];
bcc?: string | string[];
subject?: string;
text?: string;
html?: string;
threadId?: string; // For replies
inReplyTo?: string; // Message-ID for threading
raw?: string; // base64url RFC 2822
}
// Output
{
draft: { id, messageId?, threadId?, snippet? },
meta?: { nextSteps?, relatedTools? }
}update_draft
Replace a draft's content (Gmail drafts are immutable internally).
// Input
{
draftId: string;
to?: string | string[];
cc?: string | string[];
bcc?: string | string[];
subject?: string;
text?: string;
html?: string;
threadId?: string;
raw?: string;
}send_draft
Send a draft. Optionally update it before sending.
// Input
{
draftId: string;
to?: string | string[]; // Override before send
cc?: string | string[];
bcc?: string | string[];
subject?: string;
text?: string;
html?: string;
threadId?: string;
raw?: string;
}
// Output
{
sent: { id, threadId?, labelIds?, snippet?, webUrl? },
meta?: { nextSteps?, relatedTools? }
}Examples
1. Get inbox summary
{ "name": "inbox_overview", "arguments": { "days": 7 } }Response:
Inbox (last 7 days): 42 unread, 156 inbox, 12 sent, 3 starred
Recent unread:
Alice: Meeting tomorrow at 3pm
GitHub: PR merged in project-x
Starred:
Boss: Q4 Planning document2. Search for unread emails from a sender
{
"name": "search_threads",
"arguments": {
"query": "from:alice@example.com is:unread newer_than:7d",
"limit": 10
}
}3. Read a thread
{
"name": "get_thread",
"arguments": {
"threadId": "19be18067165251d",
"format": "full"
}
}4. Archive multiple threads
{
"name": "modify_thread",
"arguments": {
"threadIds": ["19be18067165251d", "19be17f8a2c3b4d5"],
"actions": { "archive": true, "markRead": true }
}
}Response:
Modified 2/2 threads. -INBOX -UNREAD5. Reply to a thread (draft first)
{
"name": "create_draft",
"arguments": {
"threadId": "19be18067165251d",
"to": "alice@example.com",
"text": "Thanks, I'll be there!"
}
}{
"name": "send_draft",
"arguments": { "draftId": "r8651610029774" }
}HTTP Endpoints
Endpoint | Method | Purpose |
| POST | MCP JSON-RPC 2.0 |
| GET / DELETE |
|
| GET | Health check |
| GET | OAuth AS metadata |
| GET | RFC 9728 OAuth protected-resource metadata |
| GET | Backward-compatible OAuth RS metadata alias |
| GET | OAuth authorization-server metadata |
OAuth proxy (PORT + 1 on Bun; same origin on Workers):
GET /authorize— Start OAuth flowGET /oauth/callback— Provider callbackPOST /token— Token exchangePOST /revoke— Revoke tokens
Development
bun dev # Bun MCP + OAuth proxy
bun run typecheck # Bun and Worker TypeScript checks
bun run test # Protocol, OAuth, provider, and storage tests
bun run lint # Biome check
bun run format:check # Formatting check
bun run build # Bun bundle
bun run build:worker # Wrangler dry-run bundle
bun run types:worker:check # Generated binding type check
bun start # Run Bun production entryFor an actual local workerd protocol check, start wrangler.test.jsonc and run the official-client probe in another terminal:
bunx wrangler dev --config wrangler.test.jsonc --env-file wrangler.types.env
bun run test:workerd-clientArchitecture
src/
├── shared/
│ ├── tools/
│ │ └── gmail/ # Gmail tools shared by Bun and Workers
│ │ ├── get-profile.ts
│ │ ├── inbox-overview.ts
│ │ ├── list-labels.ts
│ │ ├── search-threads.ts
│ │ ├── get-thread.ts
│ │ ├── get-message.ts
│ │ ├── modify-thread.ts
│ │ ├── create-draft.ts
│ │ ├── update-draft.ts
│ │ └── send-draft.ts
│ ├── oauth/ # OAuth flow (PKCE, discovery)
│ └── storage/ # Token storage (file, KV, memory)
├── core/ # Fresh-server MCP v2 factory/runtime
├── http/ # Fetch-native security, auth gate, and routing
├── services/
│ └── gmail.ts # Unchanged Gmail API client behavior
├── schemas/ # Complete Zod 4 input/output schemas
├── index.ts # Bun dual-port entry
└── worker.ts # Workers entryCandidate status, storage compatibility, and rollback
Candidate baseline: protocol
2026-07-28, server/client2.0.0-beta.5, Zod 4. This is not the final-release gate.Pre-migration repository baseline:
48d4ca49f3dba5621bb739608bcb2e483f1c14d6.FileTokenStoreremains version 1 and reads the existing plaintext or whole-file AES-256-GCM representation.Worker KV keys and values are unchanged:
rs:access:*,rs:refresh:*,txn:*,code:*, andsession:*; optional AES-GCM wrapping is unchanged.No migration rewrites, deletes, or invalidates provider refresh tokens or OAuth records; existing FileTokenStore expiry and provider-refresh behavior is preserved.
Roll back application code by redeploying the recorded baseline (or reverting the migration changes) without clearing
.data, KV, MCP resource tokens, or Google refresh tokens. Both versions can read the same stored records.
Troubleshooting
Issue | Solution |
"Unauthorized" | Complete OAuth flow again; refresh token may be revoked. |
"Invalid Credentials" | Ensure OAUTH_SCOPES match your Google app and user consent. |
"Insufficient Permission" | Add |
"Rate Limit Exceeded" | Slow down requests; use smaller limits. |
"Thread not found" | Thread IDs expire; search again to get fresh IDs. |
Draft update fails | Drafts are immutable; updates replace the underlying message. |
OAuth does not start (Worker) |
|
Empty search results | Check query syntax; use |
KV namespace error | Run |
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceA high-performance MCP server that enables AI assistants to interact with Gmail securely via OAuth 2.0, supporting smart email retrieval and thread-aware drafting.Last updated1MIT
- Alicense-qualityCmaintenanceA Model Context Protocol server for Gmail that lets AI assistants read, send, search, label, and filter Gmail through natural language, with OAuth2 auto-authentication and full attachment support.Last updated2023MIT
- Alicense-qualityDmaintenanceStreamable HTTP MCP server for Gmail enabling search, read, draft management, and inbox organization.Last updated24ISC
- Flicense-qualityDmaintenanceEnables AI agents to interact with Gmail through a standardized MCP server interface, allowing for natural language email management and automation.Last updated
Related MCP Connectors
Shipmail MCP server for AI agent custom-domain email inboxes with REST API and webhooks.
Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.
Hosted email MCP for AI agents with inboxes, send/receive, memory, recovery, and credits.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/iceener/gmail-streamable-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server