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.
The consent prompt
Step 3 is the one screen you see: the client's /authorize request is stored server-side under a request id and you're sent to the dashboard's consent page, https://dash.busymate.dev/mcp/consent?rid=<request-id>. If you're not signed in yet you go through the normal sign-in first and land back on the same consent request.
The page reads Allow this application to access Busymate, names the scope (full dashboard access), and shows exactly which account is about to grant it — an identity row of the form Signed in as you@example.com with your product role as a badge (ADMIN, viewer, a custom role). A grant is bound to whoever presses Allow, so the badge is there to make a wrong-account grant obvious before it happens.
- Allow completes step 4: the server issues the authorization
codeto the client's registeredredirect_uri, bound to the signed-in account. - Deny returns the standard
access_deniederror to the client. - Switch account is for when the badge shows the wrong account (you're signed into a personal account but want to connect the client to your team admin, say). It signs the current dashboard session out and brings you back to the same consent request — the same
client_id,redirect_uri,state, PKCE challenge, scope, and resource — so you can sign in as the other account and press Allow without restarting the client. Nothing about the authorization request travels through the URL except its request id, and the return path is validated to the consent route only, so it can't be turned into an open redirect.
The prompt is the same for both MCP products — the DevTools server at mcp.busymate.dev and the Busymate AI server at busymate.ai/mcp share one sign-in — so Switch account behaves identically on either.
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 454 tools and their permission gating.
- Prompts & examples — what to actually say once connected.