What connecting Claude to your CRM actually gives you
Connecting Claude to your CRM over MCP gives the assistant a live, permissioned door into your own records, so it can search contacts, read whole DM threads, review the deal pipeline, check the ledger and draft or send replies without you pasting a single screenshot into the chat. The boundary of what it may touch is not a promise in a system prompt, it is the list of scopes on the API key you minted, enforced on every call by the server.
The Model Context Protocol is an open standard that lets an AI client call a server's tools, read its resources and load its prompt templates over one authenticated connection. CRM Solid publishes such a server at https://api.crmsolid.com/mcp. It exposes 62 tools, 21 resources and 15 prompts, speaks protocol revision 2025-06-18 over Streamable HTTP, is available on the Business plan, and authenticates with a bearer API key you mint in the panel.
The practical difference is easier to feel than to describe. Without MCP, asking an assistant about your pipeline means exporting a CSV, trimming it down to fit the context window, pasting it, and then re-pasting it tomorrow when it is stale. With MCP, you ask the question and the assistant fetches the current rows itself, in the order it needs them, following up on whatever it finds. It reads a deal, notices the last message in the linked conversation is nine days old, checks whether a task was ever created for it, and tells you that the deal is not slow, it is simply forgotten.
Three things change the day you connect it. First, the copy-paste step disappears, which removes both the tedium and the truncation that used to hide the row you actually needed. Second, the context is never stale, because there is no snapshot: every answer is assembled from the records as they stand at the moment you ask. Third, the actions become reviewable, because a write tool writes to the same audit trail your team sees. When Claude tags a contact, the contact timeline records a TagAdded activity exactly as it would if a human had clicked the button.
It is worth being clear about what this is not. It is not a chatbot bolted onto the panel, and it is not a bulk export. Claude is not given a copy of your database, it is given the ability to make individual, scoped, rate-limited calls against it, one question at a time, while you watch. If you want the protocol architecture, the session model, the discovery document and the full security reasoning behind that design, that all lives on the MCP server overview. This page is about getting Claude connected and then getting real work out of it.
The rest of this page is a setup walkthrough followed by the part most setup guides skip: what to actually ask once the green light comes on. A connected assistant that you only ever ask to list contacts is a worse contact list. The value shows up in the questions that would have cost you four tabs and twenty minutes.
Which Claude surface should you connect?
Use Claude Code if you want the shortest and least fragile setup, because it speaks remote Streamable HTTP MCP natively and accepts a static Authorization header on the command line. Use Claude Desktop if you would rather have the CRM in a chat window than a terminal, and accept that a bearer key has to travel through the mcp-remote bridge, because the Desktop connector screen is built for OAuth and has no field for a static header.
Claude Code: the direct path
Claude Code is the terminal client, and for this server it is the best-fitted one. It supports the HTTP transport as a first-class option, so there is no bridge process, no extra dependency and one less thing to misconfigure. It also gives you something the other clients do not: a choice of where the server definition is stored, made at install time with a single flag. That matters more than it sounds, because the difference between a key that is committed to a shared repository and a key that never leaves your machine is one letter in that flag.
The trade-off is that Claude Code lives in a terminal and is oriented around a working directory. If you want CRM access while writing code, in the repository that talks to the CRM, that is exactly right. If you want to ask about your pipeline from an armchair, it is the wrong shape.
Claude Desktop: the honest limitation
Claude Desktop can absolutely use this server, but not through the screen you would expect. Its Settings and Connectors path is designed around OAuth: you pick a connector, you get bounced to an authorization server, you come back with a token. There is no field anywhere in that flow for pasting a static Authorization: Bearer value. This is the detail nearly every competing integration page gets wrong, usually by showing a screenshot of the connector dialog next to an API key that has nowhere to go in it.
The supported route is the config file. Claude Desktop reads a JSON file that defines MCP servers as local commands, which is a stdio interface: the client launches a process and talks to it over standard input and output. A remote HTTP endpoint is not a process, so you put a small bridge in between. That bridge is mcp-remote, launched with npx, and it does exactly one useful thing: it speaks stdio to Claude Desktop, speaks Streamable HTTP to https://api.crmsolid.com/mcp, and carries your Authorization header across the gap. The full block is in the Claude Desktop section below.
The Claude apps on the web and on mobile
The browser and mobile apps are not a fit for this server today, and the reason is structural rather than a missing feature. There is no local process for a bridge to run in, so the mcp-remote workaround has nowhere to execute. That leaves the custom-connector path, which is OAuth-only, and CRM Solid's discovery document at /.well-known/oauth-protected-resource currently advertises bearer API-key authentication with an empty authorization_servers list. An OAuth-only client has nothing to negotiate against.
So the practical answer is: connect from a desktop client. Claude Code if you live in a terminal, Claude Desktop if you do not, and one of the editors in the also-works-with section if your day already happens inside one. All of them reach the same endpoint with the same key and see the same 62 tools.
Mint a Business-plan key with the right scopes
Every setup on this page starts with the same object: an API key minted in the panel at Settings > Developers > API keys, on a workspace that is on the Business plan. Keys look like csk_live_..., they are shown in full exactly once, and they carry the scope list you tick at creation time. There is also a dedicated MCP page in the panel sidebar at app.crmsolid.com/mcp that collects the endpoint, the client snippets and the key management in one place.
Check the plan first. This is the single most common wasted debugging hour: a Free or Pro workspace can mint a perfectly valid key, and that key will authenticate, and then every MCP call will answer HTTP 402. Nothing about the key is wrong. If your client connects and immediately reports a payment-required error, stop reading logs and go look at the plan.
Now the scopes. These are exact strings and they are all in the shape resource:action, never the reverse. The full list you can grant is:
contacts:readandcontacts:writetelegram:send,twitter:sendsocial:readandsocial:writeposts:readandposts:writedeals:readanddeals:writetasks:readandtasks:writeemail:readandemail:writesequences:readandsequences:writewebhooks:readandwebhooks:writepipelines:read,analytics:read,finance:read,jobs:readagents:readandagents:run
Three further scopes exist and are deliberately left unchecked by default: finance:write, email:send and keys:manage. Treat them as a separate decision rather than part of the default sweep, and in particular think hard before putting keys:manage on a key that a conversational assistant holds, because that is the scope that can mint more keys.
A useful way to size a key is to count what it unlocks. In the current build contacts:read covers 8 tools, contacts:write covers 7, social:read covers 5, finance:read covers 4, and analytics:read, posts:read and posts:write cover 3 each. The sending scopes are the narrowest of all: telegram:send and twitter:send unlock exactly one tool apiece. That is worth knowing, because it means you can grant an assistant broad reading ability and withhold the ability to speak by leaving three small boxes unticked.
Copy the token the moment it appears and put it straight into an environment variable, for example CRMSOLID_MCP_KEY. Every config shape below assumes the token lives there and not in the file you are editing. The full list of which tool needs which scope is on the MCP tools reference, and the persona section further down turns that list into three ready-made scope sets.
Set up Claude Code, step by step
Claude Code connects with one command, and the command is short because the client supports the HTTP transport directly. Here is the whole thing, in order.
Step 1: put the key in your environment
Add the token to your shell profile rather than typing it into a command that your shell history will remember forever:
# ~/.zshrc, ~/.bashrc, or your secret manager of choice
export CRMSOLID_MCP_KEY="csk_live_YOUR_KEY"On Windows the equivalent is a user environment variable, set once, so that every new terminal sees it. The point is the same everywhere: the key should be something your shell knows and your repository does not.
Step 2: add the server
The command that registers the server is claude mcp add with an explicit transport:
claude mcp add --transport http crmsolid https://api.crmsolid.com/mcp \
--header "Authorization: Bearer ${CRMSOLID_MCP_KEY}"Reading it left to right: --transport http tells the client this is a remote Streamable HTTP server rather than a local command, crmsolid is the name you will see the tools grouped under, the URL is the endpoint, and --header carries the bearer key. Keep the transport flag even though some clients guess: guessing is exactly what produces the stdio misconfiguration further down this page.
Step 3: choose the config scope
Claude Code stores server definitions in one of three places, chosen with -s. This is a security decision as much as a convenience one:
-s localis the default. The server exists for you, in this project only, and the definition is not written into a file your teammates will pull. This is the right choice for a key that is personal to you.-s projectwrites the definition into a.mcp.jsonfile at the root of the repository, which means it is shared with everyone who clones it. Use it so the team inherits the server entry, and make sure the key itself resolves from the environment rather than sitting in the file.-s usermakes the server available to you in every project on this machine. This is the choice for a CRM assistant you want on hand regardless of which repository you happen to be in.
A good default for a solo operator is -s user. A good default for a team is -s project for the entry plus an environment variable for the key, so that a new developer runs one export and is done.
Step 4: what the .mcp.json entry must look like
If you write the entry by hand rather than through the CLI, or you want to review what the CLI produced, this is the shape. The one line people leave out is "type": "http", and leaving it out is fatal:
{
"mcpServers": {
"crmsolid": {
"type": "http",
"url": "https://api.crmsolid.com/mcp",
"headers": {
"Authorization": "Bearer ${CRMSOLID_MCP_KEY}"
}
}
}
}Why "type": "http" is mandatory. A file-based MCP entry with no explicit type is treated as a stdio command server. The client then tries to execute your URL as if it were a program, gets nothing that looks like a JSON-RPC stream back, and reports a startup failure that says nothing about HTTP at all. The symptom is a server that never lists a single tool, and the fix is one line. If you take nothing else from this page, take this line.
The ${CRMSOLID_MCP_KEY} placeholder is there so a committed .mcp.json carries the server definition without carrying the secret. Claude Code expands environment variables in these values, so the file stays safe to check in and the token stays in your shell. If you would rather not rely on expansion at all, use -s local or -s user instead and keep the key out of the repository entirely.
Step 5: verify
Run claude mcp list to confirm the server is registered, then open Claude Code and use the /mcp command to see the connection state and the tools it has discovered. A healthy connection lists tools under the crmsolid name. If the list is empty but the server is connected, the key is almost certainly missing scopes rather than broken.
Then ask the verification question. The best first question is one that needs a single, cheap, read-only tool:
Ask: Using the CRM, give me a one-paragraph summary of my account: how many contacts, how many messages queued and sent today, and which accounts are connected.
What a good first response looks like. Claude calls crm_dashboard_summary, tells you it did, and reports your real totals: your contact count, today's queued and sent message counts, and the list of connected accounts. It should read like a status line, not like a description of what a CRM is. If you get a generic paragraph about CRMs with no numbers in it, the tool was never called and the connection is not live. If you get a scope error instead, see the troubleshooting table: that is a good sign, because it means the transport works and only the key needs widening.
Set up Claude Desktop through mcp-remote
Claude Desktop reaches the same endpoint through a local bridge, configured in a JSON file you edit by hand. There is no screen in the app that does this for a bearer key, so do not go looking for one.
Step 1: find the config file
The file is called claude_desktop_config.json and it lives here:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
If the file does not exist yet, create it. If it does exist and already has an mcpServers object, add your entry inside the existing object rather than pasting a second top-level block, which is invalid JSON and will be rejected silently at startup.
Step 2: add the bridge entry
{
"mcpServers": {
"crmsolid": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.crmsolid.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer csk_live_YOUR_KEY"
}
}
}
}Note two deliberate choices in that block. The header is passed as Authorization: with no space, and the actual value including the word Bearer lives in env. That is the workaround for the header trap described in the next section, and it is worth doing from the start rather than after an hour of silent 401s. Second, npx -y fetches and runs the bridge without an interactive install prompt, which matters because Claude Desktop launches this process with no terminal attached to answer a prompt.
Step 3: restart, properly
Quit Claude Desktop completely and reopen it. Closing the window is not enough on either macOS or Windows: the app keeps running and keeps the old configuration in memory, so your edit appears to do nothing. Quit from the menu or the tray, then launch again. The config file is read at startup and only at startup.
After the restart the CRM tools appear in the tools list inside the conversation. Ask the same verification question as above. The first launch may take a few extra seconds while npx fetches the bridge, which is normal and only happens once per version.
The Authorization header trap
The single most confusing failure in MCP client setup is a silent 401 caused by a truncated header, and the cause is a space. Several clients pass --header "Authorization: Bearer csk_live_..." through to a child process without escaping the space inside the quoted value, so the process receives only Authorization: and the word Bearer and the token are parsed as separate arguments. The server receives a header that is either empty or malformed, answers 401, and the client shows you an authentication failure for a key that is perfectly valid.
What makes it so costly is that everything you would naturally check looks fine. The key works in curl. The URL is right. The plan is Business. You re-copy the key, you re-mint the key, you check for a trailing newline. None of it helps, because the key never arrives intact.
The fix is to split the header so that no argument contains a space. Pass the header name and an environment reference as one unbroken string, and put the full value, including the word Bearer and the space after it, into the environment:
"args": [
"-y",
"mcp-remote",
"https://api.crmsolid.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer csk_live_YOUR_KEY"
}The argument the client passes is now Authorization:${AUTH_HEADER}, a single token with no space in it, and the substitution happens inside the bridge where quoting is no longer an issue. The header reaches the server whole.
How to tell this is your problem rather than a genuinely bad key: the request reaches the server, so you get a clean 401 rather than a connection error or a timeout, and the same key pasted into a plain curl call succeeds. If curl works and the client does not, suspect the quoting before you suspect the credential.
curl -s -X POST https://api.crmsolid.com/mcp \
-H "Authorization: Bearer csk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'A successful call returns the tool catalogue. A 402 means the plan, a 401 means the credential, and anything else means the endpoint or the transport. That one command separates three problems that otherwise look identical from inside a client.
Also works with Cursor, VS Code and Windsurf
The same endpoint and the same key work in the three editor clients people ask about most, but the config shapes genuinely differ and copying the wrong one between them produces a server that never appears rather than an error you can read. Here is one exact block each. For anything deeper, including the protocol and session details these clients all sit on top of, see the MCP server overview.
Cursor
Cursor reads ~/.cursor/mcp.json for a global server or .cursor/mcp.json inside a project for a scoped one. It uses mcpServers with url and headers, and it has no type field. It supports ${env:VAR} interpolation, so the key can come from your environment:
{
"mcpServers": {
"crmsolid": {
"url": "https://api.crmsolid.com/mcp",
"headers": {
"Authorization": "Bearer ${env:CRMSOLID_MCP_KEY}"
}
}
}
}VS Code
VS Code reads .vscode/mcp.json and differs in two ways that catch people out. The top-level key is servers, not mcpServers, and the entry does need "type": "http". It also has the nicest secret handling of the group: an inputs array with password set to true, referenced as ${input:id}, so the editor prompts you once and the key never enters the file:
{
"inputs": [
{
"id": "crmsolid-key",
"type": "promptString",
"description": "CRM Solid MCP API key",
"password": true
}
],
"servers": {
"crmsolid": {
"type": "http",
"url": "https://api.crmsolid.com/mcp",
"headers": {
"Authorization": "Bearer ${input:crmsolid-key}"
}
}
}
}Windsurf
Windsurf reads ~/.codeium/windsurf/mcp_config.json. The field that holds the endpoint is serverUrl, not url, which is the single thing that breaks a config pasted over from Cursor. It supports ${env:} and ${file:} interpolation:
{
"mcpServers": {
"crmsolid": {
"serverUrl": "https://api.crmsolid.com/mcp",
"headers": {
"Authorization": "Bearer ${env:CRMSOLID_MCP_KEY}"
}
}
}
}Across all three, the debugging order is the same: confirm the top-level key name, confirm the URL field name, confirm whether type belongs there, and only then look at the credential. Three of the four common failures are spelling.
What to actually ask Claude once it is connected
A connected assistant is only worth the setup if you ask it things a search box cannot answer. The prompts below are grouped by the job they belong to, and each one names the tools Claude reaches for underneath and what a good answer actually looks like, so you can tell a working setup from a hallucinating one. The 15 prompt templates the server ships are named where they match, because a client that lists prompts lets you run them without typing any of this out.
Morning triage
1. "Give me a morning briefing: what happened overnight and what needs me first?"
Under the hood Claude reaches for crm_dashboard_summary for the headline counts, the crm://tasks/today and crm://recent-conversations resources for what is already loaded, and crm_social_inbox_summary for anything waiting across the social channels. The daily-briefing prompt template does this in one shot. A good answer is ranked, not exhaustive: three or four items, each naming a real contact or deal, with the reason it is at the top. A bad answer is a wall of KPIs with no ordering, which means the model summarised the tool output instead of reasoning over it.
2. "What is overdue right now, and what is due today? Put the oldest first."
This one uses crm_list_tasks, backed by the crm://tasks/overdue and crm://tasks/today resources, which are deliberately separate: overdue means strictly before now, today includes the rest of the day. The task-prioritize prompt pulls overdue tasks plus the next 48 hours and asks for a single ordered list. A good answer separates the two buckets, keeps the linked contact or deal attached to each task, and does not quietly reorder overdue work by priority when age is the more useful signal.
3. "Did anything fail to send since yesterday? Show me the actual error."
Claude calls crm_list_jobs to find the failures and crm_get_job for the full text, target and lastError of any that look interesting. A good answer quotes the error rather than paraphrasing it, and distinguishes categories that need different responses: a rate-limit wait resolves itself, a bad username never will. If Claude tells you everything is fine without naming a job id, ask it to show its work.
4. "Which of my connected accounts is under the most pressure this week?"
This combines crm_list_accounts with crm_messaging_stats scoped per account over a 7-day window, and it is exactly what the audit-account-health prompt automates. A good answer compares sent against failed as a ratio rather than a raw count, because the account sending the most is not the account in trouble. Pair it with crm://kpis/7d when you want the per-day shape rather than the total.
Inbox zero
5. "Who is waiting on a reply across every channel, oldest first?"
The opening move is crm_social_inbox_summary, which returns conversation and unread totals per network plus the threads still waiting, oldest first, in a single call. Claude then adds crm_list_recent_conversations for the CRM-side threads and crm_search_email_threads for the inbox. The social-inbox-triage prompt does the social half and asks for a ranking with reasons. A good answer merges the channels into one list instead of handing you three lists to merge yourself.
6. "Summarize the Instagram thread with this handle and tell me what they actually want."
Claude finds the thread with crm_list_social_conversations, opens it with crm_get_social_conversation for the participant, the bridged CRM contact and the last ten messages, and pages further back with crm_list_social_messages if the answer is older than that. Voice notes come through with their transcript attached and translated messages carry both wordings, so a good answer quotes what the person said rather than describing that they sent a voice note.
7. "Summarize this email thread and tell me the one open question."
crm_search_email_threads to locate it, then crm_get_email_thread for the messages in order plus any AI summary and lead score already attached. The summarize-email-thread prompt is built for this and explicitly does not draft a reply, which is the right default for a thread you have not read yet. A good answer ends with one question, not a list of themes.
8. "Close the threads I have already handled and assign the rest."
This is the write half of inbox work: crm_set_email_thread_status moves a thread between open, pending and closed, crm_assign_email_thread hands it to a teammate, and crm_mark_social_conversation_read clears a social unread badge on the network as well as in the CRM. All three need write scopes, none of them sends mail, and the social one is idempotent so re-running it is harmless. A good answer lists what it is about to change and waits.
Pipeline review
9. "Review my pipeline: give me a weighted forecast and the three deals most at risk."
Claude pulls crm_list_deals, ordered by stage then value with open task counts attached, and the crm://deals/open resource for the live opportunity list ranked by size. The pipeline-review prompt asks for exactly this shape. A good answer states the weighting it used, names the three deals, and gives a specific reason per deal, ideally one drawn from the data rather than from general sales wisdom: no open task, no message in three weeks, probability unchanged since the stage moved.
10. "Which stages are clogged? Show me the shape of the board."
crm_list_pipelines returns every board with its ordered stage columns and per-stage contact counts, and crm_get_pipeline zooms into one. The crm://pipelines and crm://deals/pipeline resources give the same picture without a tool call. A good answer talks about the ratio between adjacent stages rather than the absolute size of one, because a fat stage is only a problem relative to the one after it.
11. "Move the Acme deal to negotiation, note why, and remind me to chase it Thursday."
Three tools in sequence: crm_update_deal_stage, crm_add_contact_note and crm_create_task. Two limits are worth knowing before you ask. crm_update_deal_stage accepts lead, qualified, proposal, negotiation and lost, and refuses won on purpose, because moving a deal to won books an income ledger entry and that belongs in the panel. And crm_add_contact_note appends into a 500-character field that drops the oldest text from the front when it fills, so a chatty assistant can push out an earlier note. Ask for short notes.
12. "Score this contact, tag them, and show me the timeline of what changed."
crm_set_lead_score sets a manual 0 to 100 score and flags it as a non-AI override. Tagging is deliberately two steps: crm_list_tags or crm_create_tag to get the tag, then crm_tag_contact to attach it, because the attach tool rejects an unknown name rather than inventing a tag. crm_get_contact_activity then shows notes, stage changes, tags, score changes and assignments newest first. A good answer confirms the tag already existed instead of quietly creating a near-duplicate.
Follow-up drafting
13. "Draft a follow-up to this contact that references what we actually discussed."
The value here is that Claude reads the thread rather than your summary of it: crm_get_contact for the contact plus the last ten messages, or crm_get_conversation for the full thread, up to 50 messages a page, newest first. The draft-followup-message prompt tailors the draft to the contact's stage and offers a tone selector. A good draft quotes a specific detail from the thread in the first two lines. If it opens with "I hope this finds you well", the tool output was not used.
14. "Draft it, do not send it, and show me what our AI agent would have replied."
crm_list_agents reports the configured auto-reply agents with their status, channels, response mode and 24-hour run count. crm_run_agent then test-runs one against a sample inbound message and returns the reply it would send. That reply is never delivered to anyone and no CRM record is created, changed or deleted, which makes it the safest way to audit an agent's tone before you let it loose. It costs AI credits, which is the only reason it is annotated as a write tool.
15. "Send it on Telegram from the right account, then confirm it went."
Sending is a two-part move. crm_list_accounts first, because crm_send_telegram_message needs an accountId and guessing it is how a message goes out from the wrong identity. The send is asynchronous, so the honest confirmation is crm_list_jobs or crm_get_job afterwards rather than the send call's own response. The crm://accounts and crm://jobs/recent resources cover both ends without a tool call.
16. "This sequence is under-performing. Pause it and tell me what to change."
crm_list_sequences for status and target progress, crm_get_sequence_status for the deep report with processed, successful and failed counts plus the message steps and recent job activity, then crm_pause_sequence and later crm_resume_sequence, both idempotent. The outreach-plan prompt asks for next week's focus plus one improvement per under-performing sequence. A good answer points at a specific step in the sequence, not at the sequence as a whole.
Finance check
17. "How did we do in the last 30 days, per currency?"
crm_finance_summary returns realized income, expense and net per currency plus outstanding totals and the top expense categories over a window, and crm_list_transactions lists the underlying ledger entries newest first. The weekly-finance-report prompt does the 7-day version grouped per currency. Both are reporting only: they read the ledger and cannot move, charge or settle money, so this is a question you can safely ask an assistant that has no business touching your books.
18. "Who owes us money, and how overdue is it?"
crm_list_invoices lists invoices newest first with an outstanding summary per currency, and the crm://finance/invoices resource surfaces the most overdue ones directly. It cannot create, send, void or pay an invoice and it returns no payment link, which is the correct boundary. A good answer sorts by how overdue rather than by amount, then tells you which of them already has an open task against the linked contact.
19. "Are all my revenue sources still syncing?"
crm_revenue_sources_summary lists the configured external revenue sources with last-sync status, total ingested count and last event time. Secrets are never returned, and it cannot add, edit or trigger a source. This is a question worth asking weekly, because a source that quietly stopped ingesting looks exactly like a slow month until someone checks.
Social replies and publishing
20. "Which network has the biggest unread pile right now?"
One call: crm_social_inbox_summary gives totals overall and per network plus the threads waiting on a reply. Ask it first thing and it becomes the routing decision for the next twenty minutes. The crm://social/inbox resource carries the same picture pre-loaded.
21. "Draft a reply to this DM in the customer's language, then send it once I approve."
crm_get_social_conversation and crm_list_social_messages for the thread, then the dm-reply-draft prompt, which drafts using the real history, the participant's language and the platform's length conventions, and never sends. Sending is a separate, explicit step through crm_send_social_message, which reaches a real person immediately, pauses the AI auto-reply for the linked contact and records the send on the CRM timeline. Platform windows still apply: WhatsApp only allows a free-form reply within 24 hours of the customer's last message.
22. "What is going out this week, and did anything fail?"
crm_list_social_posts returns scheduled and published posts with status, published URL and any failure reason, crm_get_social_post opens one, and crm_social_post_stats gives publishing volume and outcome by status and network over a recent window. A good answer separates "scheduled and fine" from "failed and nobody noticed", because those need different responses and only one of them is urgent.
23. "Schedule Thursday's post on LinkedIn and X, and push the Instagram one to Friday."
Start with crm_list_social_accounts so Claude picks real account ids, then crm_schedule_social_post, which creates one post per target account, so two platforms means two posts. Per-platform rules are enforced at that call: TikTok and YouTube need a video, Instagram needs media, and X caps at 280 characters. crm_update_social_post edits a post that has not gone out and crm_cancel_social_post stops one that has not. Neither can touch a post that already published. Watch for publishNow: it goes to a real audience straight away, so it should be something you asked for rather than something the assistant chose.
24. "Plan next week's posting based on how the last month went."
The weekly-content-plan prompt combines the connected accounts, what is already scheduled and the last 30 days of posting performance into a concrete plan. It is the one prompt on this list that is worth running even when you already know what you want to post, because it makes the gaps in the calendar visible before they become a quiet week. More on the social surface specifically is on social media over MCP.
Choosing scopes: three personas
The most useful security control you have is the scope list, and the easiest way to use it well is to decide which of three assistants you are building before you tick a single box. Each persona below is a concrete scope set plus an explicit statement of what that key physically cannot do.
Persona 1: the read-only briefing assistant
Scopes: contacts:read, analytics:read, pipelines:read, deals:read, tasks:read, email:read, sequences:read, jobs:read, social:read, posts:read, finance:read, agents:read.
This is the assistant that answers questions and never changes anything. It can brief you on the pipeline, read any conversation, summarise an email thread, report the ledger, tell you which sequence is failing and which post did not go out. Every prompt in the morning triage, pipeline review and finance groups above works on this key.
What it cannot do: send a Telegram message, send an X DM, send a social DM, publish or schedule a post, move a deal, create or complete a task, add a note, tag a contact, set a lead score, assign a contact or a thread, pause or resume a sequence, register or delete a webhook, or run an agent. Every one of those needs a write scope this key does not hold, and the server answers JSON-RPC -32002 with the missing scope named rather than failing ambiguously.
This is the right default for a first key, for a shared machine, and for anyone who wants to see what the surface feels like before granting anything that writes. It is also the right persona for a scheduled briefing that runs without a human watching.
Persona 2: the drafting assistant
Scopes: everything in persona 1, plus contacts:write, tasks:write, deals:write, email:write and agents:run.
This is the assistant that does the CRM housekeeping around a conversation while you keep the conversation itself. It can write notes, tag and untag, set lead scores, assign contacts, move deals between stages, create and complete tasks, set an email thread's status, assign a thread to a teammate, and test-run an agent to see what it would have said.
What it cannot do: deliver a message to a human being. It holds none of telegram:send, twitter:send, social:write or posts:write, so it can draft anything and send nothing. It also cannot mark an email thread as sent mail, because email:write covers thread status and assignment and does not send. And it still cannot move a deal to won, because no scope enables that.
This is the persona most teams should actually run day to day. The drafting is where the leverage is, and the send button is where the regret is. Splitting them at the scope layer means an over-eager assistant produces a draft you delete rather than a message a customer receives.
Persona 3: the full operator
Scopes: everything in persona 2, plus telegram:send, twitter:send, social:write, posts:write, sequences:write and, only if you need it, webhooks:read.
This key runs the desk. It sends DMs on Telegram and X, replies in social threads, clears unread badges, schedules and cancels posts, and pauses or resumes outbound sequences. It is genuinely useful and it should be treated like a credential that can talk to your customers, because it is one.
What it still cannot do: the safe-write boundary holds even here. It cannot create a ledger transaction, pay or void an invoice, or force a deal to won. crm_run_agent remains a dry run whose reply is never delivered. The one hard delete anywhere in the surface is crm_delete_webhook, which is annotated destructive and needs webhooks:write, and there is no good reason for a conversational assistant to hold that scope. Leave it off.
Treat finance:write, email:send and keys:manage as outside all three personas. They are unchecked by default for a reason, and the reason is that the blast radius of a confused assistant holding them is larger than anything the other scopes can produce.
Daily-driver habits
The setup is a one-off. These are the habits that keep it from becoming a problem three months later, when the key is in a repository nobody remembers committing to and the laptop it was minted for has been replaced.
Keep the key out of source control
The key belongs in an environment variable or a secret manager, and the config file belongs in the repository. Every client shape on this page supports that split: Claude Code expands environment variables in .mcp.json, Cursor and Windsurf take ${env:VAR}, and VS Code takes an inputs entry with password set to true so the editor prompts and stores it outside the file. If you are using Claude Code and would rather not think about it, install with -s local or -s user, both of which keep the definition out of the shared file altogether.
If a key does end up committed, treat it as leaked even in a private repository. Rewriting history does not reliably remove it from every clone, mirror and cached view. Revoke it and mint a replacement. Revocation is instant, which is exactly what you want in that moment.
Use one key per machine, per assistant
Rate limits are per key, and so is your ability to reason about what happened. One shared key across a laptop, a desktop and a scheduled job produces a single blended stream you cannot separate. Three keys named for where they live let you revoke the laptop without touching the job, and let you widen the scopes on one without widening them everywhere.
Name them for the machine and the persona rather than the person: claude-code-laptop-readonly tells you more nine months later than my-key-2. The same discipline applies when you hand a teammate access: they mint their own key with their own scopes, rather than receiving a copy of yours.
Revoke deliberately, rotate in order
Revoking is done in the panel under Settings > Developers > API keys, and it takes effect immediately for every client already holding the key. That is the correct behaviour for a leak and the wrong order for a planned rotation. To rotate without downtime, mint the replacement first, update the client config, confirm the tools still list, and only then revoke the old key.
Do a scope review at the same time. A key that was widened for one afternoon's work is still wide today. If the assistant only ever briefs you, take the write scopes back off, because a scope you are not using is a scope that can only cost you.
What happens when you hit 60 requests per minute
The default limit is 60 requests per minute per key, and the Business ceiling is 300. When you exceed it the server answers HTTP 429 with a Retry-After header, and a well-behaved client waits that long and retries rather than hammering the endpoint.
In interactive use this is generous. A conversational turn usually costs a handful of calls: an inbox summary is one call, a pipeline review might be three or four, a thread walked back through several pages might be six. You will not notice the limit while talking to the assistant. Where it bites is a batch instruction, the kind that starts with "for each of my open deals". Fifty deals times two calls each is a hundred calls, and Claude will pause partway through waiting on a retry.
Two habits keep that pleasant. Scope batch questions before you ask them, so that "the top ten by value" replaces "every one". And when you genuinely need to walk a large set, that is the moment the REST API is the better tool: it is built for loops, it does not spend model tokens on each row, and it can be told to run overnight. The trade-off is laid out on MCP vs REST API vs Zapier, and the endpoints are on the public API page.
Sessions, and why you rarely think about them
When a client calls initialize, the server returns an Mcp-Session-Id header that later calls may send back, and a DELETE to the endpoint tears the session down. Stateless calls with no session header still work, which is why a plain curl against tools/list returns the catalogue with no ceremony. Every client on this page handles the session lifecycle for you. It is worth knowing only because it explains why a quick command-line check behaves the same as a full client.
One more property worth remembering when you read tool output: MCP responses are camelCase, while the REST v1 API is PascalCase. If you are reading both in the same session, that difference is the fastest way to tell which surface a snippet came from.
Troubleshooting
Six failures cover almost everything. The table maps the symptom you see to the cause, and the notes below it explain the two that are genuinely confusing.
| Symptom | Cause | Fix |
|---|---|---|
| HTTP 401 on every call, key looks correct | The Authorization header was truncated at the space, or the key was revoked | Split the header with the token in env, then re-test the same key with curl |
| HTTP 402 immediately after a successful connect | The workspace is on Free or Pro; MCP is a Business-plan surface | Upgrade the workspace. The key itself needs no change |
| JSON-RPC error -32002 on one tool only | The key is missing that tool's scope | Read data.requiredScope and data.granted, mint a key with the missing scope |
| HTTP 429 partway through a long request | Over 60 requests per minute on that key | Wait out Retry-After, narrow the question, or move the batch to the REST API |
| Server connects but lists no tools | The key holds no scopes, or the client cached an older catalogue | Check the scopes on the key, then restart the client so it re-lists |
| Client reports the server exited or failed to start | A url entry with no "type": "http" is being run as a stdio command | Add "type": "http", or use --transport http on the add command |
The 401 that is not an authentication problem
A 401 with a key you just minted almost always means the header was mangled rather than rejected. The test that separates the two takes ten seconds: run the curl command from the header-trap section above with the same key. If curl returns the tool catalogue, the credential is fine and the client is mangling it, so apply the split-header workaround. If curl also returns 401, the key really is invalid or revoked, and you should mint a new one.
The reason this is worth a paragraph is that the natural response to a 401 is to suspect the key, and suspecting the key sends you around a loop of re-copying and re-minting that cannot possibly help. Test the credential outside the client first.
The stdio misconfiguration
The second confusing one is a server that will not start with an error mentioning a process, a spawn or a missing executable, when you never configured a process at all. That is the client interpreting your url entry as a stdio command because no type was declared. It literally tries to run the URL as a program.
Add "type": "http" to the entry, or if you added the server from the Claude Code command line, re-add it with --transport http. Note that this is exactly the field Cursor and Windsurf do not want, which is why a config block copied between clients breaks in both directions: paste a VS Code entry into Cursor and the stray type is a problem, paste a Cursor entry into VS Code or a .mcp.json and the missing type is.
Tools that appear and then disappear
If the catalogue was there yesterday and is empty today, check the key before anything else. A revoked key produces a clean connection and no tools in some clients rather than a visible authentication error, because the client shows you the transport state and not the response body. Re-run the curl check and read the status code.
If the key is fine and the list is short rather than empty, the scopes changed. The catalogue is filtered by what the key can actually call, so removing a scope removes tools. That is the system working, and it is also why widening a key requires restarting the client: the tool list is fetched at connect time.