On this page

Connect a chat client to Commander over MCP

Commander exposes itself to chat clients through a separate process, the **commander-mcp gateway**. Claude (web or desktop), ChatGPT and any MCP-conforming client connect to it the same way: you add one URL, the client discovers the gateway's OAuth server, registers itself, sends you to a sign-in page, and receives tokens that belong to that client alone. There is no API key to copy and no token to paste — the only secret you ever type is the Commander app password, on the gateway's own consent page, once per client.

This page is the operator's view: what to click, what to type, what happens underneath, and how to take a connection away again.

Before you start

You needWhere it comes from
The gateway's public URLmcp_gateway_public_url in Settings ▸ Advanced ▸ MCP gateway (env MCP_GATEWAY_PUBLIC_URL). By default it is Commander's own host plus /mcp — for example https://commander-dev.myorg.ai/mcp — and it is exactly the URL you paste into a client.
The gateway running and publishedpm2 status commander-mcp (commander-mcp-dev on the dev instance), and mcp_gateway_enabled on in Settings ▸ Advanced ▸ MCP gateway — that is what makes Commander route /mcp to it. GET <publicUrl>/.well-known/oauth-protected-resource answers JSON naming the resource.
An app password setCommander Settings ▸ General. The consent page refuses to render without one ("This instance has no app password set").
The service token mintedOperator step from the runbook (scripts/mcp-gateway-setup.js); it lands in the instance's secrets.env and the Password Vault. You never hand this to a client.

The MCP endpoint is <publicUrl> — there is nothing to append. Everything below uses that one URL.

Connect from Claude (claude.ai or the desktop app)

  1. Settings → Connectors → Add custom connector. Name it (for example Commander dev)

