Skip to main content
Glama
WhymustIhaveaname

lite-google-workspace-mcp

README.md
# lite-google-workspace-mcp

Lightweight MCP server for Gmail and Google Calendar. Each Google account runs as an independent process on its own port.

Important safety feature: direct Gmail sends require a per-account `allowed_recipients` list. If the list is missing or empty, all sends are blocked. The allowlist applies only to `send_gmail_message`; `draft_gmail_message` stays unrestricted so agents can prepare drafts for human review without being able to send them.

## How it works

```
Claude Code  --HTTP-->  lite-google-workspace-mcp (port 8001)  --Google API-->  account-A@gmail.com
             --HTTP-->  lite-google-workspace-mcp (port 8002)  --Google API-->  account-B@umd.edu
```

- One process per Google account, each listening on a separate port
- OAuth tokens stored locally at `~/.config/lite-google-workspace-mcp/tokens/<account>.json`
- Token refresh handled automatically via `google-auth`
- 21 tools exposed per account: Gmail (search, read, send, draft, labels, filters) + Calendar (events, free/busy, OOO, focus time)

## Prerequisites

1. A GCP project with Gmail API and Google Calendar API enabled
2. An OAuth 2.0 credential (Web application type) with redirect URI `http://localhost:8000/oauth2callback`
3. Download the credential JSON and save it as `~/.config/lite-google-workspace-mcp/client_secret.json`

## Install

```bash
git clone https://github.com/WhymustIhaveaname/lite-google-workspace-mcp.git
cd lite-google-workspace-mcp
uv sync
```

## First-time setup

### 1. Configure ports

Create `~/.config/lite-google-workspace-mcp/config.toml`:

```toml
[accounts.myaccount]
port = 8001

[accounts.work]
port = 8002
allowed_recipients = ["boss@company.com", "team@company.com"]
allowed_body_formats = ["html"]
```

The account name is just a label you choose. It maps to a token file and a port.

`send_gmail_message` will only deliver to addresses explicitly listed in `allowed_recipients` (checked against to/cc/bcc, case-insensitive). If `allowed_recipients` is omitted or empty, all sends are blocked. A recipient field that cannot be parsed into addresses is treated as disallowed rather than skipped, so exotic separators cannot smuggle an address past the check. Drafts are never delivered, so `draft_gmail_message` is not restricted by this list.

Both `send_gmail_message` and `draft_gmail_message` only accept formats listed in `allowed_body_formats`. If the option is omitted, it defaults to `["html"]`, so plain-text messages are blocked. Add `"plain"` explicitly only if that account needs to create or send plain-text mail.

Both options must be TOML arrays of strings, and `allowed_body_formats` accepts only `"plain"` and `"html"`. Anything else aborts server startup instead of silently blocking every message.

If an attachment cannot be read or decoded, nothing is sent and no draft is created. The error names the failing attachment so it can be fixed or dropped before retrying.

### 2. Authorize accounts

```bash
uv run lite-google-workspace-mcp auth myaccount
```

This prints an OAuth URL and opens your browser. Sign in with the Google account you want to link, grant permissions (you can skip some scopes if you want), and the token is saved locally.

Repeat for each account.

### 3. Start the server

Manual:

```bash
uv run lite-google-workspace-mcp serve --account myaccount
```

With systemd (recommended):

```bash
cp contrib/lite-google-workspace-mcp@.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now lite-google-workspace-mcp@myaccount
```

### 4. Connect to Claude Code

```bash
claude mcp add --scope user --transport http lite-gmail-myaccount http://localhost:8001/mcp
```

## Re-authorization

If a token expires or you want to change granted scopes:

```bash
# Stop the service first (auth uses port 8000 which must be free)
systemctl --user stop lite-google-workspace-mcp@myaccount

# Re-authorize
uv run lite-google-workspace-mcp auth myaccount

# Restart
systemctl --user start lite-google-workspace-mcp@myaccount
```

### Tokens expiring every 7 days

If the OAuth consent screen has an external user type and a Testing publishing status, Google expires every refresh token 7 days after consent (this applies to any scope beyond basic profile/email). The service then fails to start with `invalid_grant: Token has been expired or revoked`, and re-authorizing only resets the 7-day clock.

The fix is to publish the app to production: GCP Console > APIs & Services > OAuth consent screen > Publish app. Production refresh tokens don't expire on a timer. With restricted Gmail scopes you'll see an "unverified app" warning until verification, which is harmless for personal or self-hosted use.

## Tools

### Gmail (14 tools)

| Tool | Description |
|------|-------------|
| search_gmail_messages | Search by Gmail query syntax |
| get_gmail_message_content | Read a single message |
| get_gmail_messages_content_batch | Read multiple messages |
| get_gmail_thread_content | Read an entire thread |
| get_gmail_threads_content_batch | Read multiple threads |
| get_gmail_attachment_content | Download attachment |
| send_gmail_message | Send (plain text or HTML, with attachments) |
| draft_gmail_message | Create a draft |
| list_gmail_labels | List all labels |
| manage_gmail_label | Create/update/delete labels |
| list_gmail_filters | List all filters |
| manage_gmail_filter | Create/delete filters |
| modify_gmail_message_labels | Add/remove labels on a message |
| batch_modify_gmail_message_labels | Bulk label modification |

### Calendar (7 tools)

| Tool | Description |
|------|-------------|
| list_calendars | List all calendars |
| get_events | Get events by ID or time range |
| manage_event | Create/update/delete/RSVP to events |
| manage_out_of_office | Create/list/update/delete OOO blocks |
| manage_focus_time | Create/list/update/delete focus time |
| query_freebusy | Check availability |
| create_calendar | Create a new calendar |

## Development

```bash
uv sync --extra dev
uv run pytest
uv run ruff check src/
```