mcp-gmail-oauth
Provides tools for interacting with a Gmail account, enabling search, read, send, reply, forwarding, draft management, label handling, message state changes, trash operations, and bounded attachment downloads.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-gmail-oauthsearch my inbox for unread emails from my boss"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-gmail-oauth
A self-hostable Gmail MCP server with one executable, one SQLite storage implementation, and two selectable transports:
stdiofor a local MCP client;Streamable HTTP for localhost development or a remotely hosted MCP endpoint (
remoteis an alias forhttp).
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_messagesruns a Gmail search when the tool is called;gmail_get_messageandgmail_get_threadfetch 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 |
| Local process/stdio trust; no transport bearer token | Experimental negotiated stdio authorization extension, Google Desktop OAuth client, PKCE, ephemeral loopback callback |
| 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 buildRun the compiled executable with:
node dist/cli.js --helpThe 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:49152The 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/callbackExamples:
http://127.0.0.1:3000/oauth/google/callback
https://gmail-mcp.example.com/oauth/google/callbackScheme, 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-profileExample 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/callbackThen 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-httpThe 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.jsThe externally configured Google callback must be:
https://gmail-mcp.example.com/oauth/google/callbackProduction requirements:
expose only HTTPS publicly and redirect or reject public HTTP;
preserve the external
Hostheader, 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_KEYfrom 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 |
|
Protected-resource metadata |
|
Authorization-server metadata |
|
Dynamic client registration |
|
Authorization |
|
Token |
|
Revocation |
|
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 |
|
Compose/send |
|
Drafts |
|
Labels/state |
|
Trash |
|
Attachments |
|
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 |
|
|
|
SQLite path |
|
|
|
Google client JSON |
|
| required |
Stdio local identity |
|
|
|
HTTP listener |
|
|
|
External origin |
|
| derived listener origin in HTTP mode |
Insecure loopback HTTP |
|
|
|
Requests per IP/minute |
|
|
|
Requests total/minute |
|
|
|
DCR requests per IP/hour |
|
|
|
DCR requests total/hour |
|
|
|
In-flight requests per IP |
|
|
|
In-flight requests total |
|
|
|
Trusted proxy hops |
|
|
|
Unverified DCR client cap |
|
|
|
DCR inactive age |
|
|
|
Extra allowed hostname | repeat |
| public-origin hostname |
Data encryption key | no CLI option |
| generated sidecar key when allowed |
Log level |
|
|
|
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.jsSee .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 buildLogs 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
This server cannot be installed
Maintenance
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
- Alicense-qualityBmaintenanceLocal 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 updatedMIT
- Alicense-qualityCmaintenanceStreamable HTTP MCP server for Gmail that enables AI agents to search threads, read messages, manage drafts, and organize the inbox via OAuth authentication.Last updated41710MIT
- Alicense-qualityDmaintenanceStreamable HTTP MCP server for Gmail enabling search, read, draft management, and inbox organization.Last updated24ISC
- AlicenseAqualityCmaintenanceA 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 updated4234MIT
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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