and paste <publicUrl> (for example https://commander-dev.myorg.ai/mcp).

  1. Claude fetches /.well-known/oauth-protected-resource and, from it, the authorization

server's metadata, then registers itself as an OAuth client. You see nothing yet.

  1. A browser window opens on the gateway's consent page. It shows the client's name, the

permissions it is asking for (the scopes — see below), and one field: the **Commander app password**. This is the same password the Commander login screen takes.

  1. Choose Allow. The window closes; Claude exchanges a one-time code for its tokens.
  2. In a chat, tell the model to call _hello first. It answers with the instance

identity, the tools your token unlocks, and the tools that exist but are gated (and why).

Tools appear under their umbrella names (projects, sessions, transcripts, artifacts, workboard, health, …), each with a command. Anything destructive carries a hint, so Claude asks you before re-issuing the call with confirm: true.

Connect from ChatGPT

  1. Settings → Connectors → Create. MCP server URL <publicUrl>, authentication

OAuth. The discovery, registration and consent steps are identical to Claude's; you sign in with the app password on the same page.

  1. A standard connector — and Deep Research — uses only two tools, search and fetch.

They are a façade over projects, sessions, transcripts, artifacts and the prompt library in the fixed shapes ChatGPT requires.

  1. Developer Mode exposes the full umbrella set under the same scopes and phases.

Connect from Claude Code, MCP Inspector or another client

Point the client at <publicUrl>. A conforming client runs OAuth discovery on its own and opens the consent page in your browser; the flow is the same as above. Redirect URIs must be https or a loopback address (http://localhost, http://127.0.0.1), which is what desktop clients use.

What you are signing in with, and what the client ends up holding

It is checked against the same hash the Commander login uses and is never stored by the gateway.

and a refresh token (valid 30 days by default, replaced on every use). Both are bound to that client's registration and to the gateway's URL, so a token copied to another client or another host is refused.

service token and adds the connected user as x-acting-subject. That service token is confined server-side: it cannot reach the Password Vault, Integrations, accounts, license, the login settings, uploads, promote or rollback — whatever a client asks for.

tokens rotate. If a client ever re-uses an old authorization code, the gateway revokes that client's whole token lineage on purpose — you simply reconnect.

Later, the gateway can delegate sign-in to DataShield Auth (MCP_GATEWAY_OAUTH_MODE=auth); the consent step then becomes your Auth sign-in and the app password is no longer asked for. Everything else on this page stays the same.

Scopes: what a connection may do

The consent page lists the scopes the client requested. Grant only what the use calls for.

ScopeUnlocksNeeds phase
commander:readevery read-only command, the two commander:// resources, persona prompts, search and fetch0
commander:workboardtasks, notes, prompt-library and persona edits; project update; artifact rescan; session rename1
commander:sessions.writearchive, restore, restart, kill a session (restart and kill need confirm: true; refused on prod)2
commander:adminregistry tunables and the non-secret settings map, access grants, plus all of the above1 (grants: 0)

A client that asks for no scope gets commander:read. A phase is what the operator has enabled on the instance (mcp_gateway_enable_phases, default 0 — read-only). A command needs both its scope and its phase; a disabled command is absent from the tool list and refused by name if called. Production stays at phase 0 by policy.

Roles: what each connection may do

Scopes are what you ticked; a role is what the operator allows that connection. Every connector is its own account: with no grant it is a visitor (read-only) whatever scopes it holds. The operator raises it — editor (workboard edits, git commits), operator (session control, terminal on dev), admin (everything, including grants) — either with the access tool from an admin connection or from the host with scripts/mcp-gateway-access.js. A change applies on your next call; you do not reconnect. The instance itself has a ceiling too (mcp_gateway_enable_phases): production is read-only for everyone by policy, so a role can never exceed what the install allows. _hello tells you your role and, for each unavailable command, which of the three limits applies.

How the connection works underneath

The sequence is the standard OAuth 2.1 authorization-code flow with PKCE (RFC 6749, RFC 7636), with the gateway acting as both resource server and authorization server:

  1. Discovery — the client reads /.well-known/oauth-protected-resource (RFC 9728) and

/.well-known/oauth-authorization-server (RFC 8414).

  1. RegistrationPOST /oauth/register (RFC 7591). Registration is rate-limited,

capped and restricted to https or loopback redirect URIs; each client gets its own client_id.

  1. Authorization — the browser opens /oauth/authorize with a PKCE challenge and the

gateway's URL as the resource (RFC 8707). The consent page verifies the app password and issues a one-time code (valid 5 minutes).

  1. Token — the client exchanges the code plus its PKCE verifier at /oauth/token and

receives the access and refresh tokens.

  1. Calls — every POST /mcp carries Authorization: Bearer <access token>. The gateway

verifies it locally (signature, issuer, audience, expiry), checks the command's scope and phase, validates parameters, relays to Commander, redacts and caps the result, and writes one audit row — for denials too.

  1. Refresh — before the access token expires the client presents its refresh token and

receives a new pair; the old refresh token is dead.

Restarting the gateway drops in-memory MCP sessions; clients re-initialise on their own. It never touches Commander's terminals.

Each Commander deployment publishes its own gateway at its own host — dev at https://commander-dev.myorg.ai/mcp, production at https://commander.myorg.ai/mcp — with separate clients, tokens, keys and audit. A client connected to one knows nothing of the other.

See what is connected, and disconnect

latency for every call. Commander's own log shows relayed requests as acting=<subject> while mcp_gateway_request_log is on.

refresh token at /oauth/revoke, RFC 7009), or, operator-side, mark that client's tokens revoked and delete its registration — its access tokens die at expiry (≤ 1 hour).

outstanding access tokens fail immediately. The runbook (§8 and §10) has the exact steps.

Troubleshooting

You seeIt meansDo
The consent page says there is no app passwordCommander has none setSet one in Settings ▸ General, then reconnect.
The client reports 401 and asks to reconnectExpired or invalid tokenReconnect; check the client's clock (60 s tolerance).
invalid_target during authorizationThe client used a different form of the URLUse exactly mcp_gateway_public_url — same scheme, no trailing slash.
404 mcp_gateway_disabled at the URLCommander is not publishing the gatewayTurn on mcp_gateway_enabled (Settings ▸ Advanced ▸ MCP gateway).
502 mcp_gateway_unreachable at the URLThe gateway process is not runningpm2 start … --only commander-mcp (dev: commander-mcp-dev).
invalid_grant on the token stepCode expired (5 min), reused, or PKCE mismatchReconnect; a reused code also revokes that client's refresh tokens on purpose.
invalid_redirect_uri on registrationNon-https, non-loopback redirectOnly https or http://localhost / 127.0.0.1 are accepted.
A tool is missing from the listIts phase is off or its scope was not granted_hello names the gated commands and the reason; the operator sets mcp_gateway_enable_phases.
-32001 Insufficient scopeToken lacks the command's scopeReconnect and grant the scope on the consent page.
-32002 Rate limitedMore than mcp_gateway_rate_limit_per_min callsSlow the client or raise the tunable.
-32003 Commander: …Commander refused or timed out upstream401 means the service token is dead — re-mint it; 502 means Commander is down.

You've seen the proof

Ready for a number? Scope your deployment and we'll price it against your own economics.

Get your quote →