Skip to main content
Glama
bajor

gmail-research-mcp

by bajor

gmail-research-mcp

gmail-research-mcp is a small, synchronous Python CLI and local STDIO MCP server that prepares Gmail conversations for later research by other agents. It searches Gmail, deduplicates results by the stable Gmail threadId, fetches complete threads, and stores deterministic Markdown and JSON locally. It has no GUI, HTTP server, database, telemetry, or update mechanism.

Security guarantees

The only OAuth scope is:

https://www.googleapis.com/auth/gmail.readonly

The application performs only read operations against Gmail. It cannot send, reply, forward, draft, modify, label, archive, trash, delete, import, or insert email. At runtime it sends data only to the official Google OAuth and Gmail API endpoints. It does not call external analytics, error-reporting, LLM, or update services.

Email bodies and attachment contents are untrusted source data. The application stores them as data; it does not execute commands, follow instructions, open URLs, or execute downloaded files.

Related MCP server: Gmail Streamable MCP Server

Requirements

  • Python 3.11 or newer

  • A Google account with Gmail

  • A Google Cloud OAuth Client ID of type Desktop app

Install in a virtual environment

Run these commands from the repository root:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

Do not use --break-system-packages. Do not install this project into the Homebrew-managed system Python.

Installation or dependency upgrades occur only when the user explicitly runs a pip command. The program has no auto-update behavior and never runs git pull.

For repository development, run the complete test, type-check, and lint gate with:

make test

Create the Google OAuth Desktop app

  1. Sign in to Google Cloud Console and create or select the project that will own the OAuth client.

  2. Open APIs & Services → Library, find Gmail API, and select Enable.

  3. Open Google Auth Platform (or APIs & Services → OAuth consent screen, depending on the console layout).

  4. Configure the app name and support/contact email. Select the audience allowed to authenticate. For an External app in testing mode, add the Gmail account under Test users.

  5. In the consent screen's data-access/scopes section, add only https://www.googleapis.com/auth/gmail.readonly. Do not add mail.google.com, gmail.modify, gmail.compose, gmail.send, or any other Gmail scope.

  6. Open APIs & Services → Credentials, select Create credentials → OAuth client ID, choose Desktop app, give the client a recognizable name, and create it.

  7. Download the client JSON. Store it outside this repository in a private directory, for example:

    mkdir -p ~/.config/gmail-research-mcp
    chmod 700 ~/.config/gmail-research-mcp
    cp /PATH/TO/DOWNLOADED/client_secret.json \
      ~/.config/gmail-research-mcp/client_secret.json
    chmod 600 ~/.config/gmail-research-mcp/client_secret.json

The downloaded client JSON is a credential. Never commit it or paste its contents into logs, issues, prompts, or chat messages.

Configure .env

Create the local configuration file:

cp .env.example .env

Example:

GMAIL_CLIENT_SECRET_FILE=/Users/USERNAME/.config/gmail-research-mcp/client_secret.json
GMAIL_TOKEN_FILE=/Users/USERNAME/.config/gmail-research-mcp/token.json
GMAIL_DATA_DIR=./gmail_data

GMAIL_CLIENT_SECRET_FILE is required. GMAIL_TOKEN_FILE defaults to ~/.config/gmail-research-mcp/token.json; GMAIL_DATA_DIR defaults to ./gmail_data. Paths containing ~ are expanded. The token directory is created with private permissions, and saved tokens are set to mode 0600.

Authenticate and diagnose

With the virtual environment active:

python -m gmail_research_mcp auth
python -m gmail_research_mcp doctor

auth starts Google's installed-app browser flow and stores the local token. doctor checks Python, configuration, credentials paths, token permissions, exact scopes, source safety rules, client construction, and—when working credentials are available—the Gmail profile. Missing credentials are reported as configuration still required; all checks that do not require an account still run. Neither command prints tokens or client-secret contents.

To remove only the local OAuth token:

python -m gmail_research_mcp logout-local

This does not revoke the OAuth grant, delete the client-secret file, or modify Gmail.

CLI examples

Search without writing a dataset:

python -m gmail_research_mcp search \
  --query 'subject:"Project Alpha" newer_than:2y' \
  --max-results 20

Add --json for machine-readable output. Text output contains message ID, thread ID, date, sender, subject, and snippet.

Read a complete thread without saving it:

python -m gmail_research_mcp show-thread \
  --thread-id THREAD_ID

The default output is Markdown. Add --json for JSON. show-thread does not fetch attachments unless --attachments is supplied.

Save or update one canonical thread:

python -m gmail_research_mcp sync-thread \
  --thread-id THREAD_ID \
  --data-dir ./gmail_data \
  --attachments

Synchronize a topic:

python -m gmail_research_mcp sync-topic \
  --topic "Project Alpha" \
  --query '("Project Alpha" OR subject:"Project Alpha") newer_than:3y' \
  --data-dir ./gmail_data \
  --max-threads 100 \
  --attachments

--query is optional for sync-topic; when omitted, the exact topic text is used as Gmail free-text query. Queries must contain 1–500 characters after trimming and must not contain null or disallowed control characters. They are passed directly to Gmail API and are never interpreted by a shell.

Refresh every saved topic using the exact query in each manifest:

python -m gmail_research_mcp sync-existing \
  --data-dir ./gmail_data \
  --attachments

