Skip to main content

MCP server exposing Google Chat to MCP-compatible clients.

Project description

google-chat-mcp

CI PyPI Latest release Container image License: Apache 2.0 Python

A production-grade MCP server that exposes Google Chat to any MCP-compatible client. Read, search, and reply to spaces and DMs through natural-language prompts in your MCP client of choice.

Two transports ship in this repo:

  • Stdio (recommended for individual users) — install from PyPI with uv tool install google-chat-mcp, run a one-time OAuth login against your own Google account, then launch the server as a subprocess under Claude Code, opencode, Cursor, etc.
  • Streamable HTTP (shared / hosted deployments) — self-host in Docker for teams; the MCP client connects over HTTPS and walks the OAuth flow per-user against the operator's Google app.

Per-user OAuth against Google Workspace or consumer Google accounts. No service accounts, no domain-wide delegation, no publishing step.

Which mode do I want?

Pick one path and jump straight to its setup.

Stdio HTTPS
Who runs it You, on your own machine A team operator, on a server
Users per install One (the OS user) Many (each authenticates per-user)
Prereqs uv + your own Google Cloud project Public HTTPS host, Docker, reverse proxy, your own Google Cloud project
Auth surface Loopback OAuth on 127.0.0.1; Fernet-encrypted tokens at ~/.config/google-chat-mcp/ FastMCP-issued JWT in front of Google OAuth proxy; Fernet-encrypted refresh tokens on disk
Jump to setup Stdio mode HTTPS mode

Tool surface

Tool Purpose Required scope
list_spaces List DMs, group chats, named spaces. Optional limit and space_type chat.spaces.readonly
find_direct_message Resolve an email to a DM space ID (creates one on miss) chat.spaces.readonly + chat.spaces.create
create_group_chat Create an unnamed group chat with 2-20 members; dry_run previews the body chat.spaces.create
create_space Create a named space (display_name required, 1-20 members); dry_run previews the body chat.spaces.create
update_space Rename a space or edit its description (spaces.patch); at least one field required; dry_run previews chat.spaces (restricted)
send_message Post a text message. Optional thread_name reply; dry_run previews the payload without posting chat.messages.create
update_message Edit the text of a message you sent (updateMask=text); dry_run previews chat.messages (restricted)
delete_message Delete a message by resource name; idempotent (deleted=false on re-delete); missing-scope still raises chat.messages (restricted)
get_messages Read recent messages, newest-first. Senders resolved via People API (24h cache) chat.messages.readonly
get_space Fetch one space by ID chat.spaces.readonly
list_members Humans + groups in a space; humans resolved to email via People API chat.memberships.readonly + directory.readonly
add_member Invite a user to a space by email; ToolError on duplicate; dry_run previews chat.memberships
remove_member Delete a membership by resource name; idempotent (removed=false on re-delete) chat.memberships
search_people Hybrid lookup over Workspace directory + caller contacts; back-fills the email cache directory.readonly + contacts.readonly
whoami Authenticated user's identity (sub, email, display name) via OIDC /userinfo openid email profile
get_thread All messages in one thread, oldest-first chat.messages.readonly
get_message One message by resource name, with reaction summaries inline chat.messages.readonly
add_reaction Add a Unicode-emoji reaction to a message (idempotent) chat.messages.reactions
remove_reaction Delete by resource name, or by (message, emoji, user) via server-side filter chat.messages.reactions
list_reactions Paginated reactions on a message chat.messages.reactions
search_messages Client-side exact / regex scan of one space; always pass space_id and created_after chat.messages.readonly

Three MCP Resources are dual-exposed for host-UI inclusion:

  • gchat://spaces/{space_id} — same shape as get_space
  • gchat://spaces/{space_id}/messages/{message_id} — same shape as get_message
  • gchat://spaces/{space_id}/threads/{thread_id} — same shape as get_thread

Dry-run on send_message

Set dry_run: true on any send_message call to preview the exact JSON body the server would post. The tool returns rendered_payload and does NOT hit the Chat API. Intended for ungated Agent-SDK loops and MCP clients running with bypassPermissions — preview, inspect, then re-invoke without dry_run to actually post. Rate-limit and audit still fire on the dry run.

Granular-consent errors

Google's January 2026 granular-consent rollout lets users toggle individual scopes at grant time. When a tool call fails because a scope is missing, the server returns a structured ToolError naming the exact scope:

