Skip to main content
Glama
flujo-app

mcp-gmail-oauth

by flujo-app

mcp-gmail-oauth

A self-hostable Gmail MCP server with one executable, one SQLite storage implementation, and two selectable transports:

  • stdio for a local MCP client;

  • Streamable HTTP for localhost development or a remotely hosted MCP endpoint (remote is an alias for http).

The server lets one Google account be connected per authenticated principal and exposes Gmail search/read, sending, replies, forwards, drafts, labels, message state, Trash, and bounded attachment downloads. It does not expose an authentication, connection, or status tool. Authorization stays in the protocol control plane.

This is an early self-hosted implementation. Read Security before exposing it to the internet. Dynamically registered MCP clients are explicitly unverified, and the current SQLite deployment model is single-process.

What “receiving mail” means

Gmail receives the mail. This server reads it on demand:

  • gmail_search_messages runs a Gmail search when the tool is called;

  • gmail_get_message and gmail_get_thread fetch current content from Gmail when called.

There is no background mailbox synchronization, SMTP receiver, Gmail Pub/Sub watch, webhook, or proactive notification in this release. An MCP client must call search/read again to see newly delivered messages.

Related MCP server: Gmail Streamable MCP Server

Authorization model

There are two different authorization relationships. Google credentials are never used as MCP bearer tokens and never pass through MCP messages.

Mode

MCP client → this server

This server → Google

stdio

Local process/stdio trust; no transport bearer token

Experimental negotiated stdio authorization extension, Google Desktop OAuth client, PKCE, ephemeral loopback callback

http / remote

MCP OAuth authorization code flow, S256 PKCE, resource binding, DCR, rotating refresh tokens

Google Web OAuth client, PKCE, fixed callback (HTTPS except explicitly enabled loopback development HTTP)

In stdio mode, authorization is exposed through the negotiated co.com.flujo/mcp-stdio-oauth control-plane methods, not tools/list. See STDIO-OAUTH-EXTENSION.md.

In HTTP mode, the server publishes protected-resource and authorization-server metadata. MCP clients dynamically register as public clients, the user confirms the exact callback on a local consent page, and Google sign-in establishes the principal bound to the resulting MCP tokens.

Requirements

  • Node.js 22.13 or newer

  • A Google Cloud project with the Gmail API enabled

  • A configured Google OAuth consent screen

  • A Google OAuth client JSON file of the correct application type

The server requests openid, email, profile, and https://www.googleapis.com/auth/gmail.modify. Google classifies gmail.modify as a restricted scope. Public deployments may require OAuth app verification and, when restricted-scope data is stored or transmitted through a server, a security assessment. Review the current Google Gmail scope requirements.

Install from this repository

npm ci
npm run check
npm test
npm run build

Run the compiled executable with:

node dist/cli.js --help

The npm binary name is mcp-gmail-oauth when the package is installed as a package.

Create the Google OAuth clients

Google treats installed/desktop and web-server OAuth clients as different application types. This server enforces that distinction, so one client JSON cannot be reused for both transports.

Stdio: Desktop app client

Create an OAuth client with application type Desktop app and download its JSON. Stdio authorization opens an ephemeral listener on 127.0.0.1 and sends Google a runtime redirect such as:

http://127.0.0.1:49152

The port changes per authorization attempt. This is Google’s desktop loopback flow; the listener accepts only the root callback, atomically binds state to that exact attempt, times out after ten minutes, and closes after completion. Stray or mismatched localhost requests cannot consume another attempt or disable timeout cleanup.

HTTP/remote: Web application client

Create a separate OAuth client with application type Web application. Add this exact authorized redirect URI in Google Cloud:

<public-base-url>/oauth/google/callback

Examples:

http://127.0.0.1:3000/oauth/google/callback
https://gmail-mcp.example.com/oauth/google/callback

Scheme, hostname, port, path, and trailing-slash behavior matter. Google requires the redirect URI to exactly match a configured URI, and the server refuses to start if the callback derived from --public-base-url is not present in the Web client JSON. See Google’s web-server OAuth guidance.

The intended hosting origin is required because it defines all externally visible security identifiers and endpoints, including the OAuth issuer, MCP resource audience, metadata URLs, and Google callback. The server deliberately does not infer them from Host or forwarded headers.

Keep downloaded client JSON outside the repository. The .gitignore excludes common client-secret filenames, but that is not a substitute for secret management.

Run over stdio

node dist/cli.js \
  --transport stdio \
  --google-client /absolute/path/to/google-desktop-client.json \
  --database /absolute/path/to/mcp-gmail-oauth.sqlite \
  --local-user-id my-local-profile

