Skip to main content

Recipes

A recipe is a hardcoded bundle of:
  • a curated crew identity (name, slug, icon, color),
  • the credentials it needs (provider + env var name + UX label), and
  • the MCP servers it should run (transport, command, env mapping).
Recipes are baked into the binary (internal/recipes) for the MVP — there is no admin UI to author them; a future migration may promote them to a DB-backed catalogue. The browse / preview / install endpoints below back the dashboard empty state and the “Install recipe” Sheet. The implementation lives in internal/api/recipes.go.
Install is atomic: credentials, the new crew, and its MCP servers are all created in a single transaction — any failure rolls back everything, so a half-installed recipe (orphan crew without its credentials, etc.) never exists.

Endpoints

MethodEndpointPurpose
GET/api/v1/recipesList the curated recipe set
GET/api/v1/recipes/{slug}Get a single recipe
GET/api/v1/recipes/{slug}/previewDry-run an install (no state change)
POST/api/v1/recipes/{slug}/installAtomically install a recipe

Recipe shape

FieldTypeNotes
slugstringURL-stable identifier, e.g. code-review-crew.
namestringHuman label shown on the dashboard card.
descriptionstringOne-line tagline shown under the name.
iconstringLucide icon name (matches the CrewIcon palette).
colorstringPalette color id (blue, violet, amber, …).
crew_slugstringPreferred slug for the new crew. Install resolves slugslug-2slug-3 etc. if it’s already taken.
credentialsarrayCredentials the recipe needs — see below.
mcp_serversarrayMCP servers to provision on the new crew.
Each credential entry:
FieldTypeNotes
env_var_namestringWhat the agent will see at runtime (ANTHROPIC_API_KEY, GH_TOKEN, …). Doubles as the credential name in the workspace per the existing convention.
providerstringCanonical provider enum (ANTHROPIC, GITHUB, OPENAI, NONE, …).
typestringCredential type (API_KEY, AI_CLI_TOKEN, OAUTH2, CLI_TOKEN, SECRET).
labelstringHuman prompt shown in the install wizard (“Anthropic API key”).
help_urlstring | omittedWhere the user finds the value (e.g. https://console.anthropic.com/settings/keys).
Each MCP server entry:
FieldTypeNotes
namestringURL-stable name on the crew (must satisfy the crew_mcp_servers.name UNIQUE constraint).
display_namestringWhat shows in the connected list and marketplace cards.
transportstringstdio or streamable-http.
commandstring | omittedLaunch command for stdio servers.
argsstring[] | omittedArgv for stdio servers.
endpointstring | omittedURL for HTTP transports.
env_mappingobject | omittedMap of ENV_NAME → recipe credential env_var_name. The install flow turns this into the env_json the integration handlers consume.
iconstring | omittedBrand logo hint.

Browse & preview

Read-only endpoints that back the dashboard cards and the install Sheet.

GET /api/v1/recipes

Return the curated recipe set in display order. No pagination — the catalogue is intentionally small (3 entries at the time of writing). Auth: authenticated session (no workspace context required — recipes are static).
[
  {
    "slug": "code-review-crew",
    "name": "Code review crew",
    "description": "Anthropic-powered agent that reviews your GitHub pull requests.",
    "icon": "git-pull-request",
    "color": "blue",
    "crew_slug": "code-review",
    "credentials": [
      {
        "env_var_name": "ANTHROPIC_API_KEY",
        "provider": "ANTHROPIC",
        "type": "API_KEY",
        "label": "Anthropic API key",
        "help_url": "https://console.anthropic.com/settings/keys"
      },
      {
        "env_var_name": "GH_TOKEN",
        "provider": "GITHUB",
        "type": "CLI_TOKEN",
        "label": "GitHub personal access token",
        "help_url": "https://github.com/settings/tokens"
      }
    ],
    "mcp_servers": [
      {
        "name": "github",
        "display_name": "GitHub",
        "transport": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-github"],
        "icon": "github",
        "env_mapping": { "GITHUB_PERSONAL_ACCESS_TOKEN": "GH_TOKEN" }
      }
    ]
  }
]

GET /api/v1/recipes/{slug}

Return a single recipe by slug. Distinct from List so the install Sheet can fetch fresh detail without re-iterating the whole catalogue. Auth: authenticated session.
StatusCondition
200Recipe found — returns the recipe object (same shape as a List entry).
404Recipe slug not found.

GET /api/v1/recipes/{slug}/preview

Dry run for the install Sheet. Tells the FE which credentials the user already has in the workspace (so the wizard can skip the “Paste your X” step for those), and which crew slug the install will actually resolve to (suffixes -2 / -3 … if the recipe’s preferred slug is already taken). Does not mutate any state. Auth: authenticated session + workspace context (workspace_id query parameter). Response: 200 OK
{
  "recipe": { "...": "full recipe object" },
  "needed_credentials": ["BRAVE_API_KEY"],
  "existing_credentials": { "ANTHROPIC_API_KEY": true },
  "crew_slug_available": false,
  "resolved_crew_slug": "research-2"
}
FieldTypeNotes
recipeobjectFull recipe (same shape as GET /api/v1/recipes/{slug}) — included so the FE can render the install Sheet from one round-trip.
needed_credentialsstring[]Credentials the user must paste before install. Keyed by env_var_name.
existing_credentialsobjectMap of env_var_name → true for credentials already present in the workspace. Only true values are included.
crew_slug_availablebooltrue if the recipe’s preferred crew_slug is free in this workspace.
resolved_crew_slugstringThe slug install will actually use — equal to recipe.crew_slug if available, otherwise the first free -N suffix.
StatusCondition
404Recipe slug not found.
500Credential lookup or slug resolution failed (rare — surfaces a DB-level error rather than silently treating a failed query as “missing credential” and then 409-ing mid-install).

Install

Atomically commit a recipe — credentials, crew, and MCP servers — in one transaction.

POST /api/v1/recipes/{slug}/install

Atomic install. Creates the credentials the recipe needs (or reuses existing ones), the crew, and the MCP servers in a single SQLite transaction. The response describes what was actually created vs. reused so the FE can phrase the success toast accurately.
Install requires OWNER or ADMIN role (canRole(role, "manage")). The "manage" action maps to OWNER/ADMIN only — a MANAGER is not sufficient to install a recipe, even though MANAGER can create individual crews/credentials.
Auth: authenticated session + workspace context + OWNER or ADMIN role (canRole(role, "manage")).
{
  "credential_values": {
    "ANTHROPIC_API_KEY": "sk-ant-...",
    "GH_TOKEN": "ghp_..."
  },
  "account_labels": {
    "ANTHROPIC_API_KEY": "Personal — Anthropic Console",
    "GH_TOKEN": "Bot account"
  }
}
FieldTypeNotes
credential_valuesobjectMap of env_var_name → raw secret. Missing entries are treated as “user already has this credential” — install looks the row up by env_var_name and reuses it. Bad shape → 400.
account_labelsobjectOptional. Map of env_var_name → human label for the credential row. If absent, the recipe’s label is used.

Validation

The handler does a cheap shape check outside the transaction so it can 400 fast on bad input: for every credential the recipe declares, either the workspace must already contain it (looked up by env_var_name) or the request body must carry a non-empty value for it. Anything else returns:
{
  "error": "Missing credential values",
  "missing_credentials": ["BRAVE_API_KEY"]
}

Race-safe credential upsert

Two concurrent installs of the same recipe on the same workspace must both converge on the same credential row rather than 500-ing the loser on UNIQUE(workspace_id, name). Implementation uses INSERT OR IGNORE followed by SELECT id — the loser’s insert becomes a no-op; the follow-up SELECT returns whichever id won the race.

Race-safe crew slug allocation

Crew slug allocation also happens inside the transaction. If the recipe’s crew_slug is taken at insert time, the handler retries with slug-2, slug-3, … up to 100 attempts. Slug resolution from the Preview endpoint is advisory only — the install does its own lookup inside the tx so the existence check and the insert see the same snapshot.

Response

Status: 201 Created
{
  "crew_id": "crw_01HVZ...",
  "crew_slug": "research-2",
  "credentials_added": ["BRAVE_API_KEY"],
  "credentials_reused": ["ANTHROPIC_API_KEY"],
  "mcp_servers_added": ["brave-search"]
}
FieldTypeNotes
crew_idstring (CUID)The new crew’s id. The FE redirects to /crews?crew={crew_slug} — returning the id lets it skip a follow-up fetch.
crew_slugstringThe resolved slug (matches resolved_crew_slug from Preview, but Preview is advisory; this is the authoritative value).
credentials_addedstring[]env_var_names the install created.
credentials_reusedstring[]env_var_names already present in the workspace and reused.
mcp_servers_addedstring[]MCP server names provisioned on the new crew.
StatusCondition
400Bad JSON body or missing credential_values for credentials the workspace doesn’t already have.
401Not authenticated.
403Caller is below the ADMIN role (MANAGER and below are rejected — "manage" requires OWNER/ADMIN).
404Recipe slug not found.
500Tx commit failed, encryption failed, or slug allocation exhausted 100 attempts (the last is effectively impossible in practice — nobody installs the same recipe a hundred times).

See also

  • Credentials — the workspace credential table that recipes upsert into.
  • Crews — the crew row that recipes create.
  • Integrations — the MCP server table (crew_mcp_servers) that recipes populate.