Kuickr

Deploy via MCP

Conductor ships a built-in MCP (Model Context Protocol) server, so any MCP-capable agent — Claude, Cursor, your own scripts — can drive the whole fleet: register servers, create and configure apps, load env, provision databases, and deploy — the same operations the web UI performs, exposed as tools.

If you can do it on an app's page, you can do it from an agent.

Endpoint & auth

The server is mounted on your Conductor instance:

Method Path Purpose
GET /mcp/list List every tool + its input schema (discovery).
POST /mcp/call Call one tool: { "name": "...", "input": { ... } }.

Every request carries a bearer token, and there are two ways to get one:

  • Sign in through the browser (OAuth). The client discovers Conductor, registers itself, and sends you to approve it — nothing to copy. This is how you connect Codex, claude.ai, or Cursor. See Sign-in with OAuth.
  • Mint a token yourself. A per-user, org-scoped token from /mcp_tokens, exported as CONDUCTOR_MCP_TOKEN. Right for CI, scripts, and curl.
Authorization: Bearer <token>

Either way the call runs as you, confined to one organization, and is recorded to the MCP audit log (/admin → MCP calls) with secret arguments redacted.

Quick check

# List available tools
curl -s https://<your-conductor-host>/mcp/list \
  -H "Authorization: Bearer $CONDUCTOR_MCP_TOKEN" | jq '.tools[].name'

# Fleet snapshot
curl -s https://<your-conductor-host>/mcp/call \
  -H "Authorization: Bearer $CONDUCTOR_MCP_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"conductor_read","input":{"action":"fleet_status"}}'

Get a token

