A Model Context Protocol (MCP) server that provides read-only Gmail access to Claude CLI with label-based filtering and container deployment support.
- Security first: Read-only access means Claude cannot send, delete, or modify your emails. Label filtering lets you restrict which emails are visible.
- Summarize important information: Let Claude read and summarize emails so you can quickly understand what matters.
- Never miss important emails: Avoid overlooks by having Claude search and highlight critical information buried in your inbox.
- Save time: Quickly find and understand email content without manual searching through hundreds of messages.
Quick start:
-
Configure Claude CLI to trust your certificate (if self-signed):
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem -
Add the server:
claude mcp add --transport http gmail https://localhost:3000/mcp?allowed_labels=INBOX
Then ask Claude: "Summarize my unread emails from today" or "Find all emails about the project deadline"
Pro tip: Create a custom slash command for frequent tasks. Add to .claude/commands/email-summary.md:
Summarize unread emails from last 24 hours. Group by sender and highlight action items.
Then use /email-summary anytime.
For self-signed or internal CA certificates, configure Claude CLI to trust them:
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pemAlternatively, add to ~/.claude/settings.json:
{
"env": {
"NODE_EXTRA_CA_CERTS": "/path/to/ca-cert.pem"
}
}Note: The settings.json env configuration may not be applied due to a potential Claude CLI bug. If you experience certificate errors, use the shell environment variable approach instead.
claude mcp add --transport http gmail https://localhost:3001/mcp?allowed_labels=INBOX,STARREDLabel filtering via URL: Configure which Gmail labels Claude can access using the allowed_labels query parameter. Use comma-separated label names (e.g., INBOX,STARRED,IMPORTANT). This parameter is required.
Claude CLI configuration is stored at ~/.claude.json:
{
"mcpServers": {
"gmail": {
"transport": "http",
"url": "https://localhost:3001/mcp?allowed_labels=INBOX,STARRED"
}
}
}-
Ensure the service is running:
curl http://localhost:3001/health
Expected response:
{"status":"ok"} -
Add the server (see above)
-
Authenticate:
- Start Claude Code:
claude - Run
/mcpcommand - Select the gmail server
- Choose "Authenticate"
- Complete Google OAuth in browser
- Start Claude Code:
-
Verify tools are available: Ask Claude: "What Gmail tools do you have?"
Claude should list three Gmail tools:
list_messagesread_messagesearch_messages
Lists Gmail messages filtered by allowed labels.
Parameters:
maxResults(number, optional): Maximum number of messages to return (default: 10)pageToken(string, optional): Page token for pagination
Returns:
- Array of message summaries containing:
id: Message IDthreadId: Thread IDsnippet: Message previewlabelIds: Array of label IDsfrom: Sender email addresssubject: Email subjectdate: Send date
Reads the full content of a specific message.
Parameters:
messageId(string, required): The ID of the message to readmaxSize(number, optional): Maximum body size in characters. If exceeded, body is truncated.
Returns:
- Full message object containing:
id: Message IDthreadId: Thread IDlabelIds: Array of label IDsfrom: Sender email addressto: Recipient email addresssubject: Email subjectdate: Send datebody: Full email body (Markdown format)bodySize: Original body size in characterstruncated: Whether body was truncatedsnippet: Message preview
Note: This tool validates that the message has at least one allowed label before returning content. HTML-only emails are automatically converted to Markdown for better readability and token efficiency.
Searches messages using Gmail query syntax.
Parameters:
query(string, required): Gmail search query (e.g., "from:example@gmail.com subject:invoice")maxResults(number, optional): Maximum number of messages to return (default: 10)pageToken(string, optional): Page token for pagination
Returns:
- Array of matching message summaries (same format as
list_messages)
Note: Label filtering is automatically applied to search results.
User: List my 5 most recent Gmail messages
Claude: [Uses list_messages tool with maxResults: 5]
Claude will return a list of 5 messages showing ID, subject, sender, and date.
User: Read the full content of message ID 18c2f4a3b5e91234
Claude: [Uses read_message tool with messageId: "18c2f4a3b5e91234"]
Claude will return the full email content including headers and body.
User: Search my Gmail for emails from support@example.com
Claude: [Uses search_messages tool with query: "from:support@example.com"]
Claude will return all matching messages from that sender (filtered by allowed labels).
User: Find emails from last week with "invoice" in the subject
Claude: [Uses search_messages with query: "after:2025/11/23 subject:invoice"]
Claude will return matching messages using Gmail's search syntax.
Label filtering allows you to control which emails Claude can access by specifying a whitelist of allowed labels.
Label filtering is configured per-user via the MCP server URL query parameter allowed_labels:
claude mcp add --transport http gmail http://localhost:3001/mcp?allowed_labels=INBOX,STARREDOr in Claude CLI config:
{
"mcpServers": {
"gmail": {
"transport": "http",
"url": "http://localhost:3001/mcp?allowed_labels=INBOX,STARRED"
}
}
}System Labels (uppercase):
INBOX- InboxSENT- Sent mailDRAFT- DraftsTRASH- TrashSPAM- SpamIMPORTANT- ImportantSTARRED- StarredUNREAD- Unread
Category Labels:
CATEGORY_PERSONALCATEGORY_SOCIALCATEGORY_PROMOTIONSCATEGORY_UPDATESCATEGORY_FORUMS
Custom Labels:
Use your custom label names directly (e.g., Work, Personal). The server resolves names to IDs automatically.
Label filtering is applied at three levels:
- list_messages: Only returns messages that have at least one allowed label
- read_message: Validates the message has an allowed label before returning content
- search_messages: Automatically adds label filter to the search query
The allowed_labels parameter is required on /mcp endpoint. If empty or missing, returns 400 error.
Allow only inbox and starred:
http://localhost:3001/mcp?allowed_labels=INBOX,STARRED
Allow inbox and a custom label:
http://localhost:3001/mcp?allowed_labels=INBOX,Work
Allow work-related categories:
http://localhost:3001/mcp?allowed_labels=INBOX,IMPORTANT,CATEGORY_UPDATES
To change allowed_labels, update the URL in Claude CLI config:
Option 1: Edit config file
Edit ~/.claude.json and modify the URL:
{
"mcpServers": {
"gmail": {
"url": "https://localhost:3001/mcp?allowed_labels=INBOX,NEW_LABEL"
}
}
}Option 2: Remove and re-add server
claude mcp remove gmail
claude mcp add --transport http gmail https://localhost:3001/mcp?allowed_labels=INBOX,NEW_LABEL"Not authenticated" error:
Solution: Re-authenticate via Claude CLI:
- Run
/mcpcommand in Claude Code - Select the gmail server
- Choose "Authenticate"
- Complete Google OAuth in browser
"403 Forbidden" error:
Solution: Check that you've enabled the Gmail API and configured the correct OAuth scope (gmail.readonly).
"Quota exceeded" error:
Solution: Check your Gmail API usage in Google Cloud Console:
- Go to https://console.cloud.google.com
- Select your project
- Navigate to "APIs & Services" > "Dashboard"
- Check Gmail API quotas
Default quota is 1 billion units per day, which should be sufficient for personal use.
"Connection refused" error:
Solution: Verify the service is running:
curl http://localhost:3000/health"Timeout" error when token invalid:
Claude CLI may report a timeout instead of authentication error when the MCP server returns 401 (token decryption failed or refresh token revoked).
Solution: Re-authenticate via Claude CLI:
- Run
/mcpcommand in Claude Code - Select the gmail server
- Choose "Authenticate"
- Complete Google OAuth in browser
Tools not appearing in Claude CLI:
Solution:
- Verify the config.json format is correct
- Check that the URL is accessible:
curl http://localhost:3000/health - Restart Claude CLI
- Check Claude CLI logs for errors
The following sections are for administrators deploying and managing the MCP server.
- Stateless architecture - Encrypted bearer tokens (JWE) with no server-side session storage
- Multi-tenant OAuth 2.0 - Multiple clients with separate Gmail accounts
- Server-side Google callback - Google redirects to server, not directly to client
- Per-user label filtering - Each user configures their own allowed labels
- Transparent token refresh - Automatic Google token refresh on each request
- Three MCP tools: list messages, read message content, search messages
- Structured logging - Pino logger with JSON output in production
- Unit tests - Vitest test suite for auth modules
- HTTPS transport for secure remote deployment
- Docker ready with health checks
The server uses a stateless architecture where Google OAuth tokens are encrypted into a JWE (JSON Web Encryption) bearer token returned to Claude CLI. On each request, the server decrypts the token, refreshes the Google access token if expired, and executes Gmail API calls.
Three MCP tools are available:
list_messages: List Gmail messages filtered by allowed labelsread_message: Read full content of a specific messagesearch_messages: Search messages using Gmail query syntax
All communication happens over HTTPS. Credentials and encryption keys are loaded from files specified via environment variables.
- Node.js >= 22.0.0
- Docker (for containerized deployment)
- Gmail account
- Google Cloud Console account (free tier sufficient)
- Claude CLI installed
- Navigate to https://console.cloud.google.com
- Click the project dropdown in the top navigation bar
- Click "New Project"
- Enter project name: "Gmail MCP Server" (or any name you prefer)
- Note the Project ID (must be globally unique)
- Click "Create"
Note: The free tier is sufficient for personal use.
- In Google Cloud Console, ensure your project is selected
- Navigate to "APIs & Services" > "Library" (left sidebar)
- Search for "Gmail API"
- Click on "Gmail API" from the results
- Click the "Enable" button
- Wait for the API to be enabled (~30 seconds)
Note: There's no cost for enabling the API; it uses pay-per-use pricing.
- Navigate to "APIs & Services" > "OAuth consent screen"
- Select user type:
- Choose "External" if using a personal Gmail account
- Choose "Internal" if using Google Workspace
- Click "Create"
- Fill in the App information:
- App name: "Gmail MCP Server"
- User support email: Your email address
- Developer contact: Your email address
- Click "Save and Continue"
- Add scopes:
- Click "Add or Remove Scopes"
- Search for "gmail.readonly"
- Check the box for:
https://www.googleapis.com/auth/gmail.readonly - Click "Update"
- Click "Save and Continue"
- Add test users:
- Click "Add Users"
- Enter your Gmail address
- Click "Add"
- Click "Save and Continue"
- Review the summary and click "Back to Dashboard"
Important: The read-only scope (gmail.readonly) is the most secure option and is all that's needed.
- Navigate to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth client ID"
- If prompted to configure the consent screen, it should already be done
- Application type: Select "Web application"
- Name: "Gmail MCP Server"
- Add Authorized redirect URI: The URL where your MCP server's callback endpoint is accessible
- This must be the actual URL reachable from user's browser after Google login
- Production:
https://mcp.example.com/callback - Local testing:
http://localhost:3000/callback
- Click "Create"
- Click "Download JSON" to download credentials file
- Save the file as
credentials.jsonin yourcredentials/directory
Important:
- The redirect URI in credentials must exactly match what's configured in Google Cloud Console
- Keep credentials secure and never commit them to version control
The downloaded file format:
{
"web": {
"client_id": "xxx.apps.googleusercontent.com",
"client_secret": "xxx",
"redirect_uris": ["https://mcp.example.com/callback"]
}
}For Docker deployment, mount this file as a volume or use container secrets.
If you have this project locally, navigate to the project directory:
cd /path/to/email-mcpnpm installThe server implements the MCP Authorization Specification using OAuth 2.1 with PKCE. This creates a two-tier OAuth flow: Claude CLI authenticates with the Gmail MCP Server, which in turn authenticates with Google.
┌─────────────┐ ┌─────────────────┐ ┌────────────────┐
│ Claude CLI │────▶│ Gmail MCP Server│────▶│ Google OAuth │
└─────────────┘ └─────────────────┘ └────────────────┘
MCP OAuth 2.1 Proxies to Google OAuth 2.0
with PKCE Google OAuth
1. Discovery & Client Registration
Claude CLI discovers the server's OAuth capabilities:
- Fetches
/.well-known/oauth-authorization-serverfor OAuth metadata - Fetches
/.well-known/oauth-protected-resourcefor resource metadata - Performs Dynamic Client Registration at
/register(RFC 7591)
2. Authorization Request (Claude CLI → Server → Browser)
Claude CLI initiates OAuth 2.1 with PKCE:
- Claude CLI opens browser to
/authorizewithcode_challenge - Server stores pending request (client ID, redirect URI, code challenge)
- Server responds with HTTP 302 redirect to Google OAuth (browser follows automatically)
3. Google Authentication (Browser → Google → Server → Browser)
User authenticates with Google:
- User logs in and grants
gmail.readonlypermission - Google responds with HTTP 302 redirect to
/callback(browser follows) - Server exchanges Google authorization code for Google access/refresh tokens
- Server generates an MCP authorization code
- Server responds with HTTP 302 redirect to Claude CLI callback (browser follows)
4. Token Exchange (Claude CLI → Server)
Claude CLI completes the OAuth flow:
- Exchanges MCP authorization code for access token at
/token - Server validates PKCE
code_verifierusing SHA-256 against storedcode_challenge - Server encrypts Google tokens + user email into a JWE (JSON Web Encryption) token
- Server returns the encrypted JWE as the MCP bearer token
5. Authenticated MCP Requests (Stateless)
Claude CLI uses the bearer token:
- Sends requests to
/mcp?allowed_labels=...withAuthorization: Bearer <encrypted-jwe> - Server decrypts the JWE to extract Google tokens
- Server refreshes Google access token if expired (transparent to client)
- Server executes Gmail API calls and returns results
The easiest way to authenticate is using Claude CLI's built-in OAuth support:
-
Add the server to Claude CLI:
claude mcp add --transport http gmail https://localhost:3000/mcp?allowed_labels=INBOX -
In Claude Code, run:
/mcp -
Select the gmail server and choose "Authenticate"
-
Complete the Google OAuth flow in your browser
Claude CLI automatically handles token storage and refresh.
For local development and testing:
npm run buildnpm startThe server will start on port 3000 (or the port specified in the PORT environment variable).
Health check:
curl http://localhost:3000/healthExpected response:
{"status":"ok"}Test MCP tools list:
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'You should see four tools listed: list_messages, read_message, search_messages, logout.
For development with auto-reload:
npm run dev| Variable | Required | Default | Description |
|---|---|---|---|
OAUTH_CREDENTIALS_PATH |
Yes | - | Path to Google OAuth credentials JSON file |
TOKEN_ENCRYPTION_PATH |
Yes | - | Path to file containing 32-byte encryption key (hex or base64) |
TLS_KEY_PATH |
Yes | - | Path to TLS private key file (PEM) |
TLS_CERT_PATH |
Yes | - | Path to TLS certificate file (PEM) |
PORT |
No | 3000 | HTTPS server port |
HOST |
No | localhost | Server hostname |
SESSION_TTL |
No | 86400 | MCP session TTL in seconds (default: 24 hours) |
LOG_LEVEL |
No | info | Log level (trace, debug, info, warn, error, fatal) |
NODE_ENV |
No | - | Set to "production" for JSON log output |
Note: Label filtering is configured per-client via URL query parameter allowed_labels. This parameter is required and cannot be empty.
This server uses the gmail.readonly scope, which provides:
- Read-only access to Gmail messages and metadata
- No ability to send, delete, or modify emails
- No access to Gmail settings or account information
This is the most secure scope for accessing Gmail data.
Local Development:
- Credentials stored in
credentials/directory - Directory is excluded from git via
.gitignore - Never commit
credentials/to version control
Container Deployment:
- Use container secrets or mounted volumes for credentials
- Ensure credentials file is read-only
- Use environment variables for configuration
- MCP bearer token is an encrypted JWE containing Google tokens and user email
- Stateless design: Server has no persistent storage for authenticated requests
- Google access tokens expire after 1 hour (auto-refreshed transparently on each request)
- Google refresh tokens are long-lived (until manually revoked by user)
- Encryption key is required (
TOKEN_ENCRYPTION_PATH) - key rotation invalidates all tokens - Re-authentication required when: refresh token revoked, encryption key rotated, or token corrupted
The server implements several OAuth 2.1 security measures:
- PKCE S256 verification: Code verifier is validated using SHA-256 hash comparison
- Redirect URI validation: Only registered redirect URIs are accepted during authorization
- Encrypted bearer tokens: JWE tokens use AES-256-GCM encryption (dir/A256GCM)
- State parameter validation: OAuth state uses UUID format with strict parsing
- Authorization code single-use: Codes are deleted immediately after exchange
- Never commit secrets: Ensure
credentials/is in.gitignore - Use Docker secrets: Always use secrets for production deployments
- Rotate tokens regularly: Regenerate OAuth tokens periodically
- Limit network exposure: Restrict access to localhost or use a reverse proxy with HTTPS
- Monitor API quotas: Check Google Cloud Console for Gmail API usage
- Use TLS: Deploy behind a reverse proxy with HTTPS for remote access
- Audit access: Review OAuth consent screen and authorized applications in your Google account
- Default quota: 1 billion units per day
- Personal use should stay well within limits
- Monitor usage in Google Cloud Console
- The server implements no additional rate limiting
-
Install dependencies:
npm install
-
Run in development mode (with auto-reload):
npm run dev
-
Authenticate via Claude CLI when first accessing the server
Run unit tests:
npm testRun tests in watch mode:
npm run test:watchRun tests with coverage:
npm run test:coverageBuild the project:
npm run buildTest the compiled output:
npm start- Modify source files in
src/ - Run tests with
npm test - Test locally with
npm run dev - Build with
npm run build - Rebuild Docker image and redeploy
This project is licensed under the MIT License.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
For issues or questions:
- Check service logs:
docker service logs gmail-mcp - Verify secrets:
docker secret ls - Test health endpoint:
curl http://localhost:3000/health - Check Gmail API quotas in Google Cloud Console
- Verify token validity by regenerating locally