CommSync

One inbox for business SMS and email. Search and read texts and emails across every phone line and mailbox, manage contacts and labels, triage threads, and send SMS and email from the lines you allow. Hosted remote server with OAuth 2.1.

Hosted MCP Server

npx add-mcp 'https://server.commsync.ai/api/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

MCP server

CommSync speaks the Model Context Protocol. Point an MCP-capable app or agent at the endpoint. It can then read threads, send messages, manage contacts and labels, and more.

There are two ways to authenticate, and both reach the same tools:

  • OAuth 2.1 is the primary way. The app signs you in with no shared secret. See Connect AI apps.
  • An API key is the alternative for scripts and servers. Send it as a Bearer token. See API keys.

OAuth is how Claude, ChatGPT, Codex, Cursor, and VS Code connect.

The signed-in user and their org role scope every operation.

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. To connect an AI app, paste the endpoint URL into the app and sign in. The Connect AI apps page has a guide for each app.

To call the endpoint from your own code with an API key:

Mint an API key

In CommSync, open **Settings → API Keys** and create a key. CommSync shows
it once — store it securely. See <a href="/docs/api-keys">API keys</a>.

Call the endpoint

POST JSON-RPC to the <code>/api/mcp</code> route on your CommSync API origin.

```bash
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 <code>tools/call</code> with the tool name and its arguments.

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": { "name": "list_threads", "arguments": { "limit": 20 } }
}
```
EndpointPOST /api/mcp (on your CommSync API origin)
TransportHTTP, stateless — one request per call
AuthOAuth 2.1 access token, or Authorization: Bearer csk_…

Authentication

OAuth 2.1

CommSync is its own authorization server. A client needs only the endpoint URL, because it discovers everything else:

  1. A request with no token gets 401 and a WWW-Authenticate header. The header names the protected-resource metadata (/.well-known/oauth-protected-resource/api/mcp, RFC 9728).
  2. That document names the authorization server. Its metadata is at /.well-known/oauth-authorization-server (RFC 8414).
  3. The client registers, sends you to the consent screen, and exchanges the code for tokens.
GrantAuthorization code with PKCE (S256 only)
Client registrationDynamic registration at POST /oauth/register (RFC 7591), or a Client ID Metadata Document: an https URL as the client_id
Client authenticationnone (public clients), client_secret_post, client_secret_basic
Scopemcp (the default when you omit scope)
ResourceThe endpoint URL (RFC 8707). CommSync binds each token to it
Access tokencsat_…, valid for one hour
Refresh tokencsrt_…, valid for 90 days. It rotates on every use. For 30 seconds after a use, the same token gets the same new pair again, for a client that refreshes twice at once. After that, a used token revokes its token family
RevocationPOST /oauth/revoke (RFC 7009)

