Composio Client Onboarding — Golden Path
Composio Client Onboarding — Golden Path
Single CLI replaces the ~60-min manual onboarding ritual (one Composio project per client, 8× browser OAuth flows, hand-edit CLAUDE.md tables, hand-edit
.mcp.json). Now: ~7 min wall time, 1 click per tool from the client, zero hand-edits.
What the CLI does
- Creates a V5 tenant in the gateway (bearer token via
POST /admin/tokens/issue). - Initiates a Composio OAuth
connected_accountfor every requested toolkit. - Either emails the client a single landing page with N OAuth links OR prints them to terminal (for screen-share).
- Polls every 5s up to 15 min, rendering a live terminal table that flips
INITIATED → ACTIVE. - Writes the routing record atomically to
~/.claude/clients/{slug}.json. - Regenerates
~/.claude/clients/index.json+~/.claude/clients/ROUTING.md. - Prints the summary table + the V5 bearer token (once — store hint only).
Architecture (locked decisions)
- One canonical Composio project (
ascend-canonical) hosting every client. Route per-client viauser_id={slug}on every tool call. - One shared
composioMCP server in.mcp.jsonpointing at the canonical project. - Per-client routing lives in
~/.claude/clients/{slug}.json; CLAUDE.md@-includes the auto-generatedROUTING.mddigest. - Polling, not webhooks — bounded by the 10-min Composio OAuth window.
- Bearer tokens print once + last-4 hint stored — never persist raw to disk.
- SaaS tokens stay in Composio — invariant 5 v3; CLI never writes
tokens:*KV keys.
Prereqs
| Credential | Source (in lookup order) |
|---|---|
COMPOSIO_API_KEY_PROD | Infisical project 1c1907fa-... env prod → ~/.zprofile fallback |
ASCEND_ADMIN_KEY | Infisical → ~/.zprofile → Wrangler secret list |
ASCEND_GATEWAY_TOKEN | ~/.zprofile (only for --client-email mode; used to call V5 SES) |
source ~/.zprofile before invoking. The CLI runs credential discovery in the order above and exits with a clear message if none resolve.
Usage
# Self-service: client gets one email with N OAuth linksnpm run onboard:client -- --slug acme --name "Acme" --level developer \ --tools hubspot,salesforce,gong,slack,googlesuper,googleads,linkedin,apollo \ --client-email founder@acme.com
# Assisted: prints OAuth links to terminal for screen-sharenpm run onboard:client -- --slug acme --tools hubspot,gong --assist
# Resume a partial onboard (idempotent — skips ACTIVE tools)npm run onboard:client -- --slug acme --resume
# Add a tool to an existing clientnpm run onboard:client -- --slug acme --add-tool quickbooks
# Tear down (test slugs only — confirm via --slug starts with test-*)npm run onboard:client -- --slug test-acme --teardown
# Dry-run (no API calls, prints intent + validates toolkits resolve)npm run onboard:client -- --slug acme --tools hubspot --dry-runSlug rules
- Lowercase, alphanumeric + hyphen.
- Regex:
^[a-z0-9][a-z0-9-]{1,30}$ - Must not already exist in
~/.claude/clients/index.json(the CLI checks).
Exit codes
| Code | Meaning |
|---|---|
0 | All tools ACTIVE (or dry-run resolved cleanly) |
1 | Validation error, missing toolkit, OAuth timeout, or any tool FAILED/EXPIRED |
2 | Usage error (missing required flag) |
What gets written
~/.claude/clients/ .active ← 1-line file: current client slug index.json ← {"clients": ["ascend","kahuna","acme"]} ROUTING.md ← auto-generated digest (CLAUDE.md @-includes this) ascend.json kahuna.json acme.json ← shape: { slug, name, level, v5, composio, tools }The per-client JSON shape lives in scripts/composio/routing-write.ts::ClientRecord. Atomic write (tmp + rename) — no partial reads from parallel sessions.
Failure modes
- Toolkit missing in ascend-canonical (e.g. hubspot, salesforce, gong, linkedin_ads as of 2026-05-18). CLI fails loud with
auth_config_idlookup error. Resolutions: register the ascend-side OAuth app, or temporarily route that client throughkahuna_prodproject. - OAuth timeout (15 min) — Composio link expired before client clicked. CLI exits 1; re-run with
--resume. - Composio API 5xx — single attempt, no retry (fail-fast invariant). Re-run with
--resume. - V5 SES delivery failure (
--client-emailmode) — CLI exits 1; fall back to--assistand screen-share the links.
Verification (smoke test, throwaway slug)
source ~/.zprofilenpm run onboard:client -- --slug test-acme --name "Test Acme" --level developer \ --tools slack,gamma --assist
# Click each link in the terminal. Wait for "ACTIVE ACTIVE".
cat ~/.claude/clients/test-acme.json # tools.slack.status == "ACTIVE"grep test-acme ~/.claude/clients/ROUTING.mdcurl -H "x-api-key: $COMPOSIO_API_KEY_PROD" \ "https://backend.composio.dev/api/v3/connected_accounts?user_id=test-acme"
# Cleanup:npm run onboard:client -- --slug test-acme --teardownManual-only tools
See docs/composio/manual-tools.md for tools that need out-of-band approval before OAuth works (LinkedIn sandbox, Google Ads dev-token allowlist).
Time-to-onboard
| Phase | Today | After |
|---|---|---|
| V5 tenant | 3 min | 30 sec |
| Composio entity | manual | 2 sec |
| Per-tool OAuth | 8 × 5–10 min = 40–80 min | 8 × 30 sec client click + parallel polling ≈ 4 min |
| Routing table | 5 min manual edit + merge conflict risk | 0 sec (auto-written) |
| MCP server setup | 5 min | 0 sec (shared server) |
| Total | ~60 min | ~7 min |
Client manual effort: 1 click per tool (OAuth consent screen). Nothing else.