#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 same vibecrm_mcp_* token bound to a single project.
  • Protocol version: 2025-06-18.
  • Capabilities: tools only — resources and prompts return empty lists.
  • Rate limit: 100 requests per token per 60 seconds. Exceeding it returns HTTP 429 with Retry-After: 60.

The endpoint is the same for every customer:

endpoint — http
POST https://app.thevibecrm.com/api/mcp

What 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.1Option B — Manual Bearer Token
Best forClaude.ai web, Cursor, ChatGPT, any MCP client with OAuth supportClaude Desktop, n8n, custom scripts, programmatic SDKs
SetupPaste the MCP URL → approve consent screen → doneGenerate a token in VibeCRM → paste into client config
User seesA VibeCRM consent screen the first timeNothing — token is in a config file
Token rotationClient may re-issue via /api/oauth/tokenManual via Regenerate Token in the UI
Auth mechanismOAuth 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

  1. In Claude / Cursor / ChatGPT, add a new MCP server with URL https://app.thevibecrm.com/api/mcp.
  2. The client opens a popup to VibeCRM. Sign in if you aren't already.
  3. 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.

EndpointSpecWhat it returns
GET /.well-known/oauth-protected-resourceRFC 9728The MCP resource URL and which authorization server to use
GET /.well-known/oauth-authorization-serverRFC 8414authorization_endpoint, token_endpoint, registration_endpoint, supported grant types and PKCE methods
POST /api/oauth/registerRFC 7591Dynamic Client Registration — issues a fresh client_id to any client that asks
GET /authorizeOAuth 2.1User consent screen, gated by VibeCRM login
POST /api/oauth/tokenOAuth 2.1Exchanges an authorization code + PKCE verifier for a vibecrm_mcp_* token

#Server policy

  • PKCE is mandatory. Only S256 is accepted; plain text challenges are rejected.
  • Public clients only. token_endpoint_auth_method must be none. There are no client secrets — PKCE is the per-flow security.
  • redirect_uri must be HTTPS (or http://localhost for local dev).
  • Authorization codes expire in 5 minutes and are single-use.
  • Scope: only mcp is 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/mcp return WWW-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:

oauth flow — bash
# 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

  1. Open your project → Integrations
  2. Click the MCP card
  3. Click Enable MCP Integration

You need to do this once per project. It only requires the Premium plan.

#2. Issue a token

  1. Click + New Token
  2. Give it a descriptive name (e.g. Claude Desktop — laptop, Cursor — work, n8n agent)
  3. Click Create Token

A modal will appear with two values — save them immediately, the token is shown only once:

ValueDescription
MCP URLThe endpoint: https://app.thevibecrm.com/api/mcp
Bearer TokenA vibecrm_mcp_* token sent in the Authorization header. Shown only once.
Important

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):

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:

sdk — typescript
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 — bash
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:

header — http
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:

challenge — http
WWW-Authenticate: Bearer realm="vibecrm-mcp",
  resource_metadata="https://app.thevibecrm.com/.well-known/oauth-protected-resource"
FailureHTTPReason
Missing header401 + WWW-Authenticate: BearerNo Authorization header
Wrong token401Token does not exist
Deactivated token401Token toggled off in the UI
Disabled integration401MCP integration was disconnected
Rate-limited429 + Retry-After: 60More 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 stage
    • limit (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 pipeline
    • phone, companyName, companyUrl, source, recurrence (string, optional)
    • value (number, optional, default 0)
  • 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 column
    • name, 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:

lead — json
{
  "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

request — json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": { "name": "my-agent", "version": "1.0.0" }
  }
}

Response

response — json
{
  "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 — bash
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

request — json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "create_lead",
    "arguments": { "name": "Jane Doe", "email": "jane@example.com" }
  }
}

#Other methods

MethodBehavior
pingReturns {}
notifications/initializedAcknowledged, no response (notification)
notifications/cancelledAcknowledged, no response (notification)
resources/listReturns { resources: [] } — not supported
prompts/listReturns { prompts: [] } — not supported
GET /api/mcpReturns 405 (server is stateless, no SSE)
DELETE /api/mcpReturns 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 401 so 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, not vibecrm_lead_create) and short, action-oriented descriptions. Verbose tool names waste agent context.
  • Strict JSON Schemas with additionalProperties: false and required arrays, 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_lead returns isError: true inside result, not a JSON-RPC error. 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 Token keeps 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 the vibecrm MCP server. Before creating or updating leads, call list_pipeline_stages once to learn the valid stage IDs. When the user describes a lead, prefer create_lead. When they ask to "move", "promote", or "close" a lead, use update_lead with a new stageId. Never call delete_lead without 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.