Missing required OAuth scope: https://www.googleapis.com/auth/chat.messages.reactions.
Re-run `google-chat-mcp login` (stdio) or re-consent in your MCP client (HTTPS).

Stdio mode — individual users

1. One-time GCP setup (~15 minutes)

See docs/gcp-setup.md for the full walkthrough. Summary:

  1. Create a Google Cloud project.
  2. Enable the Google Chat API, People API, and Google OIDC / userinfo.
  3. Configure the OAuth consent screen (upload your own app name, logo, contact email).
  4. Add the v2 scopes listed above.
  5. Add yourself as a test user if the consent screen is in "Testing" state.
  6. Create an OAuth 2.0 Client ID of type Desktop app and download client_secret.json.

2. Install and log in

# Recommended: install from PyPI
uv tool install google-chat-mcp

# Or: install from a git tag (pre-release / unreleased fixes)
# uv tool install git+https://github.com/mmedum/google-chat-mcp@vX.Y.Z

# Or: install from a local clone for dev / custom builds
# git clone https://github.com/mmedum/google-chat-mcp && cd google-chat-mcp
# uv tool install --from . google-chat-mcp

google-chat-mcp login --client-secret ./client_secret.json

The login command:

  • Prints the authorization URL to stdout (so it works on headless machines).
  • Opens your system browser (or falls back to "paste this URL" if it can't).
  • Receives the callback on 127.0.0.1:<random>, exchanges the code (PKCE + state throughout).
  • Stores tokens at ~/.config/google-chat-mcp/tokens.json (0600, Fernet-encrypted).

Log out with google-chat-mcp logout — revokes the refresh token at Google and deletes local files.

3. Wire into your MCP client

Point your client at the installed binary. Both names work:

mcp-server-google-chat        # primary, matches Anthropic's mcp-server-* convention
google-chat-mcp               # alias, for discoverability

Example Claude Code entry:

{
  "mcpServers": {
    "google-chat": {
      "command": "mcp-server-google-chat"
    }
  }
}

See docs/gcp-setup.md for the full one-time flow.


HTTPS mode — shared / hosted deployment

For teams who want a shared deployment (one Google app, many users). Requires a public HTTPS hostname, Docker, and a reverse proxy in front of port 8000.

1. Required env

export GCM_BASE_URL="https://chat-mcp.example.com"
# CSV of OAuth-callback URLs your MCP client(s) use. One entry per client.
#   Claude: https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback
#   Cursor: https://cursor.com/oauth/mcp/callback
export GCM_ALLOWED_CLIENT_REDIRECTS="https://your-client.example.com/oauth/callback"

2. Secrets

mkdir -p secrets
printf '%s' 'paste client id here'     > secrets/google_client_id
printf '%s' 'paste client secret here' > secrets/google_client_secret
python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())' \
    > secrets/fernet_key
python -c 'import secrets; print(secrets.token_urlsafe(48))' \
    > secrets/jwt_signing_key
python -c 'import secrets; print(secrets.token_hex(32))' \
    > secrets/audit_pepper
