MCP server exposing Google Chat to MCP-compatible clients.
Project description
google-chat-mcp
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 asget_spacegchat://spaces/{space_id}/messages/{message_id}— same shape asget_messagegchat://spaces/{space_id}/threads/{thread_id}— same shape asget_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:
- Create a Google Cloud project.
- Enable the Google Chat API, People API, and Google OIDC / userinfo.
- Configure the OAuth consent screen (upload your own app name, logo, contact email).
- Add the v2 scopes listed above.
- Add yourself as a test user if the consent screen is in "Testing" state.
- 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:
Originheader validation on every request (DNS-rebinding defense).- Localhost-only bind for dev (
127.0.0.1). Use0.0.0.0only behind a real reverse proxy. MCP-Protocol-Version: 2025-06-18header 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.jsonin 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.mdfor rotation procedures. - No hardcoded client-specific logic.
allowed_client_redirectsdefaults 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a9a31662f0a068f7efce8f94d9c890bd44f2078f2e1518371a274619f4c52b1
|
|
| MD5 |
84e51f44cc5e52ddbc260ef141a95c4c
|
|
| BLAKE2b-256 |
6b6da601e7c3ccf0b720284861590755d224d02214f7aac0f753585a8bcd93ec
|
Provenance
The following attestation bundles were made for google_chat_mcp-1.0.1.tar.gz:
Publisher:
release.yml on mmedum/google-chat-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
google_chat_mcp-1.0.1.tar.gz -
Subject digest:
5a9a31662f0a068f7efce8f94d9c890bd44f2078f2e1518371a274619f4c52b1 - Sigstore transparency entry: 1369353477
- Sigstore integration time:
-
Permalink:
mmedum/google-chat-mcp@f173e2313431e85aa27fc8934c4f75490eb47cc2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/mmedum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f173e2313431e85aa27fc8934c4f75490eb47cc2 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07cce581de0a3c148ed20819213a52d9e2f259cc337812e60910f7396acf8c85
|
|
| MD5 |
49899b46e45e5ffab6e9d0ff836391b1
|
|
| BLAKE2b-256 |
429a32f3a38a5b92d7e9ea5c64ede5817011f83a98a4ba047c1e512bd6b37ea6
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
google_chat_mcp-1.0.1-py3-none-any.whl -
Subject digest:
07cce581de0a3c148ed20819213a52d9e2f259cc337812e60910f7396acf8c85 - Sigstore transparency entry: 1369353689
- Sigstore integration time:
-
Permalink:
mmedum/google-chat-mcp@f173e2313431e85aa27fc8934c4f75490eb47cc2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/mmedum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f173e2313431e85aa27fc8934c4f75490eb47cc2 -
Trigger Event:
push
-
Statement type: