@zapsign-private/gepeto-mcp
v0.3.1
Published
MCP server for Google Chat. Acts as the authenticated user (OAuth) or as a bot (service account).
Maintainers
Readme
Gepeto MCP — Google Chat connector
A small MCP server that wraps the Google Chat REST API and authenticates either as a real user (OAuth) or as a bot (service account, Gepeto-style). Built because Google's official chatmcp.googleapis.com is gated behind the Workspace Developer Preview Program — this gives you the same surface (and more) without that gate.
TL;DR
npm install -g @zapsign-private/gepeto-mcpThen:
- Make sure the OAuth client has
http://localhost:53682/callbackin its Authorized redirect URIs (Cloud setup). - Run
gepeto-mcp-authonce withGOOGLE_CHAT_OAUTH_CLIENT_IDandGOOGLE_CHAT_OAUTH_CLIENT_SECRETset — it opens a browser, prints a refresh token to your terminal. - Drop a
gepetoentry into your MCP client config with all three env vars (Register with your client). - Smoke-test the binary, then fully restart the client.
The rest of this doc walks through each step with the gotchas we've actually hit.
Why this exists
Google ships a remote MCP at chatmcp.googleapis.com/mcp/v1 but the backend rejects every call from workspaces not enrolled in the Developer Preview Program — even with valid OAuth and the right scopes, every call returns 404. This server talks directly to the Chat REST API (which is GA) and works today.
Auth modes
Pick whichever fits the use case. You can have both env vars set on the same install — OAuth wins.
| Mode | When | Identity | What it can do | |---|---|---|---| | OAuth | "I want Cowork/Claude to act as me in Chat" | Authenticated user | Read/write across all spaces the user is in, including DMs | | Service account | Bot-side automations: deploy alerts, CI digests | The Gepeto Chat app | Read/write only in spaces Gepeto has been added to |
Tools
| Tool | OAuth | Bot |
|---|---|---|
| list_spaces | ✓ | ✓ (only spaces Gepeto is in) |
| get_space | ✓ | ✓ |
| find_direct_message | ✓ | ✗ |
| list_messages | ✓ | ✓ |
| get_message | ✓ | ✓ |
| send_message | ✓ | ✓ |
| send_card | ✓ | ✓ |
| update_message | ✓ (own only) | ✓ (own only) |
| delete_message | ✓ (own only) | ✓ (own only) |
| search_messages | ✓ | ✓ |
| list_members / get_member | ✓ | ✓ |
| add_reaction / list_reactions / remove_reaction | ✓ | ✓ |
| upload_file | ✓ | ✓ |
| get_attachment / download_attachment | ✓ | ✓ |
Attachments
send_message accepts attachment_paths: string[] — local file paths the
server uploads to Chat before posting. Files are uploaded sequentially, one
media call each, then referenced in the message body.
{
"space": "spaces/AAAA26LxVJ8",
"text": "Postmortem attached:",
"attachment_paths": ["C:\\reports\\incident-2026-05-10.pdf"]
}upload_file does the upload step in isolation when you want to reuse the
resulting attachmentDataRef across several messages. get_attachment
returns metadata for a known attachment resource name; download_attachment
writes the binary content to a local path (passing a directory writes under
the original contentName).
Reactions
add_reaction accepts either a Unicode emoji directly ("👍") or a custom
Workspace emoji as "customEmoji:<uid>". list_reactions supports the Chat
API filter syntax — e.g. emoji.unicode = "👍" or user.name = "users/123".
remove_reaction accepts either the full reaction resource name, or
the (message_name, emoji) pair, in which case it looks up the matching
reaction first.
One-time Cloud project setup (per Workspace)
Skip this section if your Workspace already has an OAuth client wired up for this server.
1. Enable the Chat API
gcloud services enable chat.googleapis.com --project=PROJECT_IDYou don't need chatmcp.googleapis.com for this server.
2. OAuth consent screen scopes
In Google Auth Platform → Data Access → Add or Remove Scopes, add:
https://www.googleapis.com/auth/chat.spaces
https://www.googleapis.com/auth/chat.messages
https://www.googleapis.com/auth/chat.memberships3. Create the OAuth client
In Google Auth Platform → Clients → Create Client:
- Type: Web application
- Authorized redirect URIs — add both:
http://localhost:53682/callback— required bygepeto-mcp-auth. If this is missing, the consent screen returnsredirect_uri_mismatchand you cannot get a refresh token.https://claude.ai/api/mcp/auth_callback— only if you also wire this as a Claude custom connector. Optional.
Copy the Client ID and Secret — those go into GOOGLE_CHAT_OAUTH_CLIENT_ID / _SECRET.
Install
npm install -g @zapsign-private/gepeto-mcpThis puts two binaries on your PATH:
gepeto-mcp— the MCP server (stdio transport).gepeto-mcp-auth— one-time OAuth bootstrap that prints a refresh token.
Verify they resolved:
# Mac / Linux
which gepeto-mcp gepeto-mcp-auth
# Windows
where.exe gepeto-mcp ; where.exe gepeto-mcp-authAuthorize once (OAuth path)
You need a refresh token. The gepeto-mcp-auth command runs a local HTTP listener on port 53682, prints a Google consent URL, exchanges the authorization code, and prints the refresh token to stdout.
bash / zsh
GOOGLE_CHAT_OAUTH_CLIENT_ID="725957662152-tuif...apps.googleusercontent.com" \
GOOGLE_CHAT_OAUTH_CLIENT_SECRET="GOCSPX-..." \
gepeto-mcp-authPowerShell
$env:GOOGLE_CHAT_OAUTH_CLIENT_ID = "725957662152-tuif...apps.googleusercontent.com"
$env:GOOGLE_CHAT_OAUTH_CLIENT_SECRET = "GOCSPX-..."
gepeto-mcp-authcmd.exe
set GOOGLE_CHAT_OAUTH_CLIENT_ID=725957662152-tuif...apps.googleusercontent.com
set GOOGLE_CHAT_OAUTH_CLIENT_SECRET=GOCSPX-...
gepeto-mcp-authOpen the printed URL, sign in with the Google account that should authorize the integration, approve the three Chat scopes. The browser hits http://localhost:53682/callback, the terminal prints:
=== Refresh token (save to GOOGLE_CHAT_REFRESH_TOKEN) ===
1//0g...Copy that token — it goes into GOOGLE_CHAT_REFRESH_TOKEN. It's long-lived (until you revoke at https://myaccount.google.com/permissions or rotate the OAuth client secret).
If gepeto-mcp-auth says "No refresh_token returned", Google omitted it because this client has been authorized by the same user before. Revoke at the URL above and re-run.
Register with your MCP client
Claude Desktop
Edit (Mac) ~/Library/Application Support/Claude/claude_desktop_config.json or (Windows) %APPDATA%\Claude\claude_desktop_config.json. Note the filename is claude_desktop_config.json, not mcp_servers.json.
The most portable launch form across platforms (recommended):
Mac / Linux
{
"mcpServers": {
"gepeto": {
"command": "npx",
"args": ["-y", "@zapsign-private/gepeto-mcp"],
"env": {
"GOOGLE_CHAT_OAUTH_CLIENT_ID": "725957662152-tuif...apps.googleusercontent.com",
"GOOGLE_CHAT_OAUTH_CLIENT_SECRET": "GOCSPX-...",
"GOOGLE_CHAT_REFRESH_TOKEN": "1//0g..."
}
}
}
}Windows — wrap with cmd /c so the .cmd shim resolves correctly:
{
"mcpServers": {
"gepeto": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@zapsign-private/gepeto-mcp"],
"env": {
"GOOGLE_CHAT_OAUTH_CLIENT_ID": "725957662152-tuif...apps.googleusercontent.com",
"GOOGLE_CHAT_OAUTH_CLIENT_SECRET": "GOCSPX-...",
"GOOGLE_CHAT_REFRESH_TOKEN": "1//0g..."
}
}
}
}The npx -y form auto-picks up new versions you publish. If you'd rather pin to whatever's globally installed, point command straight at the binary instead — gepeto-mcp on Mac/Linux, or the absolute path to gepeto-mcp.cmd on Windows (e.g. C:\\Users\\<you>\\AppData\\Roaming\\npm\\gepeto-mcp.cmd).
If claude_desktop_config.json already has a top-level preferences block (Claude Desktop UI state), keep it — just add mcpServers as a sibling key. Don't replace the file wholesale or you'll wipe the UI state.
Claude Code
claude mcp add gepeto gepeto-mcp \
-e GOOGLE_CHAT_OAUTH_CLIENT_ID="725957662152-tuif...apps.googleusercontent.com" \
-e GOOGLE_CHAT_OAUTH_CLIENT_SECRET="GOCSPX-..." \
-e GOOGLE_CHAT_REFRESH_TOKEN="1//0g..."Cowork
Cowork uses the same claude_desktop_config.json as Claude Desktop on the same machine.
Other MCP clients
Anything that runs an MCP server over stdio works the same way — point command/args at the binary or npx -y @zapsign-private/gepeto-mcp and pass the three env vars through whatever the client's env-injection mechanism is.
Verify
Before restarting your client, run the binary directly with the same env vars to make sure auth works. This catches a bad refresh token or a misconfigured OAuth client without involving the MCP layer.
bash / zsh
GOOGLE_CHAT_OAUTH_CLIENT_ID=... GOOGLE_CHAT_OAUTH_CLIENT_SECRET=... GOOGLE_CHAT_REFRESH_TOKEN=... gepeto-mcpPowerShell
$env:GOOGLE_CHAT_OAUTH_CLIENT_ID = "..."
$env:GOOGLE_CHAT_OAUTH_CLIENT_SECRET = "..."
$env:GOOGLE_CHAT_REFRESH_TOKEN = "..."
gepeto-mcpYou should see gepeto-mcp: starting with auth mode = oauth on stderr and the process holding stdio open. That means env vars reached the process, the refresh token minted an access token, and the MCP server boot-handshake is ready. Hit Ctrl+C to exit.
If you see Configure either OAuth (...) and exit 1, the env vars didn't reach the process — check shell quoting.
Now restart your MCP client. For Claude Desktop, Quit from the tray/menu-bar icon, not just by closing the window — closing the window leaves the background process alive with the old config cached.
After restart, ask the model "list my Google Chat spaces" or invoke gepeto-list_spaces directly.
Troubleshooting
Tools don't appear in Claude Desktop after restart.
- Confirm the file is
claude_desktop_config.json(notmcp_servers.json, notconfig.json) at the standard path. - Confirm the JSON parses:
Get-Content $path -Raw | ConvertFrom-Json(PowerShell) orpython -m json.tool < $path(Mac/Linux). Claude Desktop silently ignores files that don't parse. - Open Settings → Developer (Desenvolvedor on pt-BR) and look for
gepetoand any error string — that's where startup failures surface. - Confirm you Quit (tray icon → Quit), not just closed the window.
- If Claude Desktop's UI later overwrites
claude_desktop_config.jsonand clobbers themcpServersblock, register the server through Settings → Developer → Add MCP Server instead of editing the JSON directly.
Configure either OAuth (...) and immediate exit when launching the server.
The three env vars aren't reaching the process. Most often: the env block in the client config is missing or has empty strings. Run the Verify step in a terminal — if that works there, the issue is in how your client passes env to the spawned process.
redirect_uri_mismatch from Google during gepeto-mcp-auth.
The OAuth client doesn't have http://localhost:53682/callback in its Authorized redirect URIs. Go to Google Auth Platform → Clients → your client → add it.
No refresh_token returned from gepeto-mcp-auth.
Google only issues a refresh token on the first consent for a given client+user. If this isn't your first time, revoke the app at https://myaccount.google.com/permissions and re-run. The bootstrap already passes prompt=consent, so simply re-running won't fix it without a revoke.
invalid_grant errors from the running server.
Refresh token was revoked, or it was minted against a different OAuth client. Re-run gepeto-mcp-auth and replace GOOGLE_CHAT_REFRESH_TOKEN.
PERMISSION_DENIED / 403 on list_spaces.
- OAuth path: the user lacks a Chat license in this Workspace, or the consent screen scopes don't include the three above.
- Service account path: Gepeto isn't in any spaces yet. Add the bot to the spaces you want it to see.
Windows: command: "gepeto-mcp" fails to spawn.
On Windows the binary is a gepeto-mcp.cmd shim and Claude Desktop's spawn path doesn't always resolve PATH the way a shell does. Use the cmd /c npx -y @zapsign-private/gepeto-mcp form, or the absolute path to the .cmd file.
Service-account / Gepeto-bot path
If instead of a user identity you want the server to act as the Gepeto bot:
GOOGLE_CHAT_CREDENTIALS=/absolute/path/to/gepeto-sa.json gepeto-mcpOptional: act as a user via DWD (requires Workspace admin to grant the SA's client ID the chat scopes):
GOOGLE_CHAT_CREDENTIALS=/path/to/sa.json \
[email protected] \
gepeto-mcpConstraints:
- Gepeto can only see/post in spaces it's been added to.
- DMs and
find_direct_messagearen't available with bot auth.
Develop locally / contribute
git clone https://github.com/ZapSign/google-chat-mcp.git
cd google-chat-mcp
npm install
npm run build
# bootstrap an OAuth refresh token (interactive)
GOOGLE_CHAT_OAUTH_CLIENT_ID=... GOOGLE_CHAT_OAUTH_CLIENT_SECRET=... npm run auth
# run the MCP server from source
GOOGLE_CHAT_OAUTH_CLIENT_ID=... \
GOOGLE_CHAT_OAUTH_CLIENT_SECRET=... \
GOOGLE_CHAT_REFRESH_TOKEN=... \
npm run devTo use a local checkout with Claude Desktop instead of the published package, point command at the absolute path to node and args at the built entrypoint:
{
"mcpServers": {
"gepeto": {
"command": "node",
"args": ["/absolute/path/to/google-chat-mcp/dist/index.js"],
"env": {
"GOOGLE_CHAT_OAUTH_CLIENT_ID": "...",
"GOOGLE_CHAT_OAUTH_CLIENT_SECRET": "...",
"GOOGLE_CHAT_REFRESH_TOKEN": "..."
}
}
}
}Run npm run build after every source change so dist/ is fresh.
Roadmap
- Streamable HTTP transport for hosted multi-user deploys
- Per-user OAuth in a hosted variant
- Drive-attachment pass-through (Chat sometimes returns Drive file refs)
- Bulk message export
Changelog
0.3.1
- Fix
send_messagerejecting OAuth user-identity uploads. The Chat media API returnsattachmentDataRef.attachmentUploadTokenfor user-identity callers (notresourceName), so 0.3.0 threw on every successful upload. Accept either field.
0.3.0
send_messageacceptsattachment_pathsfor inline file uploadsupload_fileexposes the upload step on its ownget_attachment/download_attachmentfor reading binary contentadd_reaction/list_reactions/remove_reaction
0.2.0
- Dual-mode auth (OAuth user-identity + service-account/bot)
find_direct_message,send_card,search_messages,list_members/get_membergepeto-mcp-authinteractive bootstrap binary
0.1.0
- Initial release
