Skip to main content
Glama
boonkgim

mcp-google-contacts

by boonkgim
README.md
# mcp-google-contacts

[![CI](https://github.com/boonkgim/mcp-google-contacts/actions/workflows/ci.yml/badge.svg)](https://github.com/boonkgim/mcp-google-contacts/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A remote MCP server for Google Contacts, deployed on Cloudflare Workers, for
use as a **custom connector in claude.ai** (web/desktop/mobile). Each user
who connects authorizes their own Google account via OAuth — the server
proxies that OAuth flow and never sees your Google password, just the tokens
it needs to call the People API on your behalf.

Built for a specific workflow: send Claude a photo of a namecard/business
card and have it extract the details and save a new contact for you.

## Architecture

- **`@cloudflare/workers-oauth-provider`** — implements the OAuth 2.1 server
  that claude.ai talks to (authorize/token/dynamic client registration), and
  hands your Worker an encrypted `props` bag per authorized user.
- **`google-handler.ts`** — a small Hono app that owns `/authorize` and
  `/callback`: shows the consent screen, redirects to Google, exchanges the
  code for tokens, and stores the Google refresh token in `props`.
- **`agents`' `McpAgent`** — a Durable Object-backed MCP server. Tool calls
  read `this.props.googleRefreshToken` and mint a fresh Google access token
  on demand (cached in-memory per Worker isolate) before calling the People
  API.
- **`workers-oauth-utils.ts`** — CSRF/session/approval-dialog plumbing,
  copied near-verbatim from Cloudflare's reference OAuth demo (provider-agnostic).

## Tools

- `create_contact` — create a contact (name, company, title, emails, phones, address, notes)
- `search_contacts` — search existing contacts by name/email/phone/company (use before creating, to avoid duplicates)
- `list_contacts` — list contacts, most recently modified first
- `get_contact` — fetch full details for one contact
- `update_contact` — update fields on an existing contact

## 1. Google Cloud setup

1. Go to the [Google Cloud Console](https://console.cloud.google.com/) and create (or pick) a project.
2. Enable the **Google People API**.
3. Go to **APIs & Services → Credentials → Create Credentials → OAuth client ID**.
   - Application type: **Web application**
   - Authorized redirect URI: `https://<your-worker-subdomain>.workers.dev/callback`
     (you'll know your Worker's URL after the first `wrangler deploy` — you can
     add/edit this redirect URI afterward in the Cloud Console).
   - Save the **Client ID** and **Client Secret**.
4. If your OAuth consent screen is in "Testing" mode, add your own Google account as a test user.

## 2. Cloudflare setup

```bash
npm install
npx wrangler login
npx wrangler kv namespace create OAUTH_KV
```

Copy the `id` printed by the last command into `wrangler.jsonc` under both
`kv_namespaces[0].id` and `env.development.kv_namespaces[0].id`.

If `npx wrangler whoami` lists more than one Cloudflare account, add
`"account_id"` to `wrangler.jsonc` (see the comment there) or set the
`CLOUDFLARE_ACCOUNT_ID` env var — otherwise `wrangler` can't tell which
account to deploy to.

Set secrets (you'll be prompted to paste each value):

```bash
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET
npx wrangler secret put COOKIE_ENCRYPTION_KEY   # e.g. output of: openssl rand -hex 32
```

## 3. Deploy

```bash
npm run deploy
```

This prints your Worker's URL, e.g. `https://mcp-google-contacts.<your-subdomain>.workers.dev`.
Go back to the Google Cloud Console and make sure the OAuth client's redirect
URI is exactly `<that-url>/callback`.

## 4. Connect it to claude.ai

In claude.ai: **Settings → Connectors → Add custom connector**, and enter:

```
https://mcp-google-contacts.<your-subdomain>.workers.dev/mcp
```

Claude will walk you through authorizing — you'll see the consent screen
from `google-handler.ts`, then Google's own OAuth screen. Once approved, the
Google Contacts tools are available in your conversations.

## Local development

```bash
cp .env.development.example .env.development   # fill in the same three values as the secrets above
npm run dev
```

`wrangler dev` runs the Worker locally; note that Google's OAuth redirect
must match what you're actually running against (add `http://localhost:8788/callback`
as an additional authorized redirect URI in Google Cloud Console for local testing).

## Notes

- Uses the `https://www.googleapis.com/auth/contacts` OAuth scope (read/write access to your contacts) plus `email profile` to identify who's connecting.
- Google access tokens expire in ~1 hour; this server refreshes them on demand from the stored refresh token rather than syncing to this Worker's own OAuth token lifetime — simpler and correct regardless of how long a Claude session goes between its own token refreshes.
- `search_contacts`/`list_contacts` page through all of `people/me`'s connections client-side rather than using the People API's `searchContacts` endpoint, which requires a separate index warm-up and can lag behind recent edits.
- A previous local-only version of this server (stdio transport, for Claude Desktop) is available in this repo's git history if you'd rather run it locally instead of deploying.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup and the quality
checks (`npm run check`) to run before opening a PR.

## License

[MIT](LICENSE)