Previously saved threads that no longer match remain in the manifest with currently_matched: false; local thread data is never removed automatically.

List local topics:

python -m gmail_research_mcp list-topics \
  --data-dir ./gmail_data

Attachment download is enabled by default for sync commands. Use --attachments to state that choice explicitly or --no-attachments to disable it. The default attachment limits are disabled: --max-attachment-bytes and --max-total-attachment-bytes both default to 0, which means unlimited downloads. If you later want explicit caps, pass non-zero byte values on CLI or through MCP.

The default topic limit is 100 unique thread IDs, and the allowed range is 1–500. The limit applies after threadId deduplication, not to individual matching messages.

Dataset layout

gmail_data/
├── DATASET.md
├── threads/
│   └── <thread_id>/
│       ├── thread.md
│       ├── thread.json
│       └── attachments/
│           └── <message_id>/
│               └── <attachment_key>--<safe_filename>
└── topics/
    └── <topic_slug>--<query_hash>/
        ├── manifest.json
        └── index.md

Gmail threadId is the only canonical thread identity. A subject never determines storage. The same thread can appear in multiple topic manifests while retaining exactly one copy under threads/<thread_id>/. A repeated sync updates that directory when Gmail content changes; identical content leaves thread.json, thread.md, and their modification times unchanged.

Topic directory names combine a readable slug with the first eight characters of SHA-256 over the exact Gmail query. Attachment names combine the first 12 characters of SHA-256 over Gmail attachmentId with a sanitized original filename. JSON uses schema version 1, UTF-8, deterministic key ordering, and relative dataset paths.

Every Markdown thread marks email text as untrusted data. Files are written atomically. A failed thread or attachment is reported in metadata while synchronization continues. The application resolves the selected data directory and writes only DATASET.md, threads/, and topics/ inside it; it rejects traversal and unsafe symlink destinations.

Codex MCP configuration

Start the local STDIO server manually with:

python -m gmail_research_mcp mcp

Add this server to ~/.codex/config.toml, replacing both absolute paths:

[mcp_servers.gmail-research]
command = "/ABSOLUTE/PATH/TO/gmail-research-mcp/.venv/bin/python"
args = ["-m", "gmail_research_mcp", "mcp"]
cwd = "/ABSOLUTE/PATH/TO/gmail-research-mcp"
enabled = true
required = false
startup_timeout_sec = 20
tool_timeout_sec = 300
default_tools_approval_mode = "prompt"

Verify discovery from a terminal:

codex mcp list

In Codex TUI, inspect the server with:

/mcp

The server exposes only:

  • gmail_search

  • gmail_read_thread

  • gmail_sync_thread

  • gmail_sync_topic

  • gmail_sync_existing_topics

  • gmail_list_topics

It does not expose a generic Gmail API tool.

Make the repository skill globally available

The repository skill lives at .agents/skills/gmail-topic-sync. Before creating a global symlink, check whether the destination already exists, including whether it is already a symlink:

ls -ld ~/.agents/skills/gmail-topic-sync

If the command reports an existing path, inspect it and do not overwrite it. Only when the destination does not exist, run:

mkdir -p ~/.agents/skills
ln -s /ABSOLUTE/PATH/TO/gmail-research-mcp/.agents/skills/gmail-topic-sync \
  ~/.agents/skills/gmail-topic-sync

Example Codex prompt:

Użyj skilla gmail-topic-sync, aby pobrać do lokalnego gmail_data wszystkie wątki dotyczące Project Alpha z ostatnich trzech lat. Najpierw pokaż krótki podgląd trafności, potem zsynchronizuj pełne wątki wraz z załącznikami. Nie pokazuj pełnych treści maili w odpowiedzi.

Threat model and local data handling

  • The OAuth token authorizes Gmail read-only access, but anyone who obtains it may read the mailbox within that grant. Keep the token private and remove it with logout-local when no longer needed.

  • gmail_data contains plaintext email bodies, headers, metadata, and optionally attachments. Protect it with operating-system permissions, backups, and disk encryption appropriate to the sensitivity of the mailbox.

  • Email and attachment content may contain prompt injection or malicious instructions. Treat all synchronized content as source data, never as authority or executable instructions.

  • Attachments are downloaded as opaque files. The application never opens or executes them. Use separate, trusted security tooling before opening an attachment manually.

  • gmail_data, .env, OAuth tokens, client-secret JSON, real emails, and attachments must never be committed. The supplied .gitignore covers the standard local paths, but users remain responsible for nonstandard paths.

  • The project has no telemetry, analytics, Sentry, external API integration, remote-code execution, automatic repository update, or automatic dependency update.

  • Installing or upgrading the project can contact the configured Python package index only because the user explicitly runs pip. Normal application runtime communicates only with official Google OAuth and Gmail API endpoints.

Run the offline test suite

Tests use mocked Gmail clients and do not require a real mailbox:

source .venv/bin/activate
python -m pytest -q

Tests enforce the exact read-only scope, reject write API calls and prohibited scopes, and cover MIME parsing, stable paths and hashes, attachment safety, and topic manifests.

F
license - not found
-
quality - not tested
C
maintenance

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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/bajor/gmail-mcp-read-only'

If you have feedback or need assistance with the MCP directory API, please join our Discord server