On this page
- Before you start
- Connect from Claude (claude.ai or the desktop app)
- Connect from ChatGPT
- Connect from Claude Code, MCP Inspector or another client
- What you are signing in with, and what the client ends up holding
- Scopes: what a connection may do
- Roles: what each connection may do
- How the connection works underneath
- See what is connected, and disconnect
- Troubleshooting
- Related
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 need | Where it comes from |
|---|---|
| The gateway's public URL | mcp_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 published | pm2 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 set | Commander Settings ▸ General. The consent page refuses to render without one ("This instance has no app password set"). |
| The service token minted | Operator 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)
- Settings → Connectors → Add custom connector. Name it (for example Commander dev)
and paste <publicUrl> (for example https://commander-dev.myorg.ai/mcp).
- Claude fetches
/.well-known/oauth-protected-resourceand, from it, the authorization
server's metadata, then registers itself as an OAuth client. You see nothing yet.
- 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.
- Choose Allow. The window closes; Claude exchanges a one-time code for its tokens.
- In a chat, tell the model to call
_hellofirst. 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
- 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.
- A standard connector — and Deep Research — uses only two tools,
searchandfetch.
They are a façade over projects, sessions, transcripts, artifacts and the prompt library in the fixed shapes ChatGPT requires.
- 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
- You type: the Commander app password, on the gateway's consent page, once per client.
It is checked against the same hash the Commander login uses and is never stored by the gateway.
- The client receives: an access token (a signed JWT, valid 1 hour by default)
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.
- What Commander sees: the gateway calls Commander over the loopback with its own
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.
- Nothing to rotate by hand: access tokens expire; the client refreshes silently; refresh
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.
| Scope | Unlocks | Needs phase |
|---|---|---|
commander:read | every read-only command, the two commander:// resources, persona prompts, search and fetch | 0 |
commander:workboard | tasks, notes, prompt-library and persona edits; project update; artifact rescan; session rename | 1 |
commander:sessions.write | archive, restore, restart, kill a session (restart and kill need confirm: true; refused on prod) | 2 |
commander:admin | registry tunables and the non-secret settings map, access grants, plus all of the above | 1 (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:
- Discovery — the client reads
/.well-known/oauth-protected-resource(RFC 9728) and
/.well-known/oauth-authorization-server (RFC 8414).
- Registration —
POST /oauth/register(RFC 7591). Registration is rate-limited,
capped and restricted to https or loopback redirect URIs; each client gets its own client_id.
- Authorization — the browser opens
/oauth/authorizewith 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).
- Token — the client exchanges the code plus its PKCE verifier at
/oauth/tokenand
receives the access and refresh tokens.
- Calls — every
POST /mcpcarriesAuthorization: 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.
- 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
- Who is calling:
commander.mcp_auditholds subject, client, tool, command, outcome and
latency for every call. Commander's own log shows relayed requests as acting=<subject> while mcp_gateway_request_log is on.
- Remove one client: delete the connector in Claude or ChatGPT (the client revokes its
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).
- Remove everyone at once: revoke all refresh tokens and rotate the gateway signing key;
outstanding access tokens fail immediately. The runbook (§8 and §10) has the exact steps.
- Turn the whole surface off: stop the gateway process. Commander keeps running.
Troubleshooting
| You see | It means | Do |
|---|---|---|
| The consent page says there is no app password | Commander has none set | Set one in Settings ▸ General, then reconnect. |
The client reports 401 and asks to reconnect | Expired or invalid token | Reconnect; check the client's clock (60 s tolerance). |
invalid_target during authorization | The client used a different form of the URL | Use exactly mcp_gateway_public_url — same scheme, no trailing slash. |
404 mcp_gateway_disabled at the URL | Commander is not publishing the gateway | Turn on mcp_gateway_enabled (Settings ▸ Advanced ▸ MCP gateway). |
502 mcp_gateway_unreachable at the URL | The gateway process is not running | pm2 start … --only commander-mcp (dev: commander-mcp-dev). |
invalid_grant on the token step | Code expired (5 min), reused, or PKCE mismatch | Reconnect; a reused code also revokes that client's refresh tokens on purpose. |
invalid_redirect_uri on registration | Non-https, non-loopback redirect | Only https or http://localhost / 127.0.0.1 are accepted. |
| A tool is missing from the list | Its 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 scope | Token lacks the command's scope | Reconnect and grant the scope on the consent page. |
-32002 Rate limited | More than mcp_gateway_rate_limit_per_min calls | Slow the client or raise the tunable. |
-32003 Commander: … | Commander refused or timed out upstream | 401 means the service token is dead — re-mint it; 502 means Commander is down. |
Related
- Agent skill: commander-mcp — the tool and command reference the models read.
- Administering Commander — app password, authentication modes, the Password Vault.
- Reference — settings families including
mcp_gateway_*.
You've seen the proof
Ready for a number? Scope your deployment and we'll price it against your own economics.
Get your quote →