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.devmcp.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:
GET https://mcp.busymate.dev/.well-known/oauth-authorization-server
GET https://mcp.busymate.dev/.well-known/oauth-protected-resourceThose 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.devgets a.devissuerand.devendpoints; a client onmcp.busymate.netgets.net. Per RFC 9207, the issuer a client discovers always matches theissit 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
- Register — the client POSTs its metadata to
/registerand gets back aclient_id. - Authorize — it opens
/authorizein your browser with a PKCEcode_challenge. - Consent — you sign in with your Busymate login and approve.
- Code — the server redirects back with an authorization
code. - Token — the client exchanges the code (+ PKCE
code_verifier) at/tokenfor 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'shttps://claude.ai/api/mcp/auth_callback— rather than a local loopback listener. The Dynamic Client Registration endpoint accepts anyhttps://redirect (alongside loopbackhttp://127.0.0.1/localhostfor 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 theredirect_uriis exact-matched at both/authorizeand/token.
If you prefer config-file form (or are on a build that reads claude_desktop_config.json), add an HTTP MCP server entry:
{
"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:
{
"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:
hermes mcp add --url https://mcp.busymate.devOne-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, …):
# 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:
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:
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
401with aWWW-Authenticate: Bearer resource_metadata="…"header pointing at the discovery document — that's the cue for an MCP client to start the OAuth flow. (Theresource_metadataURL is host-derived, so it points back at whichever host you called.)
Already on
mcp.busymate.net? Nothing to do —mcp.busymate.netdual-serves the same Edge Function forever, so every command above works verbatim withmcp.busymate.netin place ofmcp.busymate.dev. New setups should prefer the canonicalmcp.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/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:
| Response | Meaning | Retry? |
|---|---|---|
503 temporarily_unavailable (+ Retry-After) | a dependency is briefly unreachable | yes, after Retry-After |
400 invalid_client | the client_id isn't registered — re-run DCR | no |
500 server_error | a genuine server-side fault | no — 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:
POST https://mcp.busymate.dev/first-party-tokenSo 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
initializeresult'sserverInfo.versioncarries 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
- Tool reference — the full list of 409 tools and their permission gating.
- Prompts & examples — what to actually say once connected.