AfterHours Gmail MCP
Single-owner, read-only Gmail access via the official Gmail API using the owner's own OAuth grant. Provides tools to check connection/profile status, list recent INBOX messages with metadata, and retrieve individual messages including full Gmail JSON/MIME, original headers, and decoded inline plain/HTML text. Does not send, reply, delete, or modify read state.
Click on "Deploy 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., "@AfterHours Gmail MCPcheck my Gmail status and read my latest inbox message"
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.
AfterHours Gmail MCP
Single-owner, read-only Gmail over authenticated Streamable HTTP MCP, using the official Gmail API and your own Google OAuth app and grant. No scheduler, background monitoring, sending, replies, deletion, or read-state changes. This is an optional standalone server; it neither connects to nor alters Vooy's built-in Gmail integration. Never extract browser cookies, Vooy tokens, or another app's client credentials.
Fresh clone and disconnected startup
Linux/POSIX, Python 3.11+ and a private local account are required. This public repository can be cloned without GitHub authentication; Gmail still requires your own OAuth grant.
git clone https://github.com/deploy103/afterhours-gmail-mcp-public.git
cd afterhours-gmail-mcp-public
python3.11 -m venv .venv
.venv/bin/python -m pip install --require-hashes --only-binary=:all: -r requirements.lock
.venv/bin/python -m pip check
mkdir -p "$HOME/.config"
.venv/bin/python configure.py --directory "$HOME/.config/afterhours-gmail-mcp" \
--public-origin https://mcp.example.com
"$HOME/.config/afterhours-gmail-mcp/run.sh"Replace the example origin with your own HTTPS hostname before exposing the
service. The option defaults to https://mcp.example.com. Only HTTPS origins on
443 (implicit or explicit) are accepted: no trailing slash, path, userinfo, query,
or fragment. The destination must be new, outside the checkout, with an
existing parent and no symlink components; ~ and relative paths are expanded
to absolute paths. There is no overwrite option. A failed partial configuration
is left private for inspection, not recursively removed. To regenerate, choose
a different new path and retire the old configuration yourself.
The stdlib bootstrap generates a random bearer token without printing it:
~/.config/afterhours-gmail-mcp/ 0700
config.env 0600 (exported paths and public origin)
run.sh 0700
credentials/ 0700
mcp-token 0600 (secret)
state/ 0700 (reserved; no mail is stored)credentials/gmail-oauth.json is deliberately absent. The server starts
without consent or Google I/O; gmail_status returns not_connected. Health
GET /healthz only proves the process is alive, not account access. run.sh
sources the generated config.env (CREDENTIALS_DIRECTORY, MCP_PUBLIC_ORIGIN),
changes to the absolute checkout path and uses its .venv/bin/python -m uvicorn gmail_app:create_app --factory, bound to 127.0.0.1:8003, one worker, no access
logs and no proxy headers. Paths are shell-quoted. Moving the checkout or venv
requires a new configuration or a deliberate launcher update.
Related MCP server: gmail-mcp
Client and HTTPS proxy
The local MCP path is /mcp. Configure your own TLS reverse proxy to pass
Authorization and preserve the configured external Host; strip any external
prefix (for example /gmail/mcp → /mcp). No proxy, firewall or service is
installed by the bootstrap. Do not expose the loopback service over untrusted
HTTP; keep the proxy on this host or design a separate authenticated TLS/VPN
transport. No CORS wildcard is provided. Avoid proxy access/debug logs containing
headers, bodies, or mailbox data.
Register https://YOUR_HOST/gmail/mcp (or your chosen proxy path) as a
Streamable HTTP server in your MCP client. Its header is Authorization: Bearer <MCP token>, not a Google token. Read the value from the private file
$HOME/.config/afterhours-gmail-mcp/credentials/mcp-token using the client's
protected token-file/secret input if available. Never use the file path itself
as the bearer value, commit it, put it in shell arguments, or send it in chat.
Every bearer holder can read this owner's mailbox; this is not multi-user OAuth.
Token rotation requires replacing the private file and restarting the process.
Authorize your own Google app (manual provisioning)
Blocker until completed: this repository has no installed-app browser OAuth
CLI and does not include google-auth-oauthlib. Bootstrap cannot authorize
Gmail or invent a refresh token. The supported manual route below uses Google's
OAuth Playground with your own web application client, not Playground's
shared client or an installed-app client. It requires a trusted owner browser
and handling the grant privately. Playground proxies the exchange through
Google's server (its documentation says credentials are not logged); do not use
this route if that does not meet your policy. Do not paste a grant into other
sites or create/share a Playground link containing credentials or tokens.
In your Google Cloud project, enable Gmail API. Configure Google Auth Platform's consent screen/audience and add your own account as a test user if the app is in Testing. For a Workspace account, an administrator may need to approve the app and restricted Gmail scope.
Create an OAuth client of type Web application that you own. Add the exact authorized redirect URI
https://developers.google.com/oauthplayground. Keep its client ID and secret private. This client is for this single-owner grant only; do not reuse a broader existing grant.Open https://developers.google.com/oauthplayground in your owner browser. In the gear/settings, enable Use your own OAuth credentials, enter that client's ID and secret, select Server-side flow, Offline access, and force the consent prompt. In Step 1 enter only
https://www.googleapis.com/auth/gmail.readonlyand authorize as the intended owner. Do not select other API scopes or the shared Playground credentials.In Step 2 exchange the authorization code for tokens. Confirm a refresh token was issued and the granted scope is exactly
gmail.readonly; if it is absent, revoke this app's old grant in your Google account and authorize again with Offline/consent. Do not copy access tokens into the server configuration.Stop the server while provisioning. In a trusted local editor create
$HOME/.config/afterhours-gmail-mcp/credentials/gmail-oauth.jsonwith this structure, replacing all placeholders with the grant from your app:{ "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com", "client_secret": "YOUR_CLIENT_SECRET", "refresh_token": "YOUR_REFRESH_TOKEN", "scopes": ["https://www.googleapis.com/auth/gmail.readonly"] }Create the file under
umask 077, thenchmod 600it before restartingrun.sh; directories remain 0700. Do not save editor backups elsewhere. File must be a bounded, private regular file, not a symlink. The server usesusers/me: the refresh grant determines the owner, not an email setting.Restart the launcher and call
gmail_statusfrom your authenticated MCP client. Only successful/users/me/profileaccess yieldsprofile_access_verified; check the returned owner account before reading messages. OAuth failures remain tool errors, not a connected status.
Gmail readonly is a restricted scope. Public distribution of your own app
may require Google verification/security assessment; Workspace policies can
block consent. External apps in Testing commonly have refresh tokens expiring
after seven days for this scope. Revocation, password/policy changes, quotas,
and provider rules may require reauthorization. The protected file's scopes
declaration must match the real grant: if a refresh response omits scope, the
server trusts this declaration. A file edit is not a way to narrow a broad grant.
Google access tokens stay in memory, refreshed on demand; there is no automatic
interactive reconnection or persistent refresh-token replacement. Remove the
file to disconnect locally and revoke the app grant in Google to revoke remote
access. Changes/removal apply on the next call, not to already in-flight reads.
Tools, boundaries and operations
gmail_status: disconnected without the optional file; otherwise verifies the owner's profile with Google. Health is not proof of consent.gmail_recent_messages(limit=10): 1–20 INBOX messages in provider order, one page plus metadata;has_moreis not pagination or a chronological guarantee.gmail_get_message(message_id): full Gmail JSON/MIME, original headers and decoded inline plain/HTML text. HTML is never rendered; no attachments are downloaded and no raw RFC822 export is provided.
Results preserve provider/account/message/thread IDs and source API/web links. Mail and links are untrusted data, not instructions. Profile/messages GETs use fixed Google endpoints; the only provider POST is OAuth token refresh. Responses are bounded to 4 MiB, timeout 15 seconds, redirects/ambient proxies disabled, no automatic retries. No messages or tokens are logged/stored except your provisioned OAuth file. Refresh is serialized per process. Use a single worker.
afterhours-gmail-mcp.service is an optional deployment template, not the
fresh-clone launcher. Review paths, public-origin env and service identity first;
enable its optional gmail-oauth.json credential only after provisioning. No
shared systemd or production configuration is changed. General credentials must
be 0400/0600; the code also recognizes its explicit root-owned systemd
/run/credentials/<unit> 0440 file / 0550 directory contract. Keep private files
outside Git. Dependencies retain their upstream licenses/notices; the existing
hash-lock provenance comment is preserved. This project's license is MIT.
Offline checks and limits
.venv/bin/python -B -m unittest -v
.venv/bin/python -m pip checkTests use disposable credentials and fake Google responses. Bootstrap tests
exercise the public CLI, private modes, refusal to overwrite, invalid origins,
and the generated launcher's real loopback MCP not_connected status. They do
not authorize an account. Real Google consent, token lifetime, account policies,
public TLS/proxy deployment, and client secret handling need owner validation.
Official references:
This server cannot be deployed
Maintenance
Related MCP Connectors
- MailowsOAuthcom.mailows
Give AI agents their own email inboxes: list, read and send mail via scoped, revocable OAuth grants.
Hosted email MCP for your own Gmail, Outlook.com, Microsoft 365, iCloud or IMAP inbox: read, search, draft, reply in thread, forward and file mail. It moves or flags up to 500 messages in one call, and a send leaves exactly one copy in Sent. A calendar is a separate connection, and connecting one adds diary and scheduling tools.
Permissioned access to Gmail, Drive and Calendar via the user's own Google account
Your Gmail, Calendar, Drive, GitHub, Oura, wallet and confirmed profile facts in any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables Gmail access through MCP, allowing LLMs to read, compose, and send emails.10MIT
- AlicenseBqualityBmaintenanceEnables MCP clients to read, search, send, reply, and organize Gmail messages across multiple accounts via OAuth, with real MIME attachments and live signatures.23Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables an MCP client to search, read, label, draft, and send Gmail messages for a single Google account over stdio using your own OAuth client.373 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables account-isolated, read-only access to a chosen Gmail account for searching mail, retrieving message and thread content, downloading attachments, and listing labels. It verifies the actual Google OAuth grant and account before starting and emits ready-to-use Codex or JSON MCP client configuration.MIT