Example MCP server configuration after building the repository:

{
  "mcpServers": {
    "gmail": {
      "command": "node",
      "args": [
        "/absolute/path/to/mcp-gmail-oauth/dist/cli.js",
        "--transport",
        "stdio",
        "--google-client",
        "/absolute/path/to/google-desktop-client.json",
        "--local-user-id",
        "my-local-profile"
      ]
    }
  }
}

The MCP client must support and negotiate the stdio OAuth extension plus URL elicitation to initiate the first Google connection. A client without that support can see the Gmail business tools but has no supported way to start authorization; calls return an ordinary authorization_required tool result until the account is connected. A negotiated client receives the extension’s namespaced -32042 JSON-RPC recovery error when a Gmail call discovers missing, expired, or revoked authorization, after which it should request fresh status and offer a user-initiated start flow.

Use a stable, private --local-user-id. Profiles using the same value and database resolve to the same local principal.

Run HTTP on localhost

Local HTTP is useful for development and protocol testing only. It still requires a Web application Google OAuth client because it uses the hosted callback flow.

Register this Google redirect URI:

http://127.0.0.1:3000/oauth/google/callback

Then run:

node dist/cli.js \
  --transport http \
  --google-client /absolute/path/to/google-web-client.json \
  --listen 127.0.0.1:3000 \
  --public-base-url http://127.0.0.1:3000 \
  --allow-insecure-http

The MCP resource URL is http://127.0.0.1:3000/mcp. Plain HTTP is rejected by default. --allow-insecure-http permits it only for localhost, 127.0.0.1, or ::1, logs a dangerous-development warning, and produces a non-compliant development endpoint. It is not a production hosting option.

Host remotely

Terminate TLS at a hardened reverse proxy and keep the Node listener private when possible:

MCP_GMAIL_TRANSPORT=http \
MCP_GMAIL_DB_PATH=/var/lib/mcp-gmail-oauth/mcp-gmail-oauth.sqlite \
GOOGLE_OAUTH_CLIENT_FILE=/run/secrets/google-web-client.json \
MCP_GMAIL_LISTEN=127.0.0.1:3000 \
MCP_GMAIL_PUBLIC_BASE_URL=https://gmail-mcp.example.com \
MCP_GMAIL_ENCRYPTION_KEY='<32-byte-base64url-or-64-hex-secret>' \
node dist/cli.js

The externally configured Google callback must be:

https://gmail-mcp.example.com/oauth/google/callback

