Skip to main content
Glama
deploy103

AfterHours Gmail MCP

by deploy103

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.

  1. 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.

  2. 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.

  3. 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.readonly and authorize as the intended owner. Do not select other API scopes or the shared Playground credentials.

  4. 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.

  5. Stop the server while provisioning. In a trusted local editor create $HOME/.config/afterhours-gmail-mcp/credentials/gmail-oauth.json with 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, then chmod 600 it before restarting run.sh; directories remain 0700. Do not save editor backups elsewhere. File must be a bounded, private regular file, not a symlink. The server uses users/me: the refresh grant determines the owner, not an email setting.

  6. Restart the launcher and call gmail_status from your authenticated MCP client. Only successful /users/me/profile access yields profile_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_more is 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 check

Tests 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:

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables Gmail access through MCP, allowing LLMs to read, compose, and send emails.
    10
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables MCP clients to read, search, send, reply, and organize Gmail messages across multiple accounts via OAuth, with real MIME attachments and live signatures.
    23
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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