chmod 600 secrets/*

Docker Compose picks these up as /run/secrets/GCM_<name> inside the container.

3. Run

docker compose up -d
docker compose logs -f mcp

compose.yml pulls the published multi-arch image (ghcr.io/mmedum/google-chat-mcp:0.2) by default — linux/amd64 + linux/arm64, published with SBOM and SLSA provenance attestations on every v*.*.* tag. For local dev builds, swap the image: line for the commented build: block in compose.yml.

4. Connect your MCP client

Add a custom connector at https://chat-mcp.example.com/mcp. The client initiates OAuth; users grant scopes once, and the client stores the MCP bearer token for subsequent tool calls.

Transport-security notes (HTTPS)

FastMCP 3.x enforces these per the MCP spec (2025-06-18). You shouldn't need to touch them, but don't disable them:

  • Origin header validation on every request (DNS-rebinding defense).
  • Localhost-only bind for dev (127.0.0.1). Use 0.0.0.0 only behind a real reverse proxy.
  • MCP-Protocol-Version: 2025-06-18 header required; server returns 400 on invalid/missing version.
  • Authentication (the FastMCP-issued JWT via GoogleProvider) is mandatory — never expose the HTTP endpoint unauthenticated.

Local development with a tunnel (HTTPS mode)

MCP custom connectors call your /mcp endpoint from the public internet, so pure localhost doesn't work end-to-end for HTTPS-mode dev. Front it with Cloudflare Tunnel / ngrok / Tailscale Funnel.

# In one terminal
cloudflared tunnel --url http://localhost:8000
# Add the printed URL to your Google OAuth client's redirect list,
# then in another terminal:
export GCM_BASE_URL="https://<random>.trycloudflare.com"
uv run python -m src.server

Quick-tunnel URLs rotate on restart; use a named tunnel bound to a domain you own for a stable hostname.


Deployer invariants

  • No image rebuild. Each deployer supplies their own Google app credentials at runtime (mounted secrets in HTTPS, client_secret.json in stdio). Pull the published image or install from source; configure; run.
  • No centralized deployment. Each deployer owns their Google app, their tokens, and their rollout cadence. Compromises of a specific deployment's credentials or tokens are the deployer's responsibility — see docs/runbook.md for rotation procedures.
  • No hardcoded client-specific logic. allowed_client_redirects defaults to empty; operators configure it per their MCP client. The server is intentionally client-agnostic.

Architecture

MCP client ──(stdio OR HTTP)──► FastMCP
                                 ├── Tools + Resources (see table above)
                                 ├── chat_client — shared httpx.AsyncClient with retry/backoff
                                 ├── SQLite (audit_log, user_directory cache)
                                 ├── Rate limiter (60/min per user)
                                 └── Auth resolver (transport-specific)
                                     ├── HTTPS: FastMCP.GoogleProvider (PKCE, state, JWT issuance)
                                     └── stdio: local Fernet-encrypted token store

src/server.py is the HTTPS entry (builds the GoogleProvider). src/stdio.py is the stdio entry (loopback login + local token store). Both hand the shared build_app(settings, resolver=, auth=) composition root identical tool and resource registration. See docs/architecture.md for the full request-flow diagram, per-transport wiring, and the deliberate design decisions (no hand-rolled OAuth, no server-side message-body mutation, etc.).

Local development

uv sync --extra dev
uv run pytest                  # full suite, 80% coverage gate
uv run ruff check .
uv run ty check
uv run python -m src.server    # HTTPS (needs GCM_* env)
uv run python -m src.stdio login --client-secret ./client_secret.json
uv run mcp-server-google-chat  # stdio serve

Pre-commit hooks: uv run pre-commit install (includes gitleaks).

Operations

docs/runbook.md: missing-scope errors, rotation procedures for Fernet key / GCP client secret / refresh tokens, recovery from common mis-states.

Security

See SECURITY.md for how to report vulnerabilities.

Versioning and support

Tool names and I/O shapes are semver-stable from v1.0 onward. Breaking changes get a major-version bump and ship with at least one minor-version deprecation warning before removal. We support the Python versions listed in pyproject.toml under requires-python.

Code of conduct

See CODE_OF_CONDUCT.md — Contributor Covenant 3.0.

License

Apache 2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

google_chat_mcp-1.0.1.tar.gz (236.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

google_chat_mcp-1.0.1-py3-none-any.whl (87.1 kB view details)

Uploaded Python 3

File details

Details for the file google_chat_mcp-1.0.1.tar.gz.

File metadata

  • Download URL: google_chat_mcp-1.0.1.tar.gz
  • Upload date:
  • Size: 236.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for google_chat_mcp-1.0.1.tar.gz
Algorithm Hash digest
SHA256 5a9a31662f0a068f7efce8f94d9c890bd44f2078f2e1518371a274619f4c52b1
MD5 84e51f44cc5e52ddbc260ef141a95c4c
BLAKE2b-256 6b6da601e7c3ccf0b720284861590755d224d02214f7aac0f753585a8bcd93ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for google_chat_mcp-1.0.1.tar.gz:

Publisher: release.yml on mmedum/google-chat-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file google_chat_mcp-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: google_chat_mcp-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 87.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for google_chat_mcp-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 07cce581de0a3c148ed20819213a52d9e2f259cc337812e60910f7396acf8c85
MD5 49899b46e45e5ffab6e9d0ff836391b1
BLAKE2b-256 429a32f3a38a5b92d7e9ea5c64ede5817011f83a98a4ba047c1e512bd6b37ea6

See more details on using hashes here.

Provenance

The following attestation bundles were made for google_chat_mcp-1.0.1-py3-none-any.whl:

Publisher: release.yml on mmedum/google-chat-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page