Connect & authenticate

Sign in with OAuth 2.1 and connect from Claude, Cursor, or curl.

Connecting to the Busymate MCP server is a standard OAuth 2.1 flow — register, authorize in the browser, and you're in. Most MCP clients (Claude Desktop, Cursor) do the whole dance for you; you just paste the URL and sign in.

The endpoint

https://mcp.busymate.dev

mcp.busymate.dev is the canonical MCP host (the .dev branding alignment — the server's serverInfo.name is busymate-devtools). The previous host mcp.busymate.net keeps working forever — it dual-serves the exact same Edge Function (it never redirects), so existing client configs need no change. New configs should point at mcp.busymate.dev.

This single hostname serves both the JSON-RPC tool surface and the OAuth 2.1 authorization server. The server advertises its endpoints at the standard discovery URLs:

http
GET https://mcp.busymate.dev/.well-known/oauth-authorization-server
GET https://mcp.busymate.dev/.well-known/oauth-protected-resource

Those return the live authorization_endpoint, token_endpoint, and registration_endpoint (under /authorize, /token, /register).

OAuth is host-agnostic. The discovery metadata is derived from the host you connect to: a client that connects to mcp.busymate.dev gets a .dev issuer and .dev endpoints; a client on mcp.busymate.net gets .net. Per RFC 9207, the issuer a client discovers always matches the iss it gets back in the authorization response, so both hosts run a clean, self-consistent OAuth flow.

The auth model in one paragraph

Auth is OAuth 2.1, one path. An external client registers itself with Dynamic Client Registration (DCR), then runs an authorization-code flow with PKCE (code_challenge_method=S256). You complete the consent in the browser by signing in with your normal Busymate (Supabase) login; the server hands back an access token whose jti is recorded in the api_tokens table with label='oauth'. That same token then authenticates every Busymate service — MCP, REST, Realtime, and Edge Functions. There is no pasted universal token and no separate API key to manage.

The OAuth 2.1 flow

Your clientClaude / Cursormcp.busymate.devauth serverYoubrowser sign-in1 · register (DCR)2 · /authorize + PKCE3 · consent4 · code5 · /token → access token
  1. Register — the client POSTs its metadata to /register and gets back a client_id.
  2. Authorize — it opens /authorize in your browser with a PKCE code_challenge.
  3. Consent — you sign in with your Busymate login and approve.
  4. Code — the server redirects back with an authorization code.
  5. Token — the client exchanges the code (+ PKCE code_verifier) at /token for an access token.

You don't run these by hand — a compliant MCP client does. The steps below are just configuration.

Claude Desktop

Add Busymate as a remote MCP server. In Claude Desktop, open Settings → Connectors → Add custom connector and enter:

  • Name: Busymate
  • URL: https://mcp.busymate.dev

Claude will open a browser window for the OAuth consent. Sign in with your Busymate login and approve — that's it.

Hosted clients connect directly. Claude web/desktop (and ChatGPT, and any hosted connector) register an https:// callback — e.g. Claude's https://claude.ai/api/mcp/auth_callback — rather than a local loopback listener. The Dynamic Client Registration endpoint accepts any https:// redirect (alongside loopback http://127.0.0.1/localhost for CLI clients), so the Add custom connector path above just works with no local hop. The security boundary is unchanged: PKCE (S256) is mandatory and the redirect_uri is exact-matched at both /authorize and /token.

If you prefer config-file form (or are on a build that reads claude_desktop_config.json), add an HTTP MCP server entry:

json
{
  "mcpServers": {
    "busymate": {
      "type": "http",
      "url": "https://mcp.busymate.dev"
    }
  }
}

Cursor

In Cursor, open Settings → MCP → Add new MCP server, or add it to your mcp.json:

json
{
  "mcpServers": {
    "busymate": {
      "url": "https://mcp.busymate.dev"
    }
  }
}

Cursor handles the OAuth handshake in your browser on first use.

Hermes Agent

Hermes Agent is an OAuth 2.1 MCP client, so it mounts the full tool surface with a single command — no API key, no backend change. Hermes auto-runs Dynamic Client Registration + PKCE against the same /.well-known discovery endpoints, then opens the browser for sign-in:

bash
hermes mcp add --url https://mcp.busymate.dev

One-time consent. Connecting is standard OAuth 2.1 — the command auto-runs DCR + PKCE, then opens a one-time browser consent where a logged-in Busymate user clicks Allow (a loopback redirect + a browser are required). A normal Hermes install just works. Headless on a VPS? There's no device-code grant yet, so do a one-time out-of-band hop — SSH-tunnel the loopback callback port and open the printed authorize URL in a browser elsewhere (or copy the ?code= back to the VPS).

Mount only the safe subset. Hermes supports per-server tool include / exclude filtering, so you can scope a connection to least privilege — mount only the read-only tools (e.g. summarize_device_traffic, inspect_requests, get_status) and exclude the power executors (farm_run_shell, browser_cdp, *_install_app, …):

bash
# read-only: mount just the safe tools
hermes mcp add --url https://mcp.busymate.dev \
  --include summarize_device_traffic,inspect_requests,get_status
 
# or mount everything but the power executors
hermes mcp add --url https://mcp.busymate.dev \
  --exclude 'farm_run_shell,browser_cdp,*_install_app'

The client-side filter is convenience, not the security boundary: every destructive tool is also server-side confirm-gated and RBAC-scoped to your role regardless of what a client mounts.

curl (raw JSON-RPC)

Once you hold an access token (any OAuth-issued token works — the in-app consoles use the same type), every tool call is a JSON-RPC 2.0 POST. List the tools:

bash
curl -s https://mcp.busymate.dev \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Call one — here, list paired devices:

bash
curl -s https://mcp.busymate.dev \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": { "name": "list_devices", "arguments": {} }
  }'

