Claude Code · MCP Server
Claude Code MCP Server: build messaging and email into your app
One command adds the remote Unipile MCP server to Claude Code. From there the agent reads the Unipile API specification, writes the LinkedIn, WhatsApp, email or calendar integration into your project, and tests it on a Development application. Nothing to install or run locally. Part of the Unipile MCP server.
--transport http
--scope user
.mcp.json for the team
CLI and VS Code
Start with a Development application. 7-day free trial, no credit card.
Claude Code · crm-app
Unipile MCP connected
Add LinkedIn account connection to my CRM. Users link their own account from the Settings page.
Read endpointPOST /v2/auth/linkschema loaded
Added POST /api/accounts/connect (creates the hosted auth link server-side) and the Settings button that opens it. The callback stores the account ID on the user.
Now sync each user's LinkedIn inbox into the contact timeline.
Run requestGET /v2/{account_id}/chats200 OK
Describe the next feature…
The job
What you are trying to do
Add a LinkedIn, WhatsApp, email or calendar connection to your product. That means reading an API reference, picking the right endpoints, wiring Hosted Auth and its callbacks, then keeping the right IDs from search to message. With the Unipile MCP server in Claude Code, the agent does that reading for you and writes the code in your stack, from the terminal or the IDE extension.
Connect the Unipile MCP server to Claude CodeUnipile MCP connected
Select the channels you want to connect
developer.unipile.com/mcpConnect all channels9 channels
Instagram/v2/{account_id}/chats↑↓navigate spaceselect ↵connectone URL, one header
Without it
Tabs, guesswork, glue code
Claude Code guesses endpoint names and payloads from training data, and gets the IDs wrong.
You paste schemas from the reference into the chat, one endpoint at a time.
The first real call happens in production, after the code review.
With the Unipile MCP server
The result in your application
A connect route and a Settings button: each user links their own account through Hosted Auth.
A webhook receiver and an inbox that shows messages and emails as they arrive.
Every request already run once on your Development application before you review the diff.
claude mcp add, three scopes
Add the Unipile MCP server to Claude Code
The server is remote: one URL over streamable HTTP and one header. No npx, no local process. One command registers it; the scope you pick decides where it loads and whether your team gets it too. Command and JSON verified against the official Claude Code documentation.
Claude Code installed (CLI or IDE extension), signed in, run from your project folder.
A Development application in the Unipile dashboard, with a Scope and a scoped Account API key.
At least one test account connected to that Scope through Hosted Auth, so the agent can run real requests.
User scope--scope user · every project, private to you
Project scope.mcp.json at the repository root, shared
Local scope (default)this project only, private, in ~/.claude.json
?
Which scope?User when you build several Unipile integrations from one machine. Project when the whole team should get the server from the repository, with each developer's own key in an environment variable. Local for a one-off try. When a name exists in several scopes, local wins over project, which wins over user.
# User scope: every project on this machine, private to you
claude mcp add --transport http --scope user \
unipile "https://developer.unipile.com/mcp?branch=v2.0" \
--header "X-API-KEY: your-scoped-api-key"
# Claude Code prints "Added …", then: claude mcp list
// Project scope: committed at the repository root, shared with the team
{
"mcpServers": {
"unipile": {
"type": "http",
"url": "https://developer.unipile.com/mcp?branch=v2.0",
"headers": {
"X-API-KEY": "${UNIPILE_API_KEY}"
}
}
}
}
// "type": "http" is required; each developer exports UNIPILE_API_KEY
# Local scope (default): this project only, private, stored in ~/.claude.json
claude mcp add --transport http \
unipile "https://developer.unipile.com/mcp?branch=v2.0" \
--header "X-API-KEY: your-scoped-api-key"
Verified on Claude Code 2.1: "Added …" then ✔ Connected in claude mcp list. Keep the URL in quotes (zsh treats the ? as a pattern) and put --header after the URL, it takes several values.
claude mcp addRegisters a server in the scope you choose and prints "Added …" once written.--transport httpThe Unipile server is a remote streamable HTTP server. No command, no npx, no local process.--scope userEvery project on this machine, private to you. Omit it for local (this project only), or use --scope project to write .mcp.json.unipileThe name you will see in claude mcp list, claude mcp get and /mcp."https://developer.unipile.com/mcp?branch=v2.0"The single server URL, in quotes. The ?branch=v2.0 parameter selects the API v2.--header "X-API-KEY: …"Your scoped Account API key. Placed last because the flag accepts several headers.Verify
Verify the connection
Three checks, from the terminal, inside a session, then in a chat. None of them touches a connected account.
1From the terminalThe list shows a health status next to each server: ✔ Connected is what you want; ✘ Failed to connect points at the URL, ! Needs authentication at the header, ⏸ Pending approval at a project server not yet approved.claude mcp listclaude mcp get unipile
2Inside a sessionType the slash command in Claude Code to see the server status and, for a project-scoped .mcp.json, approve it the first time you open the folder./mcp
3In a chat, without touching an accountAsk something that only reads the API specification. If the agent answers with real routes and parameters, the server is wired.Using the Unipile MCP, list the endpoints for LinkedIn people search and their required parameters.
Prompts, not glue code
Give your agent the prompt
Three integration jobs, each with the exact prompt to paste in Claude Code, the Unipile endpoints the agent reads and runs, and what lands in your project. Paths are complete, on the API base URL
https://api.unipile.com, with your scoped key in the X-API-KEY header.Add WhatsApp messaging to our support console: sync each agent's conversations and let them reply from the ticket.
Search endpoints"chats messages send"3 matches
Run requestGET /v2/{account_id}/chats9 chats
Created
whatsapp/chat-sync.ts (chats and messages upserted on the ticket, cursor pagination) and POST /tickets/:id/reply that calls the send route on the chat's own account. Ran the sync on the Development app: 9 chats, 41 messages.A WhatsApp inbox inside your product, from one prompt
Claude Code reads the chat and message contracts through the server, writes the sync job and the reply endpoint in your stack, and runs the first requests on your Development application. LinkedIn, Instagram and Telegram use the same chat routes, so the second channel is a smaller prompt than the first.
Endpoints the agent uses
GET/v2/{account_id}/chatsGET/v2/{account_id}/chats/{chat_id}/messagesPOST/v2/{account_id}/chats/{chat_id}/messages/send
Common mistake: Mixing IDs. A message is always sent on the account that owns the chat; keep
WhatsApp API
account_id and chat_id together from the list call to the send call.Let users send emails from the contact page through their own Gmail or Outlook mailbox, and thread the replies on the contact.
Read endpointPOST /v2/{account_id}/emails/sendschema loaded
Run requestGET /v2/{account_id}/emails200 OK
Added
POST /contacts/:id/email that calls the send route on the user's mailbox account, the reply-in-thread option using the thread ID, and the inbound sync that attaches replies to the contact. Sent a test email from the Development app mailbox and saw the reply threaded.Email from the user's own mailbox, threaded in your CRM
Gmail, Outlook and IMAP share one email schema. The agent reads the send and list contracts, wires the send action on the mailbox the user connected through Hosted Auth, and keeps the thread ID so replies land on the right contact.
Endpoints the agent uses
POST/v2/{account_id}/emails/sendGET/v2/{account_id}/emailsGET/v2/{account_id}/threads/{thread_id}
Common mistake: Sending from a shared technical mailbox. Each email goes out from the account of the user who connected it, so the reply reaches their inbox.
Email API
Each workspace in my SaaS has several users with their own LinkedIn and email accounts. Isolate them: one Scope and one scoped key per workspace.
Search endpoints"scopes api-keys"4 matches
Run requestPOST /v2/scopes/201 · scope
On workspace creation the backend now creates a Scope, a scoped Account API key stored encrypted on the workspace, and every account a member connects is assigned to that Scope. All account calls use the workspace key. Tested with two workspaces on the Development app.
Many users, many accounts, one boundary per tenant
Scopes are the access boundary of the Unipile API: a scoped key only sees the accounts assigned to its Scope. The agent turns that into your tenant model, so the multi-account logic lives in the API rather than in your code.
Endpoints the agent uses
POST/v2/scopes/POST/v2/api-keys/GET/v2/accounts/
Common mistake: Using one global Account key for every tenant. The global key stays on your backend for administration; each tenant gets its own scoped key.
Accounts, Scopes and keys
Development to production
Test on a Development application first
The Unipile dashboard separates a Development application from a Production one. Give Claude Code a scoped key from the Development application, with one or two test accounts connected through Hosted Auth. The agent runs real requests on those accounts, on behalf of the authenticated user who linked them, within each provider's limits, and nothing touches your users' accounts until you ship.
Validate the connect flow end to end: auth link created server-side, account ID stored on the user.
Validate one read and one write per feature: list chats, send a message on the test account.
Validate a webhook delivery and a reconnect or checkpoint state before switching the key to Production.
crm-app · DevelopmentUsed by Claude Code
Scopedev-tests · 2 accounts
Key
scoped Account API keyAccountsLinkedIn test account, Gmail test mailbox
Webhooks1 endpoint · message events
crm-app · ProductionUntouched
Scopeone per workspace
Key
scoped keys, in your backend onlyAccountsyour users' own accounts, via Hosted Auth
Troubleshooting
Common errors and what they mean
The statuses and warnings Claude Code shows when an MCP entry is not right, as printed by claude mcp list and /mcp, and the fix for each.
✘ Failed to connect
claude mcp list shows the server, but the health check fails.
FixThe url must be exactly https://developer.unipile.com/mcp?branch=v2.0 with --transport http. Claude Code retries a transient error three times, but never a not-found or an authentication error: fix the URL or the header, then run claude mcp get unipile.
⏸ Pending approval
A project-scoped server from .mcp.json is listed but not connected.
FixRun claude in the folder, accept the workspace trust dialog, then approve the server from /mcp. A cloned repository cannot approve its own servers from committed settings.
401 on requests
The server is connected, but running a request fails.
FixThe key is missing from the header, the header name is not X-API-KEY, or you used a Service or global Account key instead of a scoped Account API key from your Development application.
Missing variable warning
claude mcp list reports that ${UNIPILE_API_KEY} is not set.
FixExport the variable in the shell that starts Claude Code, or add a default with ${UNIPILE_API_KEY:-} syntax. Unset variables in url or headers can read as empty, which ends in a 401.
Hidden whitespace in headers.X-API-KEY
A token pasted with a trailing newline.
FixClaude Code names the field in claude mcp list and /mcp without echoing the value. Re-add the server with the key trimmed; Claude Code uses values exactly as written.
Same name in more than one scope
unipile exists in user and project scope with different settings.
FixClaude Code connects once, using the highest-precedence definition (local, then project, then user) and warns about the conflict. Remove the duplicate with claude mcp remove unipile --scope user or keep one scope per machine.
MCP endpoint not found at
A 404 on the URL: the path is wrong.
FixThe full URL is https://developer.unipile.com/mcp?branch=v2.0, branch parameter included. Check it with curl -I from your machine, then claude mcp get unipile.
/mcp shows No MCP servers configured
The file you edited is not one Claude Code reads.
FixClaude Code reads ~/.claude.json and .mcp.json at the project root only, never ~/.claude/mcp.json, ~/.claude/.mcp.json or ~/.claude/config/mcp.json. Also restart the session: .mcp.json is read at startup.
Shell rejects the URL, or branch is missing
zsh treats the ? in ?branch=v2.0 as a pattern.
FixAlways quote the URL in claude mcp add. Unquoted, zsh answers "no matches found" and bash can drop the parameter, which connects you to the wrong API version.
Slow start or timeout
The server takes longer than the 30 s default at startup.
FixRaise the limit for that session: MCP_TIMEOUT=60000 claude. If you refused a project server at the approval prompt, claude mcp reset-project-choices brings the prompt back.
Claude Code MCP server FAQ
The questions people actually type: scopes, where the configuration lives, keys out of the repository, custom headers, Failed to connect, quoting the URL, .mcp.json changes, and API keys.
Local is the default: stored in
~/.claude.json under the current project, private and limited to that project. Project writes .mcp.json at the repository root and is shared through version control. User writes ~/.claude.json under the root mcpServers key and applies to all your projects. Precedence is local, then project, then user. For Unipile: --scope user for your personal development key, --scope project when the whole team works on the same integration.In
~/.claude.json (on Windows %USERPROFILE%\.claude.json) for local and user scopes, and in .mcp.json at the project root for project scope. Claude Code does not read ~/.claude/.mcp.json, ~/.claude/config/mcp.json, ~/.claude/mcp.json or %APPDATA%\Claude\mcp.json. claude mcp get unipile tells you which scope an entry lives in.Claude Code expands
${VAR} and ${VAR:-default} in command, args, env, url and headers. Write "X-API-KEY": "${UNIPILE_API_KEY}" in .mcp.json, commit the file, and each developer provides their own scoped key through their environment. If the variable is unset with no default, the configuration still loads and claude mcp list shows a warning.Yes:
--header "X-API-KEY: your-scoped-api-key", repeatable for several headers, short form -H. Put it after the URL, because the flag accepts several values. The Unipile server is a remote HTTP server: nothing to install locally, no npx, no Node to manage.Run
claude mcp get unipile for the detail (HTTP status and error text), check the leading or trailing whitespace warnings claude mcp list prints after a pasted key, and confirm the URL answers from your machine with curl -I. A 404 prints MCP endpoint not found at <origin>: the path is wrong, the full URL is https://developer.unipile.com/mcp?branch=v2.0, branch parameter included.The URL contains a
?, which zsh reads as a pattern character. Always quote the URL in claude mcp add. Unquoted, zsh answers "no matches found" and bash can drop the branch parameter, which connects you to the wrong version of the server.Claude Code reads
.mcp.json at session start: quit and relaunch. A malformed entry is ignored silently, and claude mcp list prints the parsing warning with the faulty field. If you declined the server at the project approval prompt, run claude mcp reset-project-choices.The server answers without a key when the agent only reads the API specification. To run real requests, create a Scope in your Development application, assign only the accounts concerned, and generate a scoped Account API key for that Scope. Never give an MCP client a Service key or a global Account key.