# Gmail MCP Server
[](https://github.com/SynclyAI/gmail-mcp/actions/workflows/ci.yml)
[](https://codecov.io/gh/SynclyAI/gmail-mcp)
A Model Context Protocol (MCP) server that provides read-only Gmail access to Claude CLI with label-based filtering and container deployment support.
## Why Use This?
- **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:**
1. Configure Claude CLI to trust your certificate (if self-signed):
```bash
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem
```
2. Add the server:
```bash
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.
## Claude CLI Integration
### Certificate Configuration
For self-signed or internal CA certificates, configure Claude CLI to trust them:
```bash
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem
```
Alternatively, add to `~/.claude/settings.json`:
```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.
### Add Server via Command Line
```bash
claude mcp add --transport http gmail https://localhost:3001/mcp?allowed_labels=INBOX,STARRED
```
**Label 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.
### Manual Configuration
Claude CLI configuration is stored at `~/.claude.json`:
```json
{
"mcpServers": {
"gmail": {
"transport": "http",
"url": "https://localhost:3001/mcp?allowed_labels=INBOX,STARRED"
}
}
}
```
### Integration Steps
1. **Ensure the service is running:**
```bash
curl http://localhost:3001/health
```
Expected response: `{"status":"ok"}`
2. **Add the server** (see above)
3. **Authenticate:**
- Start Claude Code: `claude`
- Run `/mcp` command
- Select the gmail server
- Choose "Authenticate"
- Complete Google OAuth in browser
4. **Verify tools are available:**
Ask Claude: "What Gmail tools do you have?"
Claude should list three Gmail tools:
- `list_messages`
- `read_message`
- `search_messages`
## Available Tools
### 1. list_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 ID
- `threadId`: Thread ID
- `snippet`: Message preview
- `labelIds`: Array of label IDs
- `from`: Sender email address
- `subject`: Email subject
- `date`: Send date
### 2. read_message
Reads the full content of a specific message.
**Parameters:**
- `messageId` (string, required): The ID of the message to read
- `maxSize` (number, optional): Maximum body size in characters. If exceeded, body is truncated.
**Returns:**
- Full message object containing:
- `id`: Message ID
- `threadId`: Thread ID
- `labelIds`: Array of label IDs
- `from`: Sender email address
- `to`: Recipient email address
- `subject`: Email subject
- `date`: Send date
- `body`: Full email body (Markdown format)
- `bodySize`: Original body size in characters
- `truncated`: Whether body was truncated
- `snippet`: 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.
### 3. search_messages
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.
## Usage Examples
### Example 1: List Recent Emails
```
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.
### Example 2: Read Specific Message
```
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.
### Example 3: Search Emails
```
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).
### Example 4: Complex Search
```
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
Label filtering allows you to control which emails Claude can access by specifying a whitelist of allowed labels.
### Configuration
Label filtering is configured per-user via the MCP server URL query parameter `allowed_labels`:
```bash
claude mcp add --transport http gmail http://localhost:3001/mcp?allowed_labels=INBOX,STARRED
```
Or in Claude CLI config:
```json
{
"mcpServers": {
"gmail": {
"transport": "http",
"url": "http://localhost:3001/mcp?allowed_labels=INBOX,STARRED"
}
}
}
```
### Label ID Formats
**System Labels** (uppercase):
- `INBOX` - Inbox
- `SENT` - Sent mail
- `DRAFT` - Drafts
- `TRASH` - Trash
- `SPAM` - Spam
- `IMPORTANT` - Important
- `STARRED` - Starred
- `UNREAD` - Unread
**Category Labels**:
- `CATEGORY_PERSONAL`
- `CATEGORY_SOCIAL`
- `CATEGORY_PROMOTIONS`
- `CATEGORY_UPDATES`
- `CATEGORY_FORUMS`
**Custom Labels**:
Use your custom label names directly (e.g., `Work`, `Personal`). The server resolves names to IDs automatically.
### How Filtering Works
Label filtering is applied at three levels:
1. **list_messages**: Only returns messages that have at least one allowed label
2. **read_message**: Validates the message has an allowed label before returning content
3. **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.
### Examples
**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
```
### Updating Labels
To change `allowed_labels`, update the URL in Claude CLI config:
**Option 1: Edit config file**
Edit `~/.claude.json` and modify the URL:
```json
{
"mcpServers": {
"gmail": {
"url": "https://localhost:3001/mcp?allowed_labels=INBOX,NEW_LABEL"
}
}
}
```
**Option 2: Remove and re-add server**
```bash
claude mcp remove gmail
claude mcp add --transport http gmail https://localhost:3001/mcp?allowed_labels=INBOX,NEW_LABEL
```
## Troubleshooting
### Common Issues
**"Not authenticated" error:**
**Solution:** Re-authenticate via Claude CLI:
1. Run `/mcp` command in Claude Code
2. Select the gmail server
3. Choose "Authenticate"
4. 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:
1. Go to https://console.cloud.google.com
2. Select your project
3. Navigate to "APIs & Services" > "Dashboard"
4. 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:
```bash
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:
1. Run `/mcp` command in Claude Code
2. Select the gmail server
3. Choose "Authenticate"
4. Complete Google OAuth in browser
**Tools not appearing in Claude CLI:**
**Solution:**
1. Verify the config.json format is correct
2. Check that the URL is accessible: `curl http://localhost:3000/health`
3. Restart Claude CLI
4. Check Claude CLI logs for errors
---
# Server Administration
The following sections are for administrators deploying and managing the MCP server.
## Features
- **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
## Architecture
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 labels
- `read_message`: Read full content of a specific message
- `search_messages`: Search messages using Gmail query syntax
All communication happens over HTTPS. Credentials and encryption keys are loaded from files specified via environment variables.
## Prerequisites
- **Node.js** >= 22.0.0
- **Docker** (for containerized deployment)
- **Gmail account**
- **Google Cloud Console** account (free tier sufficient)
- **Claude CLI** installed
## Google Cloud Console Setup
### Step 1: Create Google Cloud Project
1. Navigate to https://console.cloud.google.com
2. Click the project dropdown in the top navigation bar
3. Click "New Project"
4. Enter project name: "Gmail MCP Server" (or any name you prefer)
5. Note the Project ID (must be globally unique)
6. Click "Create"
**Note**: The free tier is sufficient for personal use.
### Step 2: Enable Gmail API
1. In Google Cloud Console, ensure your project is selected
2. Navigate to "APIs & Services" > "Library" (left sidebar)
3. Search for "Gmail API"
4. Click on "Gmail API" from the results
5. Click the "Enable" button
6. Wait for the API to be enabled (~30 seconds)
**Note**: There's no cost for enabling the API; it uses pay-per-use pricing.
### Step 3: Configure OAuth Consent Screen
1. Navigate to "APIs & Services" > "OAuth consent screen"
2. Select user type:
- Choose "External" if using a personal Gmail account
- Choose "Internal" if using Google Workspace
3. Click "Create"
4. Fill in the App information:
- App name: "Gmail MCP Server"
- User support email: Your email address
- Developer contact: Your email address
5. Click "Save and Continue"
6. Add scopes:
- Click "Add or Remove Scopes"
- Search for "gmail.readonly"
- Check the box for: `https://www.googleapis.com/auth/gmail.readonly`
- Click "Update"
7. Click "Save and Continue"
8. Add test users:
- Click "Add Users"
- Enter your Gmail address
- Click "Add"
9. Click "Save and Continue"
10. 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.
### Step 4: Create OAuth 2.0 Credentials
1. Navigate to "APIs & Services" > "Credentials"
2. Click "Create Credentials" > "OAuth client ID"
3. If prompted to configure the consent screen, it should already be done
4. Application type: Select **"Web application"**
5. Name: "Gmail MCP Server"
6. 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`
7. Click "Create"
8. Click "Download JSON" to download credentials file
9. Save the file as `credentials.json` in your `credentials/` 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:
```json
{
"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.
## Installation & Setup
### Clone or Download
If you have this project locally, navigate to the project directory:
```bash
cd /path/to/email-mcp
```
### Install Dependencies
```bash
npm install
```
## Authentication Flow (MCP OAuth 2.1)
The server implements the [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) 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
```
### Step-by-Step Flow
**1. Discovery & Client Registration**
Claude CLI discovers the server's OAuth capabilities:
- Fetches `/.well-known/oauth-authorization-server` for OAuth metadata
- Fetches `/.well-known/oauth-protected-resource` for 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 `/authorize` with `code_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.readonly` permission
- 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_verifier` using SHA-256 against stored `code_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=...` with `Authorization: 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
### Claude CLI Authentication
The easiest way to authenticate is using Claude CLI's built-in OAuth support:
1. Add the server to Claude CLI:
```bash
claude mcp add --transport http gmail https://localhost:3000/mcp?allowed_labels=INBOX
```
2. In Claude Code, run:
```
/mcp
```
3. Select the gmail server and choose "Authenticate"
4. Complete the Google OAuth flow in your browser
Claude CLI automatically handles token storage and refresh.
## Quick Start (Local Development)
For local development and testing:
### 1. Build the Project
```bash
npm run build
```
### 2. Start the Server
```bash
npm start
```
The server will start on port 3000 (or the port specified in the `PORT` environment variable).
### 3. Test the Server
**Health check:**
```bash
curl http://localhost:3000/health
```
Expected response:
```json
{"status":"ok"}
```
**Test MCP tools list:**
```bash
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`.
### 4. Development Mode
For development with auto-reload:
```bash
npm run dev
```
## Environment Variables
| 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.
## Security Considerations
### OAuth Scope
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.
### Credential Storage
**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
### Token Management
- **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
### OAuth Security Measures
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
### Best Practices
1. **Never commit secrets**: Ensure `credentials/` is in `.gitignore`
2. **Use Docker secrets**: Always use secrets for production deployments
3. **Rotate tokens regularly**: Regenerate OAuth tokens periodically
4. **Limit network exposure**: Restrict access to localhost or use a reverse proxy with HTTPS
5. **Monitor API quotas**: Check Google Cloud Console for Gmail API usage
6. **Use TLS**: Deploy behind a reverse proxy with HTTPS for remote access
7. **Audit access**: Review OAuth consent screen and authorized applications in your Google account
### Gmail API Quotas
- 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
## Development
### Local Development Setup
1. Install dependencies:
```bash
npm install
```
2. Run in development mode (with auto-reload):
```bash
npm run dev
```
3. Authenticate via Claude CLI when first accessing the server
### Running Tests
Run unit tests:
```bash
npm test
```
Run tests in watch mode:
```bash
npm run test:watch
```
Run tests with coverage:
```bash
npm run test:coverage
```
### Building
Build the project:
```bash
npm run build
```
Test the compiled output:
```bash
npm start
```
### Making Changes
1. Modify source files in `src/`
2. Run tests with `npm test`
3. Test locally with `npm run dev`
4. Build with `npm run build`
5. Rebuild Docker image and redeploy
## License & Contributing
This project is licensed under the [MIT License](LICENSE).
### Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
### Support
For issues or questions:
1. Check service logs: `docker service logs gmail-mcp`
2. Verify secrets: `docker secret ls`
3. Test health endpoint: `curl http://localhost:3000/health`
4. Check Gmail API quotas in Google Cloud Console
5. Verify token validity by regenerating locally