MCP server
Connect AI agents to CommSync over the Model Context Protocol — every inbox, contact, label, and channel action available as a tool, scoped to your API key.
CommSync speaks the Model Context Protocol. Point an MCP-capable agent (Claude Desktop, a custom client, or your own integration) at the endpoint with an API key. It can then read threads, send messages, manage contacts and labels, and more. The key owner's user and org role scope every operation it performs.
Looking for the machine-readable version?
Agents can fetch /docs/mcp.txt for the same catalog as plain text with full parameter details — one request, no scraping.
Connect
The server is a single stateless HTTP endpoint. Authenticate with a
csk_… key as a bearer token.
Mint an API key
In CommSync, open Settings → API Keys and create a key. CommSync shows it once — store it securely. See API keys.
Call the endpoint
POST JSON-RPC to the /api/mcp route on your CommSync API origin.
curl -X POST "$COMMSYNC_API/api/mcp" \
-H "Authorization: Bearer csk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Call a tool
Use tools/call with the tool name and its arguments.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "list_threads", "arguments": { "limit": 20 } }
}| Endpoint | POST /api/mcp (on your CommSync API origin) |
| Transport | HTTP, stateless — one request per call |
| Auth | Authorization: Bearer csk_… |
Authorization model
Every request resolves your key to (userId, orgId, role, accessibleChannels).
Each tool sits behind one of four gates. The server rejects a call that
exceeds your access before anything happens.
any member any user in the org channel access needs access to the relevant channel owner / admin owner only
- any member — any user with org membership.
- channel access — you must have access to the channel involved.
- owner / admin — reserved for org owners and admins.
- owner only — the single workspace owner (for example, billing).
Owners and admins have channel access implicitly; members get it per channel — see Roles & permissions.
CommSync also tags tools by effect: read (no change), write (mutates), or
destructive (removes data — use with care).
Channel-scoped keys
An API key can be fine-tuned to specific lines instead of your full channel access. When you create (or edit) a key in Settings, then API Keys, choose Specific lines. Pick the email addresses and phone numbers it can touch.
The server then intersects your live channel access with the key's allowlist on every request. A scoped key can only read, send, and act on the chosen lines. Threads on other channels are invisible to it, and CommSync refuses sends from other channels. Channel listings only show what is in scope.
- The default is All channels: the key follows your live access.
- This includes lines you connect later; CommSync migrated pre-existing keys this way.
- A scoped key never out-privileges you.
- If you lose access, or someone deletes the line, it drops from the key's scope too.
- Per-user surfaces (contacts, labels, AI, webhooks, profile) do not belong to a channel, so scope does not affect them.
Tool catalog
Threads
Org-scoped reads (filtered by channel access); mutations write only your own per-user view state.
| Tool | Access | Kind | Description |
|---|---|---|---|
list_threads | channel access | read | List threads visible to you |
get_thread_summary | channel access | read | Single-thread summary in list-row shape |
get_thread_messages | channel access | read | Paginated messages for a thread |
mark_thread_read | channel access | write | Clear your unread count |
mark_thread_unread | channel access | write | Force a thread unread for you |
archive_thread | channel access | write | Archive (per-user) |
unarchive_thread | channel access | write | Unarchive (per-user) |
mark_thread_spam | channel access | write | Move to Spam (per-user) |
mark_thread_promotions | channel access | write | Move to Promotions (per-user) |
mark_thread_automated | channel access | write | Move to Automated Messages — non-human mail (per-user) |
move_thread_to_inbox | channel access | write | Clear bucket flags (per-user) |
snooze_thread | channel access | write | Snooze until a timestamp (per-user) |
unsnooze_thread | channel access | write | Clear a snooze (per-user) |
delete_thread | channel access | destructive | Hide the thread from your view; the org keeps its copy |
restore_thread | channel access | write | Restore a soft-deleted thread |
delete_message | channel access | destructive | Hide a message from you |
hard_delete_thread | owner / admin | destructive | Permanently delete the shared thread and messages |
Outbound sends
CommSync enforces channel access before any send. Compose and forward also need at least one accessible channel of the right kind.
| Tool | Access | Kind | Description |
|---|---|---|---|
send_sms | channel access | write | Reply on a current SMS thread |
send_email | channel access | write | Reply on a current email thread |
compose_sms | channel access | write | Start a new SMS conversation |
compose_email | channel access | write | Start a new email conversation |
forward_message | channel access | write | Forward a message to a new recipient |
resend_failed_message | channel access | write | Retry a failed outbound message |
get_send_capacity | channel access | read | Sustainable send rate per line, plus any active rate-limit pause |
Rate limits never reach you
CommSync accepts every authorised send and owns delivery from that point
on — this covers carrier and mail-host rate limits too. Send tools do
not return 429 and never ask you to retry. When a provider throttles us
the message stays queued, backs off, and goes out on its own.
Every send tool returns a receipt that describes where the message actually is:
{
"accepted": true,
"messageId": "cm9x…",
"threadId": "cm7a…",
"state": "waiting_on_line",
"estimatedSendAt": "2026-07-29T18:41:12.000Z",
"pacing": {
"provider": "JustCall",
"sustainedPerMinute": 54,
"rateLimited": true,
"reason": "JustCall rate limit — waiting 45s"
}
}state is one of dispatching, waiting_on_line, scheduled, sent, or
failed. Never call a send tool twice for the same message — CommSync
has already queued a waiting_on_line message and will deliver it.
Before a bulk run, call get_send_capacity for the sustainable rate on each
line you can send from. If you exceed it, that is safe — messages queue
rather than fail; they simply take longer to go out.
Channel management
| Tool | Access | Kind | Description |
|---|---|---|---|
list_email_accounts | channel access | read | List email-account channels you can see |
add_email_account | any member | write | Link an IMAP/SMTP mailbox to the org |
delete_email_account | owner / admin | destructive | Remove an email account |
test_email_account | channel access | read | Test stored IMAP/SMTP credentials |
list_phone_numbers | channel access | read | List phone-number channels you can see |
add_phone_number | any member | write | Link a phone number to the org |
delete_phone_number | owner / admin | destructive | Remove a phone number |
Billing
| Tool | Access | Kind | Description |
|---|---|---|---|
get_billing_state | any member | read | Tier, status, seats, caps, usage |
change_tier | owner only | write | Swap subscription tier (prorates) |
set_seats | owner only | write | Set the paid seat quantity (prorates) |
seats_preview | owner only | read | Dry-run a prorated seat change |
Contacts
The contact graph is private per user — these never leak across users.
| Tool | Access | Kind | Description |
|---|---|---|---|
list_contacts | any member | read | List your contacts |
get_contact | any member | read | A contact, with identities and labels |
create_contact | any member | write | Create a new contact |
update_contact | any member | write | Update the display name or notes |
delete_contact | any member | destructive | Delete a contact; identities orphan |
merge_contacts | any member | destructive | Merge whole contacts into one survivor; CommSync deletes the sources |
merge_identities | any member | write | Merge two identities under one contact |
split_identity | any member | write | Detach an identity into an orphan |
attach_identity_to_contact | any member | write | Attach an orphan to a contact |
promote_identity_to_contact | any member | write | Promote an orphan into a new contact |
Identities
| Tool | Access | Kind | Description |
|---|---|---|---|
get_identity | any member | read | An identity and its contact |
update_identity_notes | any member | write | Edit per-channel notes |
list_orphaned_identities | any member | read | Identities not yet attached to a contact |
list_all_identities | any member | read | Every identity you own |
Labels
| Tool | Access | Kind | Description |
|---|---|---|---|
list_labels | any member | read | List labels with usage counts |
create_label | any member | write | Create a label (name and hex color) |
update_label | any member | write | Update the name, color, or AI prompt |
delete_label | any member | destructive | Delete a label everywhere |
assign_label | any member | write | Apply a label to a contact or identity |
unassign_label | any member | write | Remove a label |
AI
| Tool | Access | Kind | Description |
|---|---|---|---|
get_ai_settings | any member | read | Read AI configuration |
update_ai_settings | any member | write | Update AI configuration |
get_todays_digest | any member | read | Today's daily digest (or a date) |
list_digests | any member | read | Recent daily digests |
dismiss_digest | any member | write | Mark a digest dismissed |
trigger_digest_run | any member | write | Run a digest now |
list_ai_runs | any member | read | Recent AI activity entries |
test_ai_connectivity | any member | read | Check that CommSync has AI configured |
trigger_inbox_backfill | any member | write | Classify historical senders into Promotions or Spam |
Search, account & webhooks
| Tool | Access | Kind | Description |
|---|---|---|---|
search | any member | read | Search contacts, identities, messages |
search_threads | any member | read | Full thread-centric search: every conversation that matches a query, ranked and paginated, with match snippets |
get_profile | any member | read | Your profile (id, email, name) |
update_profile | any member | write | Update your display name |
list_webhooks | any member | read | List your webhook endpoints |
get_webhook | any member | read | A single endpoint |
register_webhook | any member | write | Create an endpoint (returns secret once) |
update_webhook | any member | write | Patch url / events / mode / status |
rotate_webhook_secret | any member | write | Rotate the signing secret (old valid 24h) |
delete_webhook | any member | destructive | Delete an endpoint + its history |
list_webhook_deliveries | any member | read | Paginated delivery log |
resend_webhook_delivery | any member | write | Retry a delivery |
Where CommSync Agents fit in
CommSync Agents are the AI teammates that answer inbound texts and email. You configure and manage them through the app or the REST admin API, not through this MCP tool catalog. See Agents for details.
There is deliberately no MCP tool that lets an external agent create,
reconfigure, or approve turns for a CommSync Agent. Use
send_sms or send_email above to have your own
integration reply directly instead.
Resources
Beyond tools, the server exposes MCP resources for direct reads:
commsync://threads/{threadId}/messages — messages in a thread
commsync://contacts/{personId} — a contact's detail
commsync://digests/{localDate} — the AI digest for a dateCommon workflows
Triage the inbox
list_threads → get_thread_messages(threadId) to read the latest.
Reply
get_thread_messages(threadId) to find the identity a message came in on →
send_sms or send_email with that identityId.
Merge a duplicate contact
list_orphaned_identities (or search) to find the stray identity →
merge_identities(identityAId, identityBId) or
attach_identity_to_contact(personId, identityId). For two whole
contact records of the same human, confirm both with get_contact and
call merge_contacts(survivorPersonId, sourcePersonIds) instead.
React in real time
You do not have to call list_threads again and again. Register a
webhook and call back into MCP only when an
event fires.
Next
Create a key in API keys, then wire up webhooks so your agent reacts to messages and does not have to ask again and again. Full parameter reference: /docs/mcp.txt.