Calling a tool without a valid bearer token returns 401 with a WWW-Authenticate: Bearer resource_metadata="…" header pointing at the discovery document — that's the cue for an MCP client to start the OAuth flow. (The resource_metadata URL is host-derived, so it points back at whichever host you called.)

Already on mcp.busymate.net? Nothing to do — mcp.busymate.net dual-serves the same Edge Function forever, so every command above works verbatim with mcp.busymate.net in place of mcp.busymate.dev. New setups should prefer the canonical mcp.busymate.dev.

If the authorization endpoints are briefly unavailable

/authorize, /token, and /register all read from the database before they can decide anything. If that dependency is briefly unreachable, they answer promptly (within a few seconds — never a long hang) with the standard retryable error rather than a generic failure:

http
HTTP/1.1 503 Service Unavailable
Retry-After: 10
 
{ "error": "temporarily_unavailable",
  "error_description": "client directory is temporarily unavailable; retry shortly" }

Treat temporarily_unavailable as retry after the advertised Retry-After (RFC 6749 §4.1.2.1 / §5.2) — it means the backend is briefly down, not that your request is wrong. Distinguish it from the two errors that are not retryable:

ResponseMeaningRetry?
503 temporarily_unavailable (+ Retry-After)a dependency is briefly unreachableyes, after Retry-After
400 invalid_clientthe client_id isn't registered — re-run DCRno
500 server_errora genuine server-side faultno — retrying won't help

A registered client is never expired or evicted by this behaviour: once DCR returns a client_id, /authorize keeps recognising it indefinitely.

In-dashboard consoles (first-party path)

The dashboard's built-in tool consoles don't make you run OAuth at all. When you're already signed in, they exchange your live session for an identical OAuth-type token at:

http
POST https://mcp.busymate.dev/first-party-token

So the in-app explorer and an external Claude client end up holding the same kind of token and hitting the same tools — full parity, one auth model.

After a server update: reconnect, not re-auth

The Busymate MCP server redeploys often — new tools land, tool schemas evolve, widget bundles rotate. A client that connected before a deploy can be holding a stale tool list: a tool looks missing, a description is out of date, or a widget briefly fails to render.

The fix is always a reconnect, never a re-auth:

  • Do: refresh the server connection in your client — Claude Desktop/web: open the connector and refresh its tools list (or toggle the connector off/on); Cursor: reload the MCP server entry; CLI clients: restart the session.
  • Don't: log out, delete the connector, or re-run the OAuth flow. Your access token survives deploys — auth state and server code are independent, so re-consenting buys you nothing and costs you a browser round-trip.

Most modern clients don't even need the manual step: the server advertises tools.listChanged + resources.listChanged and genuinely emits notifications/tools/list_changed / notifications/resources/list_changed on every deploy, so a client holding the listen stream re-discovers the new surface on its own. Stale widget reads are also served gracefully (an old widget hash returns the current bundle), so a redeploy is non-breaking either way.

Which build am I on? The initialize result's serverInfo.version carries the deployed build (e.g. 1.0.476 — it advances on every deploy). If you suspect staleness, compare it before and after a reconnect.

Next