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
| Method | Endpoint | Purpose |
|---|
| GET | /api/v1/recipes | List the curated recipe set |
| GET | /api/v1/recipes/{slug} | Get a single recipe |
| GET | /api/v1/recipes/{slug}/preview | Dry-run an install (no state change) |
| POST | /api/v1/recipes/{slug}/install | Atomically install a recipe |
Recipe shape
| Field | Type | Notes |
|---|
slug | string | URL-stable identifier, e.g. code-review-crew. |
name | string | Human label shown on the dashboard card. |
description | string | One-line tagline shown under the name. |
icon | string | Lucide icon name (matches the CrewIcon palette). |
color | string | Palette color id (blue, violet, amber, …). |
crew_slug | string | Preferred slug for the new crew. Install resolves slug → slug-2 → slug-3 etc. if it’s already taken. |
credentials | array | Credentials the recipe needs — see below. |
mcp_servers | array | MCP servers to provision on the new crew. |
Each credential entry:
| Field | Type | Notes |
|---|
env_var_name | string | What the agent will see at runtime (ANTHROPIC_API_KEY, GH_TOKEN, …). Doubles as the credential name in the workspace per the existing convention. |
provider | string | Canonical provider enum (ANTHROPIC, GITHUB, OPENAI, NONE, …). |
type | string | Credential type (API_KEY, AI_CLI_TOKEN, OAUTH2, CLI_TOKEN, SECRET). |
label | string | Human prompt shown in the install wizard (“Anthropic API key”). |
help_url | string | omitted | Where the user finds the value (e.g. https://console.anthropic.com/settings/keys). |
Each MCP server entry:
| Field | Type | Notes |
|---|
name | string | URL-stable name on the crew (must satisfy the crew_mcp_servers.name UNIQUE constraint). |
display_name | string | What shows in the connected list and marketplace cards. |
transport | string | stdio or streamable-http. |
command | string | omitted | Launch command for stdio servers. |
args | string[] | omitted | Argv for stdio servers. |
endpoint | string | omitted | URL for HTTP transports. |
env_mapping | object | omitted | Map of ENV_NAME → recipe credential env_var_name. The install flow turns this into the env_json the integration handlers consume. |
icon | string | omitted | Brand 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.
| Status | Condition |
|---|
200 | Recipe found — returns the recipe object (same shape as a List entry). |
404 | Recipe 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"
}
| Field | Type | Notes |
|---|
recipe | object | Full recipe (same shape as GET /api/v1/recipes/{slug}) — included so the FE can render the install Sheet from one round-trip. |
needed_credentials | string[] | Credentials the user must paste before install. Keyed by env_var_name. |
existing_credentials | object | Map of env_var_name → true for credentials already present in the workspace. Only true values are included. |
crew_slug_available | bool | true if the recipe’s preferred crew_slug is free in this workspace. |
resolved_crew_slug | string | The slug install will actually use — equal to recipe.crew_slug if available, otherwise the first free -N suffix. |
| Status | Condition |
|---|
404 | Recipe slug not found. |
500 | Credential 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"
}
}
| Field | Type | Notes |
|---|
credential_values | object | Map 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_labels | object | Optional. 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"]
}
| Field | Type | Notes |
|---|
crew_id | string (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_slug | string | The resolved slug (matches resolved_crew_slug from Preview, but Preview is advisory; this is the authoritative value). |
credentials_added | string[] | env_var_names the install created. |
credentials_reused | string[] | env_var_names already present in the workspace and reused. |
mcp_servers_added | string[] | MCP server names provisioned on the new crew. |
| Status | Condition |
|---|
400 | Bad JSON body or missing credential_values for credentials the workspace doesn’t already have. |
401 | Not authenticated. |
403 | Caller is below the ADMIN role (MANAGER and below are rejected — "manage" requires OWNER/ADMIN). |
404 | Recipe slug not found. |
500 | Tx 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.