contacts-mcp
Enables AI assistants to manage contacts with full CRUD, dedup, merge, import/export, sync with Google/Apple/CardDAV, and git-backed rollback.
README
contacts-mcp
An MCP server that gives AI assistants full contact management capabilities — create, search, deduplicate, merge, sync across systems, and roll back any change with confidence.
Contacts are stored as individual vCard files in a git repository. Every mutation is a git commit, so you get full version history, diffs, and revert for free. The AI can make sweeping changes knowing everything is recoverable.
Quick Start
Prerequisites
- Bun 1.0+
- Git (available in PATH)
Install & Build
cd contacts-mcp
bun install
bun run build
Add to Claude Code
claude mcp add contacts bun /path/to/contacts-mcp/dist/index.js
Add to Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"contacts": {
"command": "bun",
"args": ["/path/to/contacts-mcp/dist/index.js"]
}
}
}
Development Mode
Run directly from source without building (bun runs TypeScript natively):
bun run dev
Verify It Works
Use the MCP Inspector to test interactively:
bunx @modelcontextprotocol/inspector bun dist/index.js
What It Does
Once connected, your AI assistant gets 14 tools and 4 resources for managing contacts:
Tools
| Tool | What it does |
|---|---|
create_contact |
Create a contact with name, emails, phones, addresses, org, birthday, notes, categories. Phone numbers are auto-normalized to E.164. |
get_contact |
Retrieve full contact details by UUID. |
update_contact |
Partial update — only fields you specify are changed, everything else is preserved. |
delete_contact |
Soft-delete (moves to archive). Optional permanent delete. Archived contacts can be restored via rollback. |
search_contacts |
Fuzzy search across all fields (name, email, phone, org, notes, categories). Ranked by relevance. |
find_duplicates |
Scan for potential duplicates with confidence scores. Matches on email (0.95), phone (0.90), name (fuzzy, 0.50-0.70), with org boost. |
merge_contacts |
Merge 2+ contacts into one. Strategies: union (combine all data), keep-newest, keep-oldest. Manual field overrides supported. |
import_contacts |
Bulk import from a .vcf file. Optional dedup check against existing contacts. Dry-run mode. |
export_contacts |
Export to .vcf, .csv, or .json. Optional search filter. |
resolve_contact_points |
Resolve phone numbers and email addresses to contacts using exact normalized matching. Reports matched, ambiguous, and unresolved results. |
sync_provider |
Sync with a configured remote provider (Google, Apple, CardDAV). Pull, push, or both. Configurable conflict resolution. |
list_providers |
Show all configured providers and their sync status. |
rollback |
Undo changes by reverting git commits. Modes: undo last N, revert to a specific commit, revert to a tag. Dry-run supported. Creates a safety tag first so the rollback itself can be undone. |
history |
View change history — globally or for a specific contact. Shows operation type, commit hash, date, and message. |
CLI Export And Resolve
The binary still starts the MCP stdio server by default. It also supports deterministic non-AI command-line operations for other local tools:
contacts-mcp export --format json --output contacts.json
contacts-mcp export --format json --output -
contacts-mcp resolve --input contact-points.json --output -
contacts-mcp sync-provider --provider apple --direction pull
resolve input is JSON:
{
"phones": ["+18016022838", "(801) 602-2838"],
"emails": ["alex@example.com"],
"defaultCountry": "US"
}
Use CONTACTS_MCP_STORE=/path/to/store when exporting or resolving against a custom
store path.
Before exporting Contacts from macOS, pull Apple Contacts into the local git-backed store:
contacts-mcp sync-provider --provider apple --direction pull
If you do not have an Apple provider in ~/.contacts-mcp/config.json, the CLI will use
the built-in Apple provider on macOS. The first run may trigger a Contacts permission
prompt.
Resources
| URI | Description |
|---|---|
contacts://all |
Summary list of all active contacts |
contacts://{id} |
Full detail for a specific contact (resource template — lists all contacts for discovery) |
contacts://duplicates |
Current duplicate candidates with confidence scores |
contacts://history |
Recent change log |
How Storage Works
~/.contacts-mcp/store/
├── .git/ # Git repository
├── contacts/
│ ├── <uuid>.vcf # One vCard 4.0 file per contact
│ └── ...
├── archive/
│ └── <uuid>.vcf # Soft-deleted contacts
└── .metadata/
├── providers.json # Provider config & sync state
└── merge-log.json # Audit trail for merges
- One file per contact — each contact is a standard vCard 4.0 (
.vcf) file named by its UUID. - Every change is a commit — creating, updating, deleting, merging, importing all produce descriptive git commits like
Create contact: Jane Smith (uuid)orMerge contacts: Jane + J. Smith -> Jane Smith. - Soft deletes —
delete_contactmoves the file fromcontacts/toarchive/. It's still in the repo and can be found byget_contactor restored via rollback. - Bulk operations get tags — imports and syncs create
pre-import-<timestamp>/post-import-<timestamp>git tags so you can roll back an entire bulk operation in one shot. - Rollback = git revert — always creates new commits (never
reset --hard), so the full audit trail is preserved and rollbacks are themselves reversible.
Configuration
Configuration is loaded from ~/.contacts-mcp/config.json (or the path in CONTACTS_MCP_CONFIG env var).
Minimal Config (Local Only)
No config file needed. The server works out of the box with the local git store at ~/.contacts-mcp/store/.
Custom Store Path
{
"storePath": "/path/to/my/contacts-repo"
}
Or via environment variable:
CONTACTS_MCP_STORE=/path/to/my/contacts-repo bun dist/index.js
Full Config with Providers
{
"storePath": "~/.contacts-mcp/store",
"providers": [
{
"name": "google-personal",
"type": "google",
"enabled": true,
"config": {
"clientId": "your-client-id.apps.googleusercontent.com",
"clientSecret": "your-client-secret",
"refreshToken": "your-refresh-token"
}
},
{
"name": "fastmail",
"type": "carddav",
"enabled": true,
"config": {
"serverUrl": "https://carddav.fastmail.com/dav/addressbooks",
"username": "you@fastmail.com",
"password": "app-specific-password",
"authMethod": "Basic"
}
},
{
"name": "apple",
"type": "apple",
"enabled": true,
"config": {}
}
]
}
Environment Variables
| Variable | Default | Description |
|---|---|---|
CONTACTS_MCP_CONFIG |
~/.contacts-mcp/config.json |
Path to config file |
CONTACTS_MCP_STORE |
~/.contacts-mcp/store |
Path to git-backed contact store |
DEBUG |
(unset) | Set to any value to enable debug logging |
Provider Setup
Google Contacts
Uses the Google People API. You need OAuth2 credentials:
- Go to Google Cloud Console and create a project.
- Enable the People API.
- Create OAuth2 credentials (Desktop app type).
- Use the OAuth2 playground or a script to get a refresh token with
https://www.googleapis.com/auth/contactsscope. - Add
clientId,clientSecret, andrefreshTokento your config.
Apple Contacts (macOS only)
Uses JavaScript for Automation (JXA) via osascript. No credentials needed, but:
- The first time you sync, macOS will prompt you to allow terminal/IDE access to Contacts.
- Grant permission in System Settings > Privacy & Security > Contacts.
- Config is just
"config": {}— no fields required.
CardDAV
Works with any CardDAV server — Fastmail, Nextcloud, Radicale, iCloud, etc.
| Config field | Description |
|---|---|
serverUrl |
CardDAV server URL (e.g., https://carddav.fastmail.com/dav/addressbooks) |
username |
Your username |
password |
Password or app-specific password |
authMethod |
"Basic" (default) or "Digest" |
For iCloud: use an app-specific password and https://contacts.icloud.com as the server URL.
How Dedup Works
The find_duplicates tool compares contacts using weighted field matching:
| Match type | Confidence | How it works |
|---|---|---|
| Same email (normalized) | 0.95 | Case-insensitive exact match on any email |
| Same phone (normalized) | 0.90 | E.164 normalization, so (555) 123-4567 matches +15551234567 |
| Exact name | 0.70 | Full name string match |
| Fuzzy name | 0.50 | Levenshtein distance, handles swapped names ("John Smith" / "Smith, John") and initials ("J. Smith" / "Jane Smith") |
| Same organization | +0.15 | Additive boost (never standalone — only increases existing score) |
Contacts are grouped into blocking keys (by name initials, email domain, phone suffix) before comparison, so performance stays fast even with thousands of contacts.
The default threshold is 0.6 — anything scored at or above that is reported as a potential duplicate.
How Merge Works
merge_contacts takes 2+ contact IDs and combines them:
- First ID is the primary — it keeps its UUID, the others are archived.
unionstrategy (default) — combines all emails, phones, addresses, URLs, categories. Takes the longer/more complete name. Picks up birthday, org, photo from whichever has it.keep-newest— takes all fields from the most recently modified contact.keep-oldest— takes all fields from the earliest modified contact.fieldOverrides— manually specify which contact's value to use for specific fields:{ "organization": "uuid-of-contact-with-better-org" }.- Provider IDs are merged — so if contact A was from Google and contact B was from CardDAV, the merged contact maps to both remotes.
How Sync Works
Sync is local-first and explicit (triggered by the sync_provider tool, never automatic):
- Pull: Fetch contacts from the remote. New ones are imported locally. Changed ones are updated based on conflict strategy.
- Push: Local contacts modified since last sync are pushed to the remote. New local contacts get created remotely.
- Conflict resolution (when both sides changed):
newest-wins(default) — compare modification timestamps, keep the newer one.local-wins— always keep the local version.remote-wins— always accept the remote version.manual— flag as conflict, don't auto-resolve.
- Pre/post sync git tags are created for rollback.
Project Structure
src/
├── index.ts # Entry point — stdio transport
├── server.ts # McpServer setup, wires tools + resources
├── config.ts # Config loading from file / env vars
├── types/ # TypeScript interfaces (Contact, Provider, etc.)
├── contacts/
│ ├── model.ts # Contact construction + name parsing
│ ├── vcard.ts # vCard 4.0 serialize/deserialize (no external lib)
│ ├── normalize.ts # Phone (E.164), email, name normalization
│ ├── search.ts # Fuse.js fuzzy search
│ ├── dedup.ts # Duplicate detection with weighted scoring
│ └── merge.ts # Contact merge with multiple strategies
├── store/
│ ├── git-ops.ts # Low-level git wrapper (simple-git)
│ ├── git-store.ts # CRUD + bulk ops + history + rollback
│ └── file-layout.ts # Path conventions
├── providers/
│ ├── base.ts # Abstract provider
│ ├── google.ts # Google People API
│ ├── apple.ts # macOS Contacts via JXA
│ ├── carddav.ts # CardDAV via tsdav
│ └── local.ts # Local store wrapper
├── sync/
│ ├── engine.ts # Bidirectional sync orchestration
│ ├── conflict.ts # Conflict resolution
│ └── diff.ts # Field-level contact diffing
├── tools/ # One file per MCP tool (13 tools)
└── resources/ # MCP resource handlers (4 resources)
Tech Stack
| Component | Library | Why |
|---|---|---|
| MCP server | @modelcontextprotocol/sdk |
Official SDK, stdio transport |
| Schema validation | zod |
Required by MCP SDK for tool input schemas |
| Git operations | simple-git |
Clean async API over git CLI |
| Fuzzy search | fuse.js |
Fast client-side fuzzy matching with field weights |
| Phone normalization | libphonenumber-js |
Google's libphonenumber for E.164 normalization |
| Google Contacts | googleapis |
Official Google API client (People API v1) |
| CardDAV | tsdav |
WebDAV/CardDAV client for address book sync |
| Apple Contacts | osascript (JXA) |
Built-in macOS automation, no extra deps |
| vCard parsing | Custom | Hand-rolled RFC 6350 parser/serializer — zero dependencies, full round-trip fidelity |
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。