The authorization response carries iss (RFC 9207). A loopback redirect (http://127.0.0.1, http://localhost, http://[::1]) can use any port.

A code is single-use. A second exchange that passes the PKCE check revokes the tokens that the first exchange issued. CommSync refuses a second exchange without the right verifier, and that exchange changes nothing.

API key

Send Authorization: Bearer csk_…. A key does not expire, and you revoke it under Settings, in API keys. A key suits a script or a server-side agent.

Authorization model

Every request resolves your credential (an OAuth token or an API 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 and apps

An API key or a connected app can be fine-tuned to specific lines instead of your full channel access. For a key, choose Specific lines when you create or edit it under Settings, in API keys. For an app, choose it on the consent screen. You can change it later under Settings, in Connect AI apps. Pick the email addresses and phone numbers it can touch.

The server then intersects your live channel access with the allowlist on every request. A scoped key or app 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.

ToolAccessKindDescription
list_threadschannel accessreadList threads visible to you
get_thread_summarychannel accessreadSingle-thread summary in list-row shape
get_thread_messageschannel accessreadPaginated messages for a thread
mark_thread_readchannel accesswriteClear your unread count
mark_thread_unreadchannel accesswriteForce a thread unread for you
archive_threadchannel accesswriteArchive (per-user)
unarchive_threadchannel accesswriteUnarchive (per-user)
mark_thread_spamchannel accesswriteMove to Spam (per-user)
mark_thread_promotionschannel accesswriteMove to Promotions (per-user)
mark_thread_automatedchannel accesswriteMove to Automated Messages — non-human mail (per-user)
move_thread_to_inboxchannel accesswriteClear bucket flags (per-user)
snooze_threadchannel accesswriteSnooze until a timestamp (per-user)
unsnooze_threadchannel accesswriteClear a snooze (per-user)
delete_threadchannel accessdestructiveHide the thread from your view; the org keeps its copy
restore_threadchannel accesswriteRestore a soft-deleted thread
delete_messagechannel accessdestructiveHide a message from you
hard_delete_threadowner / admindestructivePermanently 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.

ToolAccessKindDescription
send_smschannel accesswriteReply on a current SMS thread
send_emailchannel accesswriteReply on a current email thread
compose_smschannel accesswriteStart a new SMS conversation
compose_emailchannel accesswriteStart a new email conversation
forward_messagechannel accesswriteForward a message to a new recipient
resend_failed_messagechannel accesswriteRetry a failed outbound message
get_send_capacitychannel accessreadSustainable send rate per line, plus any active rate-limit pause

Email bodies

send_email and compose_email take two body fields: bodyText and bodyHtml. Give one of them or both. A call with neither fails before CommSync sends anything.

Plain text alone is fine. CommSync builds the HTML part from bodyText. It escapes the text, keeps the line breaks, and turns each http:// or https:// link into a clickable link. When you give bodyHtml, CommSync sends your HTML unchanged. When you give only bodyHtml, CommSync makes the plain-text part from it. The account signature and the "Sent from CommSync" footer, when they are on, go after your text in both parts.

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": "<platform name>",
    "sustainedPerMinute": 54,
    "rateLimited": true,
    "reason": "<platform name> 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

ToolAccessKindDescription
list_email_accountschannel accessreadList email-account channels you can see
add_email_accountany memberwriteLink an IMAP/SMTP mailbox to the org
delete_email_accountowner / admindestructiveRemove an email account
test_email_accountchannel accessreadTest stored IMAP/SMTP credentials
list_phone_numberschannel accessreadList phone-number channels you can see
add_phone_numberany memberwriteLink a phone number to the org
delete_phone_numberowner / admindestructiveRemove a phone number

Billing

ToolAccessKindDescription
get_billing_stateany memberreadTier, status, seats, caps, usage
change_tierowner onlywriteSwap subscription tier (prorates)
set_seatsowner onlywriteSet the paid seat quantity (prorates)
seats_previewowner onlyreadDry-run a prorated seat change

Contacts

The contact graph is private per user — these never leak across users.

ToolAccessKindDescription
list_contactsany memberreadList your contacts
get_contactany memberreadA contact, with identities and labels
create_contactany memberwriteCreate a new contact
update_contactany memberwriteUpdate the display name or notes
delete_contactany memberdestructiveDelete a contact; identities orphan
merge_contactsany memberdestructiveMerge whole contacts into one survivor; CommSync deletes the sources
merge_identitiesany memberwriteMerge two identities under one contact
split_identityany memberwriteDetach an identity into an orphan
attach_identity_to_contactany memberwriteAttach an orphan to a contact
promote_identity_to_contactany memberwritePromote an orphan into a new contact

Identities

ToolAccessKindDescription
get_identityany memberreadAn identity and its contact
update_identity_notesany memberwriteEdit per-channel notes
list_orphaned_identitiesany memberreadIdentities not yet attached to a contact
list_all_identitiesany memberreadEvery identity you own

Labels

ToolAccessKindDescription
list_labelsany memberreadList labels with usage counts
create_labelany memberwriteCreate a label (name and hex color)
update_labelany memberwriteUpdate the name, color, or AI prompt
delete_labelany memberdestructiveDelete a label everywhere
assign_labelany memberwriteApply a label to a contact or identity
unassign_labelany memberwriteRemove a label

AI

ToolAccessKindDescription
get_ai_settingsany memberreadRead AI configuration
update_ai_settingsany memberwriteUpdate AI configuration
get_todays_digestany memberreadToday's daily digest (or a date)
list_digestsany memberreadRecent daily digests
dismiss_digestany memberwriteMark a digest dismissed
trigger_digest_runany memberwriteRun a digest now
list_ai_runsany memberreadRecent AI activity entries
test_ai_connectivityany memberreadCheck that CommSync has AI configured
trigger_inbox_backfillany memberwriteClassify historical senders into Promotions or Spam

Search, account & webhooks

ToolAccessKindDescription
searchany memberreadSearch contacts, identities, messages
search_threadsany memberreadFull thread-centric search: every conversation that matches a query, ranked and paginated, with match snippets
get_profileany memberreadYour profile (id, email, name)
update_profileany memberwriteUpdate your display name
list_webhooksany memberreadList your webhook endpoints
get_webhookany memberreadA single endpoint
register_webhookany memberwriteCreate an endpoint (returns secret once)
update_webhookany memberwritePatch url / events / mode / status
rotate_webhook_secretany memberwriteRotate the signing secret (old valid 24h)
delete_webhookany memberdestructiveDelete an endpoint + its history
list_webhook_deliveriesany memberreadPaginated delivery log
resend_webhook_deliveryany memberwriteRetry a delivery

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 date

Common 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 →
<code>send_sms</code> or <code>send_email</code> 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
<a href="/docs/webhooks">webhook</a> and call back into MCP only when an
event fires.

Connect an app in Connect AI apps, or 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.