Connect an Agent
OAuth, or a token you issue yourself

Connect an agent.

There is no SDK to install and no client library to keep in sync. Point any MCP client at the endpoint below: it authorises over OAuth 2.1, then discovers the whole CRM by itself — the six tables, every field, and every operation.

MCP endpoint https://headlesscrm-production.up.railway.app/mcp
Registration

Two ways in, both ending in a working agent

01

Paste the URL into your client

Most MCP clients — the Claude connector UI among them — handle authorisation themselves. Give them the endpoint and they will discover this server's OAuth metadata, register, open a browser for you to approve, and keep their own access and refresh tokens.

Add this serverhttps://headlesscrm-production.up.railway.app/mcp

You will be asked to sign in and approve the connection. Approved agents are listed on your account page, where you can disconnect any of them.

Seeing “missing refresh token”? That means the client tried the OAuth flow before it was available. Remove the connection in your client and add it again.
02

Or issue a token by hand

For scripts and clients that take a static header, issue a token yourself. One per agent, so you can revoke a single one without disturbing the others. It is shown once and only its hash is stored.

Your agent then sends it on every request:

Authorization: Bearer crmk_…
No account yet? The first person to open /setup creates it, and that page closes itself immediately afterwards. Until an account exists, the CRM refuses to serve data at all.
03

Register the server with your client

Every MCP client takes the same two things: the endpoint and the auth header.

claude mcp add --transport http crm https://headlesscrm-production.up.railway.app/mcp \
  --header "Authorization: Bearer crmk_…"
04

Check it answers

The health route needs no token and returns no CRM data — it is the quickest way to confirm the server and its database are up.

curl https://headlesscrm-production.up.railway.app/health
{
  "status": "ok",
  "database": { "engine": "postgres", "reachable": true },
  "tools": ["discover", "execute"],
  "operations": 66,
  "auth": { "accounts": 1, "setup_required": false }
}
Check auth.setup_required in that response. If it is true, nobody has created an account yet and the CRM is refusing to serve data — open /setup first.
First calls

Your agent needs no instructions from you

It calls discover to learn the schema and the signatures, then execute to do the work. Both tools describe themselves, so a client that has never seen this CRM gets it right first time.

// what is here?
discover({ scope: "overview" })

// what can I do with a contact?
discover({ scope: "operations", category: "contact" })

// exactly how do I call this one?
discover({ scope: "operation", operation: "contact.log" })
// everything about one customer
execute({
  operation: "contact.profile",
  params: { email: "alicia@northwind.com" }
})

// record what just happened
execute({
  operation: "contact.log",
  params: { contact_id: "con_8k2", type: "call",
             subject: "Discovery call" }
})
contact.profileThe customer, their company, who is dealing with them, and their history — one call
contact.logRecord a call, email, meeting, note or follow-up
contact.historyPage back through everything, newest first, by cursor
contact.findLook someone up by name, email, phone or company
contact.dueWhat you owe people, and what is overdue
company.profileA company, everyone you know there, and the whole account history

Every operation, with its full signature and a runnable example, is in the API reference.

Good to know

Before you point production at it

Access

  • OAuth access tokens last 8 hours and refresh silently; refresh tokens rotate on every use.
  • One token per agent; revoke any of them from your account and it stops working immediately.
  • Tokens can be given an expiry date when you issue them.
  • Only the token's hash is stored, so a database dump yields no working credential.
  • Every write is attributed to the token's owner — a caller cannot claim to be someone else.

Sessions

  • The server issues an mcp-session-id on initialize; send it back on every later request.
  • Sessions are per-connection and in memory — a redeploy ends them, and clients re-initialise.

Safe writes

  • dry_run executes in a transaction that is rolled back — nothing persists.
  • idempotency_key makes a retried call return its first result instead of acting twice.
  • actor records who did it, and shows up in the contact's history.

Limits

  • 500 records per page and per bulk call.
  • History pages by cursor, so depth costs nothing.
  • Errors name the right field, the allowed values, or the closest operation.