@jdickey1/mcp-google-contacts
v0.1.0
Published
Google Contacts MCP server. Six tools (search, get, list labels, apply label, remove label, additive update). Grants OAuth scopes contacts.readonly + contacts.other.readonly + contacts (full write); the write surface is tool-limited to label CRUD and sele
Downloads
81
Maintainers
Readme
mcp-google-contacts
A small Google Contacts MCP server. Searches and reads contacts, manages user labels, and selectively enriches missing contact fields. Six tools, official googleapis client, OS keychain for credentials.
Built because the popular Google Contacts MCPs on the registry are abandoned, ship third-party auth handlers, or expose a sprawling write surface that's hard to audit. This one stays narrow on purpose: explicit OAuth scopes, six tools, and a write surface tool-limited to label CRUD plus additive-only field updates.
Tools
| Tool | What it does |
|---|---|
| search_contacts(query, max?) | Read. Search Google Contacts by name (primary + "Other contacts"). Returns up to max records (default 10, cap 30) with names, emails, phones, and applied user-label names. |
| get_contact(resource_name) | Read. Fetch one contact by resource name (e.g. people/c1234567890123456789). Returns names, emails, phones, addresses, organizations, biographies, and labels. |
| list_label_groups() | Read. List user-defined contact label groups (Clients, Vendors, etc.) with current member counts. System groups (myContacts, etc.) are filtered out. |
| apply_label(person_resource_name, label_name) | Write. Apply a label to a contact. Idempotent (returns action: "noop" if already labeled); auto-creates the label group on first use. |
| remove_label(person_resource_name, label_name) | Write. Remove a label from a contact. No-op if the contact doesn't have it. |
| update_contact(person_resource_name, …) | Write (additive-only). Fills in missing contact fields: phone, address, organization, title, biography. Only writes a field when the existing value is empty — never overwrites. Returns added and skipped lists so callers can verify what changed. |
OAuth scopes
This server requests three scopes:
https://www.googleapis.com/auth/contacts.readonly— primary contact read.https://www.googleapis.com/auth/contacts.other.readonly— read "Other contacts" (the implicitly auto-saved set).https://www.googleapis.com/auth/contacts— full contacts write.
The write scope grants the API permission to mutate any contact field. The tools choose to restrain themselves to label CRUD (apply_label, remove_label) plus additive-only field updates (update_contact never overwrites existing values), but the scope itself is not narrowed. If you only want read access, fork the auth CLI and drop the third scope before running it. A future minor version may split the package into read-only and read-write variants.
Install
You need Node.js 20 or newer and a Google Cloud OAuth Desktop client (free; setup steps below).
Option A — Claude Code marketplace (recommended)
Install the google-contacts-mcp plugin from the claude-skills marketplace. The MCP wires itself up; only the one-time auth flow remains.
Option B — Manual MCP config
Add the server to your MCP client (~/.claude.json for Claude Code, claude_desktop_config.json for Claude Desktop):
{
"mcpServers": {
"google-contacts": {
"command": "npx",
"args": ["-y", "@jdickey1/mcp-google-contacts"]
}
}
}In ~/.claude/settings.json, allow the tools you want to use:
{
"permissions": {
"allow": [
"mcp__google-contacts__search_contacts",
"mcp__google-contacts__get_contact",
"mcp__google-contacts__list_label_groups",
"mcp__google-contacts__apply_label",
"mcp__google-contacts__remove_label",
"mcp__google-contacts__update_contact"
]
}
}Setup
1. Enable the People API and create a Desktop OAuth client
- Open the Google Cloud Console. Create a project or select one.
- APIs & Services → Library → search People API → Enable.
- APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Desktop app
- Name: anything (e.g.
mcp-google-contacts)
- Copy the Client ID and Client secret.
If your project is in Testing publishing status (the default), add your own Google account as a test user under OAuth consent screen → Test users.
2. Run the auth flow once
GOOGLE_OAUTH_CLIENT_ID=<your-client-id> \
GOOGLE_OAUTH_CLIENT_SECRET=<your-client-secret> \
npx -p @jdickey1/mcp-google-contacts mcp-google-contacts-authA browser tab opens for Google consent. After approval the refresh token is stored in the macOS login keychain. Re-runs reuse the stored client credentials, so subsequent invocations don't need the env vars.
If your default browser doesn't open, copy the URL printed to the terminal.
3. Verify
Restart your MCP client, then ask: "Use the google-contacts MCP to list my label groups." You should see your user-defined contact labels.
Credential storage
Credentials live in the macOS login keychain under three entries:
| Service | Account | Holds |
|---|---|---|
| cos-cron.google-contacts | $USER (or $MCP_GOOGLE_CONTACTS_ACCOUNT) | Refresh token |
| cos-cron.google-contacts.client_id | same | OAuth client ID |
| cos-cron.google-contacts.client_secret | same | OAuth client secret |
The
cos-cronservice prefix is internal substrate naming preserved across the package's history to keep existing tokens valid for adopters who migrated from earlier internal builds. The prefix has no functional meaning to new adopters.
Overrides
| Variable | Effect |
|---|---|
| MCP_GOOGLE_CONTACTS_ACCOUNT | macOS keychain account label (default $USER). Useful for multi-account setups or when running under a service account whose $USER doesn't match the keychain you want to read. |
Limitations
- macOS-only credential storage in v1. The keychain is the only backing store. Linux and Windows are not supported in this version. A future minor version may add a file-backed fallback (mode
0600JSON file under$XDG_CONFIG_HOME). - No
MCP_GOOGLE_CONTACTS_*read-time credential override layer. The sibling@jdickey1/mcp-gmailpackage has one (MCP_GMAIL_CLIENT_ID, etc.) for headless/Docker/CI contexts; this package does not yet. If you need it, pre-populate the keychain or watch for a follow-up minor version. The account-label override (MCP_GOOGLE_CONTACTS_ACCOUNT) is in place.
Inspecting and re-running auth
# Drop the cached refresh token and re-authorize:
security delete-generic-password -s cos-cron.google-contacts -a "$USER"
GOOGLE_OAUTH_CLIENT_ID=<id> GOOGLE_OAUTH_CLIENT_SECRET=<secret> \
npx -p @jdickey1/mcp-google-contacts mcp-google-contacts-authTo rotate the OAuth client itself, also delete cos-cron.google-contacts.client_id and cos-cron.google-contacts.client_secret before re-running.
Design notes
- Write surface is intentional and tool-limited.
apply_labelandremove_labelare idempotent label CRUD;update_contactis additive-only (never overwrites). The scope grant iscontacts(full write); the tools choose to restrain themselves. No batch endpoints — every write is one API call to one contact. - Idempotent label operations.
apply_labelreturnsaction: "noop"when the contact already has the label;remove_labelreturns the same when the contact doesn't have it. Repeated calls with the same args produce the same end state. - Optimistic concurrency on
update_contact. Reads the contact'setagbefore writing and includes it in the update request so concurrent edits surface as Google API errors instead of silent overwrites. - No raw API responses leak past
tools.ts. Every tool projects to a stable shape so a People API change doesn't silently reshape your MCP responses. - Label cache.
LabelCacheis per-process; it lists groups once at boot and reuses the resource-name lookup for subsequent label operations. Auto-created labels are written into the cache on creation so the next call doesn't re-list.
Why a new MCP?
The popular community Google Contacts MCPs were unsuitable for production use:
- Several vendored their own OAuth handler instead of using the official
googleapisclient (large attack surface, harder to audit). - Some were abandoned (no commits in over a year).
- Some exposed every People API write tool as default — full create/update/delete on every field — with no scope discipline.
This one is intentionally minimal so other people can read every line in one sitting.
Contributing
Source lives in the private claude-skills-private repo at mcp-servers/google-contacts/. The repo is closed source for now; the published JS is public. Issues for adopters: please file at claude-skills/issues.
For local development (if you have access):
git clone <private-source>
cd mcp-servers/google-contacts
npm install # installs Node deps + tsx
npx tsx --test src/tools.test.ts cli/auth.test.ts # 28 unit tests
npx tsc --noEmit # typecheck
npx tsc -p tsconfig.build.json # build dist/If you have Bun installed locally, bun install works alongside npm install. Note: running npm install in this directory deletes bun.lock; restore it via git checkout bun.lock after.
Source layout:
src/
index.ts # MCP server, tool registration
auth.ts # OAuth client construction
keychain.ts # macOS keychain wrapper for OAuth secrets
tools.ts # the six tools + LabelCache
tools.test.ts # 21 unit tests
cli/
auth.ts # one-time OAuth Desktop flow
auth.test.ts # 7 unit tests on Keychain (file is mislabeled)License
MIT. See LICENSE.
