#MCP Integration — LLM-Native Access
The VibeCRM MCP integration exposes your pipeline to AI agents through the Model Context Protocol. Once enabled, any MCP-compatible client — Claude Desktop, Cursor, Claude Code, ChatGPT (when MCP-enabled), or a custom agent — can list, create, update, and delete leads on your behalf.
It is the AI-agent equivalent of the Public API: same operations, same project scoping, but designed for tool-using LLMs instead of HTTP scripts.
#How it works
- →Transport: Streamable HTTP, stateless mode. Every request is a single JSON-RPC 2.0 message sent via
POST. No SSE stream, no session state to manage on the client side. - →Authentication: two options — full OAuth 2.1 with Dynamic Client Registration (Claude.ai web, Cursor, ChatGPT) or a manual
Authorization: Bearer <token>(Claude Desktop, n8n, custom scripts). Both yield the samevibecrm_mcp_*token bound to a single project. - →Protocol version:
2025-06-18. - →Capabilities:
toolsonly —resourcesandpromptsreturn empty lists. - →Rate limit: 100 requests per token per 60 seconds. Exceeding it returns HTTP
429withRetry-After: 60.
The endpoint is the same for every customer:
POST https://app.thevibecrm.com/api/mcpWhat changes per token is the project that the agent has access to.
#Two ways to connect
VibeCRM's MCP server supports both authentication styles. Pick the one your client speaks.
| Option A — OAuth 2.1 | Option B — Manual Bearer Token | |
|---|---|---|
| Best for | Claude.ai web, Cursor, ChatGPT, any MCP client with OAuth support | Claude Desktop, n8n, custom scripts, programmatic SDKs |
| Setup | Paste the MCP URL → approve consent screen → done | Generate a token in VibeCRM → paste into client config |
| User sees | A VibeCRM consent screen the first time | Nothing — token is in a config file |
| Token rotation | Client may re-issue via /api/oauth/token | Manual via Regenerate Token in the UI |
| Auth mechanism | OAuth 2.1 authorization code + PKCE (S256) | Static vibecrm_mcp_* bearer |
Both options share the same /api/mcp endpoint, the same tools, and the same per-project scoping. The only difference is how the client obtains the bearer token.
#Option A — Connect with OAuth
VibeCRM is a full OAuth 2.1 authorization server with Dynamic Client Registration, following the MCP 2025-06-18 Authorization spec. MCP clients that speak OAuth (Claude.ai, Cursor, ChatGPT) handle the whole handshake automatically — you just paste the MCP URL.
#The user flow
- In Claude / Cursor / ChatGPT, add a new MCP server with URL
https://app.thevibecrm.com/api/mcp. - The client opens a popup to VibeCRM. Sign in if you aren't already.
- Pick which project to share, click Authorize. Popup closes; the client is connected.
That's it. No token to copy. VibeCRM mints one for the client behind the scenes and binds it to the project you selected.
#Discovery endpoints
These are public, cached for 1 hour, and CORS-enabled. You only care about them if you're building a custom MCP client.
| Endpoint | Spec | What it returns |
|---|---|---|
GET /.well-known/oauth-protected-resource | RFC 9728 | The MCP resource URL and which authorization server to use |
GET /.well-known/oauth-authorization-server | RFC 8414 | authorization_endpoint, token_endpoint, registration_endpoint, supported grant types and PKCE methods |
POST /api/oauth/register | RFC 7591 | Dynamic Client Registration — issues a fresh client_id to any client that asks |
GET /authorize | OAuth 2.1 | User consent screen, gated by VibeCRM login |
POST /api/oauth/token | OAuth 2.1 | Exchanges an authorization code + PKCE verifier for a vibecrm_mcp_* token |
#Server policy
- →PKCE is mandatory. Only
S256is accepted; plain text challenges are rejected. - →Public clients only.
token_endpoint_auth_methodmust benone. There are no client secrets — PKCE is the per-flow security. - →
redirect_urimust be HTTPS (orhttp://localhostfor local dev). - →Authorization codes expire in 5 minutes and are single-use.
- →Scope: only
mcpis granted. There are no sub-scopes — a token can do everything the MCP server exposes within its bound project. - →Tokens are project-scoped. The project you pick on the consent screen is baked into the token. To grant access to another project, the user re-runs the flow.
- →401 challenges on
/api/mcpreturnWWW-Authenticate: Bearer realm="vibecrm-mcp", resource_metadata="…/.well-known/oauth-protected-resource"so clients can auto-discover the authorization server.
#Building a custom OAuth client
If your client doesn't auto-discover, here's the minimum dance:
# 1. Register
curl -X POST https://app.thevibecrm.com/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["https://your-app.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"client_name": "My MCP Client"
}'
# → { "client_id": "mcp_…", "redirect_uris": […], … }
# 2. Send user to /authorize with PKCE
# (build a verifier and challenge = base64url(sha256(verifier)))
open "https://app.thevibecrm.com/authorize\
?response_type=code\
&client_id=mcp_…\
&redirect_uri=https://your-app.com/oauth/callback\
&code_challenge=<challenge>\
&code_challenge_method=S256\
&state=<random>\
&scope=mcp"
# 3. After the user approves, your callback receives ?code=…&state=…
# Exchange it for a token:
curl -X POST https://app.thevibecrm.com/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "<code from callback>",
"redirect_uri": "https://your-app.com/oauth/callback",
"client_id": "mcp_…",
"code_verifier": "<verifier>"
}'
# → { "access_token": "vibecrm_mcp_…", "token_type": "Bearer", "scope": "mcp" }The resulting vibecrm_mcp_* token is identical to one issued from the UI — same shape, same project binding, same rate limit.
#A note on client_id
VibeCRM's authorization server is tolerant: if a client skips Dynamic Client Registration and arrives at /authorize with an unregistered client_id, we accept it and register implicitly. This is intentional — PKCE binds the authorization code to whoever started the flow, so the client_id is bookkeeping rather than a credential. Well-behaved clients that use /register get the same flow; sloppy clients still work.
#Option B — Manual bearer token
If your client doesn't speak OAuth (Claude Desktop, n8n, custom scripts), generate a long-lived token in the VibeCRM UI and paste it into the client's config.
#1. Enable the MCP integration
- Open your project → Integrations
- Click the MCP card
- Click Enable MCP Integration
You need to do this once per project. It only requires the Premium plan.
#2. Issue a token
- Click + New Token
- Give it a descriptive name (e.g.
Claude Desktop — laptop,Cursor — work,n8n agent) - Click Create Token
A modal will appear with two values — save them immediately, the token is shown only once:
| Value | Description |
|---|---|
| MCP URL | The endpoint: https://app.thevibecrm.com/api/mcp |
| Bearer Token | A vibecrm_mcp_* token sent in the Authorization header. Shown only once. |
If you lose a token, use Regenerate Token on the token card. The old token is invalidated immediately.
#3. Wire it into your agent
Claude Desktop / Claude Code / Cursor
Edit your MCP config file (Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"vibecrm": {
"url": "https://app.thevibecrm.com/api/mcp",
"headers": {
"Authorization": "Bearer vibecrm_mcp_<your-token>"
}
}
}
}Restart the client. The agent will now see the vibecrm server with the tools described below.
Programmatic / SDK
Any client that supports MCP Streamable HTTP works. Example using @modelcontextprotocol/sdk:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://app.thevibecrm.com/api/mcp"),
{
requestInit: {
headers: { Authorization: "Bearer vibecrm_mcp_<your-token>" },
},
}
);
const client = new Client({ name: "my-app", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);
const { tools } = await client.listTools();
const result = await client.callTool({
name: "create_lead",
arguments: { name: "Jane Doe", email: "jane@example.com" },
});Raw HTTP (debugging)
Every MCP message is just JSON-RPC over POST:
curl -s -X POST https://app.thevibecrm.com/api/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vibecrm_mcp_<your-token>" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'#Authentication
Regardless of how the token was obtained (OAuth flow or manual generation), every MCP request must include:
Authorization: Bearer vibecrm_mcp_<your-token>On a missing or invalid token the server replies with 401 and a WWW-Authenticate header pointing at the OAuth resource metadata document, so OAuth-aware clients can auto-start the flow described in Option A:
WWW-Authenticate: Bearer realm="vibecrm-mcp",
resource_metadata="https://app.thevibecrm.com/.well-known/oauth-protected-resource"| Failure | HTTP | Reason |
|---|---|---|
| Missing header | 401 + WWW-Authenticate: Bearer | No Authorization header |
| Wrong token | 401 | Token does not exist |
| Deactivated token | 401 | Token toggled off in the UI |
| Disabled integration | 401 | MCP integration was disconnected |
| Rate-limited | 429 + Retry-After: 60 | More than 100 req/min on this token |
A token is bound to exactly one project at creation. Rotating it (Regenerate Token) preserves the binding; revoking it (Delete) disconnects every agent using it instantly.
#Tools
The server advertises exactly six tools. They mirror the Public API one-for-one — anything the API can do, the MCP can do, scoped to the project bound to your token.
#list_pipeline_stages
List every kanban column in your project, sorted left-to-right. Use this first so the agent knows what stage IDs to pass to create_lead / update_lead.
- Input: none
- Returns:
{ stages: [{ id, name, color, order, isSold }] }
#list_leads
List leads in your project.
- Input:
stageId(string, optional) — filter by a single stagelimit(integer, optional, 1–500, default 100)
- Returns:
{ leads: [Lead, …] }
#get_lead
Fetch one lead by ID.
- Input:
{ leadId: string } - Returns:
{ lead: Lead } - Errors:
"Lead not found","Lead does not belong to this project"
#create_lead
Create a new lead.
- Input:
name(string, required)email(string, required)stageId(string, optional) — defaults to the first stage of the pipelinephone,companyName,companyUrl,source,recurrence(string, optional)value(number, optional, default0)
- Returns:
{ lead: Lead } - Defaults:
source→"MCP",recurrence→"One-time".
#update_lead
Patch one or more fields of a lead. Only fields you pass are touched. Pass an empty string to clear phone / companyName / companyUrl.
- Input:
leadId(string, required)stageId(string, optional) — move the lead to another columnname,email,phone,companyName,companyUrl,source,recurrence(string, optional)value(number, optional)
- Returns:
{ lead: Lead } - Errors:
"No updatable fields provided","stageId '…' not found in this project".
#delete_lead
Permanently delete a lead.
- Input:
{ leadId: string } - Returns:
{ ok: true, leadId }
#The Lead shape
All lead-returning tools serialize leads the same way:
{
"id": "clyyy5678",
"projectId": "clppp1111",
"stageId": "clxxx1234",
"position": 3,
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1 555 0100",
"companyName": "Acme Corp",
"companyUrl": "https://acme.com",
"source": "MCP",
"value": 5000,
"recurrence": "Monthly",
"createdAt": "2026-05-23T10:12:34.000Z",
"updatedAt": "2026-05-23T10:12:34.000Z"
}Every tool returns its payload twice on success — once as a JSON string in the content[0].text field (for clients that only read text) and once as structuredContent (for clients that prefer parsed JSON). Tool-level failures (e.g. "lead not found") set isError: true rather than producing a JSON-RPC error, so the agent can read the message and recover.
#JSON-RPC reference
The transport is plain JSON-RPC 2.0. Every method is supported as POST /api/mcp.
#initialize
The first call in any MCP session. Negotiates capabilities.
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "my-agent", "version": "1.0.0" }
}
}Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "vibecrm-mcp", "version": "1.0.0" },
"instructions": "VibeCRM MCP server. Use list_pipeline_stages first…"
}
}#tools/list
Returns the six tools described above with their JSON Schemas.
curl -s -X POST https://app.thevibecrm.com/api/mcp \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'#tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "create_lead",
"arguments": { "name": "Jane Doe", "email": "jane@example.com" }
}
}#Other methods
| Method | Behavior |
|---|---|
ping | Returns {} |
notifications/initialized | Acknowledged, no response (notification) |
notifications/cancelled | Acknowledged, no response (notification) |
resources/list | Returns { resources: [] } — not supported |
prompts/list | Returns { prompts: [] } — not supported |
GET /api/mcp | Returns 405 (server is stateless, no SSE) |
DELETE /api/mcp | Returns 204 (no session to terminate) |
#Best practices
The server is built around the MCP best practices that matter for this use case:
- →Bearer-token auth with WWW-Authenticate challenge on
401so MCP clients that support OAuth flows can detect the realm. - →Stateless transport — every request is independent. No session expiry to track, no reconnection logic.
- →Constant-set tool list with conservative names (
create_lead, notvibecrm_lead_create) and short, action-oriented descriptions. Verbose tool names waste agent context. - →Strict JSON Schemas with
additionalProperties: falseandrequiredarrays, so agents that follow schemas can't misshape requests. - →Structured + text dual output on every tool result, matching the MCP "rich content" recommendation.
- →Tool errors are not protocol errors. A failed
update_leadreturnsisError: trueinsideresult, not a JSON-RPCerror. This keeps the agent in a recoverable state. - →Per-token rate limiting prevents a runaway agent from saturating the database.
- →Token rotation without losing the binding —
Regenerate Tokenkeeps the same identity but invalidates the old secret. - →Token prefix vibecrm_mcp_ makes accidental commits trivial to spot in secret scanners.
#Guidance for agent prompts
If you're embedding the server in a custom agent, the following system-prompt fragment works well:
You have access to thevibecrmMCP server. Before creating or updating leads, calllist_pipeline_stagesonce to learn the valid stage IDs. When the user describes a lead, prefercreate_lead. When they ask to "move", "promote", or "close" a lead, useupdate_leadwith a newstageId. Never calldelete_leadwithout explicit confirmation from the user.
#Disabling MCP
- →Deactivate one token: click Deactivate on the token card. The agent immediately gets
401s. - →Delete one token: permanent, same effect.
- →Disable the entire integration: click Disconnect on the MCP card in Integrations. Every token stops working at once.
Disabling never deletes leads — it only revokes agent access.