Production requirements:

  • expose only HTTPS publicly and redirect or reject public HTTP;

  • preserve the external Host header, or explicitly configure the required hostname with --allowed-host;

  • do not rewrite /mcp, /.well-known/*, or /oauth/* paths;

  • allow streaming responses and use suitable request/idle timeouts at the proxy;

  • rate-limit DCR, authorization, token, callback, and MCP routes;

  • set MCP_GMAIL_ENCRYPTION_KEY from a secret manager even when the private listener is loopback-bound;

  • back up the SQLite database consistently and back up its encryption key separately;

  • run one application process against the database and persistent local disk.

Any HTTP deployment whose public base URL has a non-loopback hostname requires MCP_GMAIL_ENCRYPTION_KEY at startup, even when its private listener is loopback-bound.

Remote OAuth endpoints

For https://gmail-mcp.example.com, the server publishes:

Purpose

URL

MCP resource

https://gmail-mcp.example.com/mcp

Protected-resource metadata

https://gmail-mcp.example.com/.well-known/oauth-protected-resource/mcp

Authorization-server metadata

https://gmail-mcp.example.com/.well-known/oauth-authorization-server

Dynamic client registration

https://gmail-mcp.example.com/oauth/register

Authorization

https://gmail-mcp.example.com/oauth/authorize

Token

https://gmail-mcp.example.com/oauth/token

Revocation

https://gmail-mcp.example.com/oauth/revoke

Google callback

https://gmail-mcp.example.com/oauth/google/callback

The only MCP scope is mcp. The implementation supports public clients (token_endpoint_auth_method=none), authorization code plus S256 PKCE, optional refresh-token grants, exact redirect matching, RFC 8707-style resource binding, transaction-atomic code redemption and refresh issuance, refresh-token rotation, family revocation that cascades to linked live access tokens, and bearer access on /mcp.

Important DCR trust warning

DCR registration is open and does not verify a client’s claimed name or website. Every dynamically registered client is stored with trust level unverified, and the consent page labels it that way while showing the exact client ID, claimed website origin, redirect URI, and requested scope.

Registration means “syntactically accepted,” not “trusted.” Users must inspect the exact callback before approving. The server applies bounded, process-local request/concurrency limits and a separate DCR limit. By default, DCR is limited to 10 registrations per IP and 100 total per hour, with at most 1,000 unverified dynamic clients stored. Inactive clients become eligible for pruning after 30 days, but a client with a live authorization request, code, access token, or refresh token is retained.

These controls protect one process; they are not a distributed edge defense. Public operators should retain proxy-level rate/connection limits and abuse monitoring, and may need an admission policy or allowlist before treating this as an unrestricted public service. Client ID Metadata Documents, attestation, administrative approval, and a DCR client-management UI are not implemented.

Without an external admission layer, any network user who can reach the service can register a client and attempt to connect a Google account permitted by the Google project’s consent-screen policy. This release has no server-side user/domain allowlist.

Gmail tools

tools/list contains only these 18 Gmail business tools:

Area

Tools

Search/read

gmail_search_messages, gmail_get_message, gmail_get_thread

Compose/send

gmail_send_message, gmail_reply_message, gmail_forward_message

Drafts

gmail_create_draft, gmail_list_drafts, gmail_get_draft, gmail_update_draft, gmail_send_draft

Labels/state

gmail_list_labels, gmail_create_label, gmail_modify_labels, gmail_set_message_state

Trash

gmail_trash_message, gmail_untrash_message

Attachments

gmail_download_attachment

There is intentionally no gmail_auth, gmail_connect, gmail_status, disconnect, permanent-delete, settings, filter, forwarding-rule, or mailbox-watch tool.

All Gmail API calls use userId=me; a tool cannot nominate a different Google account. Sending always uses the connected address. Attachments are accepted only as in-memory canonical base64—there are no filesystem-path or URL attachment sources.

Current safety limits include:

  • up to 50 search results or drafts per page;

  • up to 10 outgoing attachments;

  • up to 10 MiB decoded per attachment and 20 MiB decoded in aggregate;

  • attachment downloads default to 5 MiB and cannot request more than 10 MiB;

  • bounded MIME traversal and body output;

  • message reads return sanitized text and attachment metadata rather than active HTML.

Moving a message to Trash is reversible. Immediate permanent deletion is not implemented and is not permitted by gmail.modify.

Configuration

CLI options take precedence over environment variables.

Setting

CLI

Environment

Default

Transport

--transport stdio|http|remote

MCP_GMAIL_TRANSPORT

stdio

SQLite path

--database <path>

MCP_GMAIL_DB_PATH

~/.mcp-gmail-oauth/mcp-gmail-oauth.sqlite

Google client JSON

--google-client <path>

GOOGLE_OAUTH_CLIENT_FILE

required

Stdio local identity

--local-user-id <id>

MCP_GMAIL_LOCAL_USER_ID

local

HTTP listener

--listen <host:port>

MCP_GMAIL_LISTEN

127.0.0.1:3000

External origin

--public-base-url <origin>

MCP_GMAIL_PUBLIC_BASE_URL

derived listener origin in HTTP mode

Insecure loopback HTTP

--allow-insecure-http

MCP_GMAIL_ALLOW_INSECURE_HTTP (true|false|1|0)

false

Requests per IP/minute

--oauth-rate-limit-per-minute <count>

MCP_GMAIL_OAUTH_RATE_LIMIT_PER_MINUTE

120

Requests total/minute

--oauth-global-rate-limit-per-minute <count>

MCP_GMAIL_OAUTH_GLOBAL_RATE_LIMIT_PER_MINUTE

10000

DCR requests per IP/hour

--oauth-dcr-rate-limit-per-hour <count>

MCP_GMAIL_OAUTH_DCR_RATE_LIMIT_PER_HOUR

10

DCR requests total/hour

--oauth-global-dcr-rate-limit-per-hour <count>

MCP_GMAIL_OAUTH_GLOBAL_DCR_RATE_LIMIT_PER_HOUR

100

In-flight requests per IP

--oauth-max-concurrent-per-ip <count>

MCP_GMAIL_OAUTH_MAX_CONCURRENT_PER_IP

8

In-flight requests total

--oauth-max-concurrent <count>

MCP_GMAIL_OAUTH_MAX_CONCURRENT

128

Trusted proxy hops

--oauth-trusted-proxy-hops <count>

MCP_GMAIL_OAUTH_TRUSTED_PROXY_HOPS

0

Unverified DCR client cap

--oauth-dynamic-client-limit <count>

MCP_GMAIL_OAUTH_DYNAMIC_CLIENT_LIMIT

1000

DCR inactive age

--oauth-dynamic-client-max-idle-days <days>

MCP_GMAIL_OAUTH_DYNAMIC_CLIENT_MAX_IDLE_DAYS

30

Extra allowed hostname

repeat --allowed-host <hostname>

MCP_GMAIL_ALLOWED_HOSTS (comma-separated)

public-origin hostname

Data encryption key

no CLI option

MCP_GMAIL_ENCRYPTION_KEY

generated sidecar key when allowed

Log level

--log-level <level>

MCP_GMAIL_LOG_LEVEL

info

remote is normalized to http. The external base URL must be an origin without a path, credentials, query, or fragment. It must use HTTPS. The only exception is loopback HTTP when dangerous development mode is explicitly enabled. Allowed-host entries are hostnames only—no scheme, port, or path.

Rate and concurrency counters are fixed-window, bounded, and held in this process only. Client IP is taken from the socket by default; forwarding headers are ignored. Set trusted proxy hops only when the service is reachable exclusively through that exact, trusted proxy chain. Multi-process deployments need a shared limiter at the edge, although this SQLite release supports only one application process.

The application reads process environment variables; it does not load .env itself. Use your process manager, container runtime, shell, or Node’s env-file support, for example:

node --env-file=.env dist/cli.js

See .env.example.

SQLite and deployment limits

Both transports use the same SqliteStore, schema, migrations, and default database path. You can stop one mode and start the other against the same file. One executable invocation selects one transport; it does not serve stdio and HTTP simultaneously.

Reusing the file does not make a Google grant portable between modes. Stdio local identities and HTTP Google identities use different principal namespaces, and each stored Gmail connection is tied to the Google OAuth client ID that created it. Switching modes/client types can therefore require a separate authorization.

The current supported deployment is one application process using a database on local persistent disk. Do not place the SQLite files on a network filesystem and do not run multiple replicas or simultaneous stdio/HTTP processes against the same database. There is no PostgreSQL adapter, distributed lock, or multi-process coordination yet.

The schema isolates remote Google connections by authenticated principal, and multiple Google users can use one hosted process. It currently has one logical default tenant, one Gmail account per principal, and no organization administration, domain allowlist, or tenant-provisioning UI. Host/path → tenant routing and tenant-bound clients/grants are future work. “Multi-user capable” should not be confused with a complete enterprise multi-tenancy control plane.

Data storage

Google access and refresh tokens and pending Google OAuth secrets are encrypted with AES-256-GCM and associated data. Google provider state, MCP authorization codes/tokens, and browser-session binding material are stored as hashes. The MCP client’s own state is stored as grant metadata so it can be echoed to the client. Principal email addresses, Google subject identifiers, scopes, DCR client metadata, and other grant metadata are not encrypted as secrets.

Without MCP_GMAIL_ENCRYPTION_KEY, a 32-byte key is generated at <database-path>.key. On POSIX systems the code requests mode 0600; Windows ACLs must be managed separately. Losing the key makes encrypted Google credentials unreadable. Copying the live database without its WAL can produce an inconsistent backup; stop the process or use a SQLite-aware backup procedure.

The server does not persist Gmail message bodies or attachment content as a mailbox cache. They pass through memory for the requested operation.

Development

npm run check
npm test
npm run build

Logs are structured JSON on stderr so stdio stdout remains reserved for JSON-RPC.

Security

See SECURITY.md for the threat model, hosted checklist, secrets guidance, DCR caveats, untrusted email-content warning, and known limitations.

License

MIT

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Servers

  • A
    license
    -
    quality
    B
    maintenance
    Local stdio MCP server that mirrors the public Gmail MCP server tool surface while calling the Gmail REST API directly, enabling email management tasks like creating drafts, searching threads, and managing labels.
    Last updated
    MIT
  • A
    license
    -
    quality
    C
    maintenance
    Streamable HTTP MCP server for Gmail that enables AI agents to search threads, read messages, manage drafts, and organize the inbox via OAuth authentication.
    Last updated
    417
    10
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    A minimal MCP server for sending plain-text emails and managing Gmail user labels over stdio, using only gmail.send and gmail.labels OAuth scopes.
    Last updated
    4
    234
    MIT

View all related MCP servers

Related MCP Connectors

  • Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.

  • Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.

  • Hosted email MCP for AI agents with inboxes, send/receive, memory, recovery, and credits.

View all MCP Connectors

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/flujo-app/mcp-gmail-oauth'

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