Back to Browse

Google Chat MCP Server

by Mmedum
Developer ToolsLow Risk10.0MCP RegistryLocal
Free

Server data from the Official MCP Registry

Google Chat as MCP tools: read, search and write to your spaces, direct messages and sidebar.

About

Google Chat as MCP tools: read, search and write to your spaces, direct messages and sidebar.

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (1 strong, 0 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry. Trust signals: trusted author (16/16 approved).

4 files analyzed · No issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

file_system

Check that this permission is expected for this type of plugin.

Documentation

View on GitHub

From the project's GitHub README.

google-chat-mcp

CI Latest release Go Reference License: Apache 2.0

Google Chat as MCP tools. Read, search and write to your spaces, DMs and sidebar.

A single Go binary that speaks MCP over stdio. It runs as a subprocess of your client, on your own machine, against your own Google account. There is no server to host, no shared deployment and no service account: you create a Google OAuth client, log in once, and the refresh token stays in your OS keyring. Claude Code, Claude Desktop and any other MCP client can drive it.

Why google-chat-mcp

Chat integrations are normally apps: they post as a bot, see only the spaces somebody added them to, and need a service account an administrator provisions. This one runs as you. It reaches what your account already reaches, and messages you send are from you, not from an app with your name on it.

That also puts the user-scoped half of Chat in reach, which an app identity cannot get to at all: your sidebar sections, how far you have read in a space or a thread, your availability and custom status, and your organisation's custom emoji. Fifty-five tools cover it, every write takes dry_run, and nothing is requested at login beyond the scopes the tools you are actually using need.

Install

go install github.com/mmedum/google-chat-mcp/v2/cmd/google-chat-mcp@latest

Or take a signed archive from the latest release — Linux, macOS and Windows, on amd64 and arm64 — and verify it before you run it:

sha256sum -c checksums.txt --ignore-missing

# The checksum file is signed with a keyless Sigstore certificate tied to
# the release workflow's identity. The bundle carries both.
cosign verify-blob checksums.txt \
  --bundle checksums.txt.bundle \
  --certificate-identity-regexp 'https://github\.com/mmedum/google-chat-mcp/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

# And the archive itself carries build provenance.
gh attestation verify google-chat-mcp_*.tar.gz --repo mmedum/google-chat-mcp

Every archive also ships an SBOM, so you can see what is inside a binary you did not build.

Claude Desktop

Every release also carries a .mcpb bundle. Open it and Claude Desktop installs the server and asks for your OAuth client JSON — no config file to edit. It covers macOS, Windows and Linux on both architectures each: macOS through a universal binary, Windows through amd64, and Linux through a small launcher that picks the right binary at start, because a bundle manifest has no key for the architecture. Its SHA-256 is in the same signed checksums.txt. The release is listed in the MCP registry as io.github.mmedum/google-chat-mcp.

The bundle does not log you in. Install the binary as well, run google-chat-mcp login once, and the bundle picks up the same credentials. Claude Code does not install .mcpb files, so it uses the command below.

Set up Google

You need your own OAuth client. It takes about fifteen minutes once, and docs/gcp-setup.md walks through it. In short: create a project, enable the Google Chat API and the People API, configure the consent screen with the scopes this server asks for, and create an OAuth 2.0 Client ID of type Desktop app. Download the JSON.

Then log in:

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

Login opens your browser, or prints the URL when there is no browser to open (--no-browser forces that). The callback lands on 127.0.0.1 on a random port, with PKCE and state throughout. The refresh token goes into your OS keyring; if there is no keyring, it goes into a 0600 file and the command tells you so. On Windows a Go file mode only sets the read-only attribute, so what protects that file there is the ACL its directory inherits, not the mode.

google-chat-mcp status says which account is signed in and where the token lives. google-chat-mcp logout revokes the token at Google and deletes the local copy.

google-chat-mcp status --json prints the same state as one JSON object on stdout, for a script that needs to know whether this server is authorised before starting it. credentials.resolved is the field to branch on, credentials.signed_in distinguishes "no profile yet" from "profile but no token", schema_version changes only when a field is removed or its meaning changes, and the account is masked to its domain exactly as the text output masks it. A label in the human output is free to be reworded in any release; the object is not.

Logging in over SSH

The callback goes to the remote host's loopback address, and your browser is local, so forward the port. It is chosen at random and printed only once login is already waiting, so read it out of the printed URL — it appears percent-encoded, as 127.0.0.1%3A<port> — and in a second local terminal:

ssh -N -L <port>:127.0.0.1:<port> user@remote-host

Then open the URL locally. If ssh reports bind: Address already in use, cancel the login with Ctrl-C and run it again to draw another port.

Connect a client

Claude Code:

claude mcp add google-chat -- google-chat-mcp

Or, in a client config file:

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

Claude Desktop reads the same shape from its own config file. Use the binary's absolute path there if it is not on the app's PATH, or install the .mcpb bundle above and edit nothing.

Every setting is an environment variable, listed in docs/configuration.md. The three worth knowing now: GCM_READ_ONLY=true leaves out every tool that changes anything in Chat, GCM_PROFILE lets one machine hold a work account and a personal one, and GCM_LOCAL_DIR names the one directory files may be written to and read from. Without that last one the server touches no files at all, and the three tools that move them say so.

Tools

ToolWhat it doesScope
whoamiWhich account the stored credentials belong toopenid email profile
list_spacesSpaces, group chats and direct messages you are inchat.spaces.readonly
get_spaceOne space by resource namechat.spaces.readonly
search_spacesNamed spaces by display name, including ones you are not inchat.spaces.readonly; chat.admin.spaces.readonly for use_admin_access
find_group_chatsThe group chats holding exactly you and the people you namechat.memberships.readonly, chat.spaces.readonly
find_direct_messageThe direct message with one person, created if there is none yetchat.spaces.readonly, chat.spaces.create
get_messagesRecent messages in a space, newest first, senders resolved to nameschat.messages.readonly
get_messageOne message, with its reaction counts, what its text links to and what it quoteschat.messages.readonly
get_threadEvery message in one thread, oldest firstchat.messages.readonly
download_attachmentSave a message's attachment into the server's local directorychat.messages.readonly
search_messagesGoogle's search across every space you can see, or a regular-expression scan of onechat.messages.readonly; chat.users.readstate.readonly for unread_only
search_peopleTurn a name into an email address, from the directory and your contactsdirectory.readonly, contacts.readonly
list_membersWho is in a space, resolved to names and addresseschat.memberships.readonly, directory.readonly
get_memberOne membership: who or what it is, their role, whether they have joinedchat.memberships.readonly, directory.readonly
update_member_roleMake someone a member, manager or assistant managerchat.memberships
list_reactionsReactions on a messagechat.messages.reactions
list_pinned_messagesWhat a space has pinnedchat.spaces.pins.readonly
pin_messagePin a message, which everyone in the space seeschat.spaces.pins
unpin_messageRemove a pinchat.spaces.pins
get_space_read_stateHow far you have read in a spacechat.users.readstate.readonly
get_thread_read_stateHow far you have read in a threadchat.users.readstate.readonly
mark_space_readClear a space's unread badge, for you onlychat.users.readstate
mark_space_unreadRewind your read mark, for you onlychat.users.readstate
list_space_eventsWhat changed in a space over the last 28 daysthe read scope of each event type asked for
get_space_eventOne of those changes by resource namechat.spaces.readonly
get_space_notification_settingWhat Chat tells you about a spacechat.users.spacesettings
update_space_notification_settingChange it, or mute the spacechat.users.spacesettings
get_availabilityYour own presence and custom statuschat.users.availability.readonly
set_availabilityActive, away, or do not disturb until a timechat.users.availability
set_custom_statusSet or clear the text and emoji beside your namechat.users.availability
list_custom_emojisYour organisation's own emojichat.customemojis.readonly
get_custom_emojiOne of them by resource namechat.customemojis.readonly
create_custom_emojiAdd one from a local imagechat.customemojis
delete_custom_emojiRemove one, for everyonechat.customemojis
delete_spaceDelete a space and everything in itchat.delete
list_sectionsYour own sidebar sectionschat.users.sections.readonly
list_section_itemsWhat a section holds, or which section a space sits inchat.users.sections.readonly
send_messagePost text, exactly as given. Optionally into a thread, or carrying an uploaded filechat.messages.create
upload_attachmentSend a local file to a space and get the token that attaches itchat.messages.create
update_messageEdit the text of a message you sentchat.messages
delete_messageDelete a message. Already gone counts as successchat.messages
add_reactionReact to a message with a Unicode emojichat.messages.reactions
remove_reactionRemove a reaction, by resource name or by message, emoji and personchat.messages.reactions
create_group_chatStart an unnamed group chat with 2 to 20 peoplechat.spaces.create
create_spaceCreate a named space with up to 20 peoplechat.spaces.create
update_spaceRename a space or change its descriptionchat.spaces
add_memberInvite someone to a space by emailchat.memberships
remove_memberRemove a membership. Already gone counts as successchat.memberships
create_sectionAdd a sidebar sectionchat.users.sections
rename_sectionRename a sidebar sectionchat.users.sections
delete_sectionDelete a sidebar section; its spaces fall back to the defaultschat.users.sections
position_sectionReorder a section, by absolute rank or by moving it to the start or endchat.users.sections
move_space_to_sectionFile a space under a section. A space already there is left alonechat.users.sections

Sections are your own sidebar. Nothing there changes a space, and nobody else sees it.

Three resources carry the same content as the matching tools, for a client that includes resources in its context:

  • gchat://spaces/{space_id}
  • gchat://spaces/{space_id}/messages/{message_id}
  • gchat://spaces/{space_id}/threads/{thread_id}

Safety

  • dry_run on 25 write tools. It returns the request body that would have been sent, and the call cannot reach the network: the flag puts the request on a context the HTTP client refuses to write under, so a tool that forgot its own preview branch fails loudly instead of posting.
  • GCM_READ_ONLY=true leaves the write tools unregistered. A tool that is not registered cannot be called, whatever permission mode the client is in or whatever a model asks for. It is set where you start the server, so it binds the whole session rather than one call.
  • Annotations and an interaction hint. Write and delete tools ask the client to put a person in the loop. What that is worth is the client's decision, and the two answers are further apart than they look. In Claude Code interactively, auto mode has run a write with no prompt at all. Headless, the same mark refuses every write outright — an allow rule does not suppress it, and there is nobody to ask. So if you are automating with nobody at the keyboard, GCM_INTERACTION_HINT=false is the way to use the write tools at all, and it is a decision to make on purpose.
  • send_message posts the body verbatim. No prefix, no suffix, no "sent by an assistant" footer.
  • A retried post cannot land twice. Each message carries a client-chosen id, so a retry after a timeout returns what already landed instead of posting again.
  • Logs never carry the payload. They record which call was made, to which resource, and how it ended. Message text, addresses, names and search terms stay out, and a test fails the build if one appears.

How it works

MCP client ──stdio──► google-chat-mcp
                       ├── tools      one handler per tool; shapes the reply
                       ├── service    the rules: idempotency, degrading, search
                       ├── gchat      REST client for Chat, People and OIDC
                       ├── directory  email cache, so a page of messages costs one lookup
                       └── auth       refresh token → access token

Every space, message and thread is addressed by its resource name, never by position, and a listing keeps paging while Google returns a token — including past an empty page, which Chat returns when it applies the page size before the filter.

Getting help

If something does not work, run google-chat-mcp doctor. It checks the credentials, the granted scopes and what Google actually answers, and names what is missing — most first-run trouble is an API that was never enabled or a consent screen without you on it.

If that does not explain it, open an issue. Never paste a space or message resource name, message text, a client_secret.json or a token into one; describe the shape instead. Security problems go through SECURITY.md, privately.

Versioning

Tool names and their output fields are stable within a major version. A change needing you to act — a new scope, another login, a different command in your client config — is marked Breaking: in CHANGELOG.md, which is also what each release's notes are made from.

Development

make build     # the binary
make test      # race detector, coverage floor
make check     # everything CI runs

make check is the definition of done: formatting, go vet, golangci-lint, race tests with a per-package coverage floor, govulncheck, a licence check, a stdio smoke test, a schema diff against the released tool surface, an API-coverage gate that fails when a Google API method has no verdict on it or a call this server makes has no row, and a staleness gate that fails when this README, the docs or the changelog drift from the code.

Documentation

Contributing

Questions and bugs go in issues; pull requests are welcome. CONTRIBUTING.md covers the branch and review flow, and make check is what has to pass.

Security

SECURITY.md says how to report a vulnerability.

Code of conduct

CODE_OF_CONDUCT.md — Contributor Covenant 3.0.

License

Apache 2.0 — see LICENSE.

Reviews

No reviews yet

Be the first to review this server!