Skip to main content
Glama
bread-wood

Google Workspace MCP Server

by bread-wood
README.md
# Google Workspace MCP Server

A private, self-hosted [Model Context Protocol](https://modelcontextprotocol.io/) server that gives Claude authenticated access to Google Calendar, Gmail, Drive, Docs, Sheets, and Slides via stdio transport.

## Features

- **43 tools** across 7 Google Workspace services
- **Secure token storage**: AES-256-GCM encryption with macOS Keychain-stored keys
- **No hard deletes**: Archive/trash semantics only — Docs, Sheets, and Slides move to ARCHIVED folder, Gmail messages can be archived or trashed (30-day retention)
- **Prompt injection defense**: 5-stage sanitization pipeline for all untrusted content
- **OWASP-aligned security**: Input validation, rate limiting, structured logging, PKCE OAuth2

## Prerequisites

- Node.js >= 20
- macOS (uses Keychain for key storage)
- A Google Cloud project with OAuth credentials

## Google OAuth Setup

### 1. Create a Google Cloud Project

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Click **Select a project** → **New Project**
3. Name it (e.g., "Workspace MCP Server") and click **Create**

### 2. Enable APIs

Navigate to **APIs & Services → Library** and enable:

- Google Calendar API
- Gmail API
- Google Drive API
- Google Docs API
- Google Sheets API
- Google Slides API

### 3. Configure OAuth Consent Screen

1. Go to **APIs & Services → OAuth consent screen**
2. Click **Get started** (or **Edit App** if you already have one configured)
3. Set **User type** to **External** (or **Internal** if using Google Workspace)
4. Fill in the required fields:
   - **App name**: e.g., "Workspace MCP Server"
   - **User support email**: your email address
   - **Developer contact email**: your email address
5. On the **Scopes** step, click **Add or remove scopes** and add:
   - `https://www.googleapis.com/auth/calendar.events`
   - `https://www.googleapis.com/auth/gmail.modify`
   - `https://www.googleapis.com/auth/gmail.send`
   - `https://www.googleapis.com/auth/gmail.labels`
   - `https://www.googleapis.com/auth/gmail.settings.basic`
   - `https://www.googleapis.com/auth/drive.readonly`
   - `https://www.googleapis.com/auth/drive.file`
   - `https://www.googleapis.com/auth/documents`
   - `https://www.googleapis.com/auth/spreadsheets`
   - `https://www.googleapis.com/auth/presentations`
6. Save and continue

### 4. Add Test Users (Required)

While the app is in "Testing" publishing status (before Google verification), only explicitly listed test users can authenticate. If you skip this step you will get a **403: access_denied** error saying the app "has not completed the Google verification process".

1. Go to **APIs & Services → OAuth consent screen**
2. Under the **Audience** section (or **Test users** tab), click **Add users**
3. Enter the Google account email you will authenticate with (e.g., `you@gmail.com`)
4. Click **Save**

You can add up to 100 test users. Only these accounts will be able to complete the OAuth flow while the app is in testing mode. You do **not** need to submit the app for Google verification — test users are sufficient for personal/self-hosted use.

### 5. Create OAuth Credentials

1. Go to **APIs & Services → Credentials**
2. Click **Create Credentials → OAuth client ID**
3. Choose **Desktop application**
4. Name it (e.g., "MCP Server")
5. Click **Create** and note the **Client ID** and **Client Secret**

### 6. Configure the MCP Server

Set environment variables:

```bash
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret"
```

Or create a config file at `~/.config/google-workspace-mcp/config.json`:

```json
{
  "clientId": "your-client-id.apps.googleusercontent.com",
  "clientSecret": "your-client-secret"
}
```

### 7. First Run

```bash
npm install
npm run build
npm start
```

On first run, the server will:
1. Open your browser for Google sign-in
2. Ask you to authorize the requested scopes
3. Store tokens securely in macOS Keychain + encrypted file
4. Start the MCP server on stdio

### 8. Claude Code Integration

Add to your `~/.claude/settings.json`:

```json
{
  "mcpServers": {
    "google-workspace": {
      "command": "node",
      "args": ["/path/to/google-workspace-mcp/dist/index.js"],
      "env": {
        "GOOGLE_CLIENT_ID": "your-client-id",
        "GOOGLE_CLIENT_SECRET": "your-client-secret"
      }
    }
  }
}
```

## Available Tools

### Gmail (13 tools)

> **Note for existing users**: The `gmail.settings.basic` scope was added to enable filter management tools. If you authenticated before this scope was introduced, you will be prompted to re-authenticate the next time your token refresh fails. Re-authentication happens automatically — simply follow the browser prompt when it appears.

- `gmail_search` — Search emails by query (up to 500 results)
- `gmail_get_message` — Get full email content by ID
- `gmail_send` — Send an email
- `gmail_create_draft` — Create a draft email
- `gmail_list_labels` — List all Gmail labels
- `gmail_archive` — Archive messages (remove from inbox)
- `gmail_modify_labels` — Add/remove labels (mark read, categorize, etc.)
- `gmail_trash` — Move messages to trash (30-day retention)
- `gmail_create_label` — Create labels and sub-labels
- `gmail_delete_label` — Delete a user-created label (messages are not deleted)
- `gmail_list_filters` — List all Gmail filters with their criteria and actions
- `gmail_delete_filter` — Delete a Gmail filter by ID (use `gmail_list_filters` to find IDs)
- `gmail_create_filter` — Create a Gmail filter with criteria and automated actions (label, archive, mark read, star, forward)

### Calendar (5 tools)
- `calendar_list_events` — List events in a time range
- `calendar_get_event` — Get full event details
- `calendar_create_event` — Create a calendar event
- `calendar_create_calendar` — Create a new calendar
- `calendar_update_event` — Update an existing event

### Drive (7 tools)
- `drive_search` — Search files in Drive
- `drive_get_file_metadata` — Get file metadata
- `drive_download` — Download/export file content
- `drive_upload` — Upload a file to Drive
- `drive_create_folder` — Create a new folder in Drive
- `drive_move` — Move a file or folder to a different parent folder
- `drive_create_pdf` — Generate a formatted PDF from text content and upload to Drive

### Docs (5 tools)
- `docs_get` — Get document content
- `docs_create` — Create a new document
- `docs_update` — Update document content
- `docs_archive` — Archive document (move to ARCHIVED folder)
- `docs_delete` — Permanently delete a document (irreversible)

### Sheets (6 tools)
- `sheets_get` — Get spreadsheet metadata
- `sheets_create` — Create a new spreadsheet
- `sheets_read_range` — Read cell values from a range
- `sheets_update_range` — Write cell values to a range
- `sheets_archive` — Archive spreadsheet (move to ARCHIVED folder)
- `sheets_delete` — Permanently delete a spreadsheet (irreversible)

### Slides (5 tools)
- `slides_get` — Get presentation content (title, slide count, extracted text)
- `slides_create` — Create a new presentation
- `slides_add_slide` — Add a slide with layout and placeholder text
- `slides_archive` — Archive presentation (move to ARCHIVED folder)
- `slides_delete` — Permanently delete a presentation (irreversible)

### Auth (1 tool)
- `auth_status` — Check authentication status and scopes

## Security

- **Controlled deletions**: Docs/Sheets/Slides support both soft archive (move to ARCHIVED folder) and permanent deletion. Gmail supports archive, trash (30-day retention), and label deletion. HTTP DELETE requests are blocked by default, with a targeted allowlist for specific safe operations (label deletion, file deletion).
- **Prompt injection defense**: All content from external sources (emails, documents, calendar events) passes through a 5-stage sanitization pipeline before being returned to the LLM.
- **Token security**: OAuth tokens are encrypted with AES-256-GCM. The encryption key is stored in macOS Keychain.
- **Rate limiting**: Per-tool rate limits prevent runaway API usage.
- **Input validation**: All tool inputs are validated with Zod schemas.

## Configuration

| Environment Variable | Default | Description |
|---|---|---|
| `GOOGLE_CLIENT_ID` | (required) | OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | (required) | OAuth client secret |
| `MAX_CONTENT_LENGTH` | `50000` | Max content length before truncation |
| `AUTH_TIMEOUT_MS` | `300000` | OAuth flow timeout (ms) |
| `LOG_LEVEL` | `info` | Log level: debug, info, warn, error |
| `ARCHIVE_FOLDER_NAME` | `ARCHIVED` | Name of the archive folder in Drive |

## Troubleshooting

### "Permission denied. The requested scope may not be authorized." despite valid token

If `auth_status` shows the required scope is present but a tool still returns a 403, the Google API itself is not enabled in your Cloud project. Each Google API must be enabled separately — granting an OAuth scope is not enough.

Go to **APIs & Services → Library** in the Cloud Console and enable the API for the failing service (e.g., **Google Slides API** for `slides_*` tools). After enabling, retry immediately — no re-authentication is needed.

### Re-authenticating to pick up new scopes

If new scopes were added to the server after your initial auth, your stored token won't include them. Delete the token file to force a fresh OAuth flow:

```bash
rm ~/.config/google-workspace-mcp/tokens.enc
```

Then restart the MCP server (e.g., run `/mcp` in Claude Code). The browser will open for re-authentication — authorize all requested scopes.

## Development

```bash
npm install
npm run dev      # Run with tsx (hot reload)
npm run build    # Compile TypeScript
npm test         # Run tests
npm run test:watch  # Run tests in watch mode
```