Mint a per-user, org-scoped token from your Conductor instance — Settings → MCP tokens (/mcp_tokens). Name it, pick a scope (read or deploy), and copy it once (it's shown only at creation, hashed at rest). Every call runs as you, confined to that token's organization, and is recorded to the MCP audit log with secrets redacted. Revoke a token any time from the same page; removing a member from the org invalidates their tokens automatically.

Export it as CONDUCTOR_MCP_TOKEN in the shell that launches your agent — never paste the raw token into a committed config file.

(A single-tenant instance may instead use the shared CONDUCTOR_MCP_TOKEN env var set on the server; per-user tokens are preferred.)

Connect an agent (Claude / Cursor / Codex / any MCP client)

POST /mcp is a standards-compliant Streamable-HTTP MCP endpoint (JSON-RPC 2.0 — initialize / tools/list / tools/call / ping), so any MCP-capable client connects the same way.

There are two ways in. OAuth is the one to prefer: the client sends you to Conductor in a browser, you sign in and pick an organization, and the client stores a token you never see. No secret is copied by hand, tokens expire and refresh, and you can revoke a connection. A static bearer token (above) still works everywhere and is the right tool for CI and scripts.

Sign-in with OAuth (no token to paste)

Conductor is an OAuth 2.1 authorization server for its own MCP endpoint — discovery at /.well-known/oauth-authorization-server (RFC 8414) and /.well-known/oauth-protected-resource (RFC 9728), dynamic client registration at POST /oauth/register (RFC 7591), authorization-code + PKCE. A client needs no configuration beyond the URL: it discovers the rest.

Codex — native remote MCP support (codex-cli 0.145+):

codex mcp add conductor \
  --url https://<your-conductor-host>/mcp \
  --oauth-resource https://<your-conductor-host>/mcp
codex mcp login conductor          # opens the browser; sign in, pick an org
codex mcp get conductor            # verify

--oauth-resource binds the token to this MCP endpoint (RFC 8707). Conductor requires it: a token carrying no resource, or one minted for a different resource, is refused at /mcp. Ask for read-only access with codex mcp login conductor --scopes mcp_read.

claude.ai / Claude Desktop — add a custom connector pointing at https://<your-conductor-host>/mcp; it registers itself and runs the same browser login.

Any stdio-only client — the mcp-remote bridge is an OAuth client too, so it performs the same flow with no token in the config:

{ "mcpServers": { "conductor": {
  "command": "npx",
  "args": ["-y", "mcp-remote", "https://<your-conductor-host>/mcp"]
} } }

What you approve. Conductor always shows a consent screen naming the client, where it will send the code, the access level, and the organization — approving is an explicit click, never automatic. Read it: registration is open, so the client's name is whatever that client called itself. If you didn't start the connection, cancel.

What a connection is bound to. One user, one organization, one scope. The connection can only see and act on that org's apps and servers, exactly like an org-bound token, and write access still requires being an organization owner (or admin) — a plain member's connection is read-only whatever scope it asked for. Access tokens last 2 hours and refresh silently. A connection dies the moment its user leaves the org.

Revoking. Connected clients are listed on the Tokens page (/mcp_tokens) with a Revoke button that kills the client's tokens and its ability to refresh. Clients can also revoke their own (codex mcp logout conductor, or POST /oauth/revoke).

Static bearer token

Point the client at the endpoint and send the header. Claude Code — native HTTP transport:

claude mcp add --transport http conductor https://<your-conductor-host>/mcp \
  --header "Authorization: Bearer $CONDUCTOR_MCP_TOKEN"

Cursor — add to ~/.cursor/mcp.json:

{
  "mcpServers": {
    "conductor": {
      "url": "https://<your-conductor-host>/mcp",
      "headers": { "Authorization": "Bearer <CONDUCTOR_MCP_TOKEN>" }
    }
  }
}

Codex — a bearer token instead of OAuth (useful in CI, where no browser exists):

codex mcp add conductor --url https://<your-conductor-host>/mcp \
  --bearer-token-env-var CONDUCTOR_MCP_TOKEN

For a client that only speaks stdio, the mcp-remote bridge forwards to the endpoint and injects the header:

{ "mcpServers": { "conductor": {
  "command": "npx",
  "args": ["-y", "mcp-remote", "https://<your-conductor-host>/mcp",
           "--header", "Authorization: Bearer ${CONDUCTOR_MCP_TOKEN}"]
} } }

Export CONDUCTOR_MCP_TOKEN in the shell that launches the agent — never paste the raw token into a committed config file.

The agent then sees conductor_app, conductor_read, and the rest as native tools.

Transport note. Conductor's endpoint is JSON-mode Streamable HTTP — it answers each POST with one application/json response and never opens a server-initiated stream, so GET /mcp returns 405. That's spec-legal (SSE is optional). If a client insists on an SSE channel over native HTTP, connect through the mcp-remote bridge above and it works unchanged.

The toolset

The surface is eight flat tools, each taking an action (fewer tools keeps agent tool-selection accurate). Always trust GET /mcp/list over this table.

Tool Actions
conductor_read fleet_status, situation, logs, deployment, cloudflare (read-only)
conductor_app create, update, deploy, rollback, sync_status, transfer_plan (read-only dry-run of moving an app to another server)
conductor_app_config set_env, gen_deploy_key
conductor_server register, update, add_ssh_key, test_connection, audit, apply_updates, install_packages, run_script
conductor_database register_cluster, provision
conductor_domain add, remove, put_behind_cloudflare
conductor_github set_token, set_app, installations
conductor_runbook per-app deploy/runbook notes

Call GET /mcp/list for each tool's exact input schema.

Cloudflare over MCP. conductor_read action=cloudflare is the discovery entry point — it returns connected accounts, the zones they own, which apps are proxyable, the read-only Cloudflare MCP attach commands, and how to proxy a domain. conductor_domain action=put_behind_cloudflare (app_id/app_name, optional ssl_mode) does the cutover through Conductor's audited CloudflareClient. See Cloudflare + MCP.

Worked example — deploy an app

H=https://<your-conductor-host>/mcp/call
A=(-H "Authorization: Bearer $CONDUCTOR_MCP_TOKEN" -H "Content-Type: application/json")

# 1. Point the app at its repo (kamal build over SSH, public or private repo)
curl -s "$H" "${A[@]}" -d '{"name":"conductor_app","input":{
  "action":"update","app_name":"My App","deploy_method":"kamal",
  "repository_url":"https://github.com/acme/my-app.git","branch":"main"}}'

# 2. (Private repo) mint a deploy key, then add it to the repo:
#    gh repo deploy-key add <public_key> --repo acme/my-app --title conductor
curl -s "$H" "${A[@]}" -d '{"name":"conductor_app_config","input":{"action":"gen_deploy_key","app_name":"My App"}}'

# 3. Load the app's env (RAILS_MASTER_KEY, DB password, …); secrets are redacted in logs
curl -s "$H" "${A[@]}" -d '{"name":"conductor_app_config","input":{
  "action":"set_env","app_name":"My App","key":"RAILS_MASTER_KEY","value":"…","secret":true}}'

# 4. Deploy — returns a deployment_id
curl -s "$H" "${A[@]}" -d '{"name":"conductor_app","input":{"action":"deploy","app_name":"My App"}}'

# 5. Stream the result
curl -s "$H" "${A[@]}" -d '{"name":"conductor_read","input":{"action":"deployment","deployment_id":42}}'

Conductor's container clones the repo, generates .kamal/secrets from the env you loaded, builds on the target's Docker daemon over SSH, and deploys behind the shared proxy. See Deploy an app and Connect GitHub.

Setting secrets without leaking them (agents)

set_env needs the value. When an agent calls the tool directly, that value lands in the tool-call payload — i.e. in the conversation transcript. Conductor already redacts value in its reply and its audit log (\Avalue\z is a sensitive key), but the transcript exposure is on the client side, before the request is even sent.

The fix is to never let the secret pass through the model at all: read it from stdin and put it straight into the HTTPS body. The bundled bin/conductor CLI does exactly that — the value is read from stdin, so it is never an argv element, never printed, never logged. It needs only Ruby stdlib (no gems) and two env vars:

export CONDUCTOR_URL=https://<your-conductor-host>
export CONDUCTOR_MCP_TOKEN=<token>          # mint one at /mcp_tokens

The value comes from stdin, so you can pipe it from any source you already use — a file, an env var, or a secrets manager (1Password CLI, HashiCorp Vault, AWS Secrets Manager, pass, …). Conductor is deliberately unopinionated here: anything that can print a secret to stdout works, so you keep your existing secret store. The literal never appears in the command text:

# From a file (the file is the secret's home; nothing on argv):
bin/conductor set-env "My App" RAILS_MASTER_KEY --secret < config/master.key

# From an already-exported env var:
printf %s "$RAILS_MASTER_KEY" | bin/conductor set-env "My App" RAILS_MASTER_KEY --secret

# From a secrets manager — pipe its output straight in. Examples:
op read op://vault/myapp/master_key | bin/conductor set-env "My App" RAILS_MASTER_KEY --secret   # 1Password CLI
vault kv get -field=master_key secret/myapp | bin/conductor set-env "My App" RAILS_MASTER_KEY --secret   # HashiCorp Vault
aws secretsmanager get-secret-value --secret-id myapp/master_key --query SecretString --output text \
  | bin/conductor set-env "My App" RAILS_MASTER_KEY --secret   # AWS Secrets Manager

# localvault (zero-infra vault with an inject mode) — https://inventlist.com/tools/localvault
# install: brew install inventlist/tap/localvault
localvault exec --map myapp.master_key=V -- \
  bash -c 'printf %s "$V" | bin/conductor set-env "My App" RAILS_MASTER_KEY --secret'

# Generic form — any tool, input JSON on stdin (keeps secret fields off argv):
printf '{"action":"set_env","app_name":"My App","key":"K","value":"…"}' \
  | bin/conductor call conductor_app_config

Whatever the source, the secret flows source → stdin → TLS body and touches neither argv nor an agent's transcript. There's no dependency on any particular vault — set-env reads whatever you pipe it.

Tokens & scope

Three kinds of bearer credential work:

  • OAuth connection (recommended for agents). Minted by the browser sign-in above and held by the client. Bound to one user, one organization, and one MCP resource; expires in 2 hours and refreshes; revocable per client from the Tokens page. Write access additionally requires an organization owner (or admin).
  • Per-user / per-org token (recommended for CI and scripts). An ApiToken bound to a user + organization. MCP runs the call as that user, scoped to their organizationsconductor_app, conductor_app_config, conductor_read, logs, domains, etc. only see and act on apps/servers in orgs the user belongs to. One org's token can't touch another org's resources. This is how "anyone can deploy their own apps" works.
  • Legacy shared token. The instance CONDUCTOR_MCP_TOKEN env var runs as the first admin with global scope. Treat it like a root credential; rotate by changing the env var and redeploying.

Mint one self-serve in the Tokens page (top nav → Tokens, or /mcp_tokens): name it, pick a scopedeploy (full) or read (read-only: conductor_read only) — and copy the token (shown once). Tokens are bound to the active org, so they can only see and act on that org's apps. Revoke anytime.

(Console equivalent: ApiToken.generate(user:, name:, organization:, scope: "deploy").)

Audit log. Every call (tool, args, the real user + affected org behind the token, duration) is recorded; secret values are redacted.

K Deploy via MCP