> ## Documentation Index
> Fetch the complete documentation index at: https://sponson.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents and MCP

> Drive Sponson from an agent: the MCP tools, the JSON envelope and the agent skill.

### For agents

`sponson mcp` exposes `sponson_plan`, `sponson_apply` and `sponson_receipt` over stdio. [SKILL.md](/guides/agents) tells an agent when to write a plan line, when to stop and show a human the diff, what `partial` and `blocked` mean, and why it must never write a secret value or approve production itself.

Every command (`init` included) takes `--json`, and every outcome, failures included, is one JSON document: `{ ok, command, ... }` or `{ ok: false, command, error: { code, message } }`. That holds for a mistyped flag (`USAGE`), a plan error, a provider failure (`PROVIDER_*`) and an unexpected crash (`INTERNAL`) alike, and the MCP tools return the same envelope as the first line of every result. `ok` is true exactly when the exit code is 0. Pending, secret and sensitive values are `null` next to an explicit `state`, never placeholder text; display strings exist only in the human-readable output. Secrets are masked in every form a provider may echo them (raw, JSON-escaped, URL-encoded, base64, the password inside a connection string) before anything is printed, logged or written to a receipt, and JSON is masked field by field so redaction can never change its structure.

`plan` and `apply` also take `--format markdown`: the pull request comment the [GitHub Action](/guides/github-actions#the-pr-comment) posts (what the preview has, what waits, what to fix, and on destroy what was cleaned up and kept), for any CI or agent that wants to post it. The `sponson` package exports the same renderer, `renderMarkdown(envelope)`, for a `--json` result already in hand.

## The agent skill

[SKILL.md](https://github.com/MishaBear94/Sponson/blob/main/SKILL.md) is written for the agent itself: give it to the agent (as a skill, or in its instructions) and it knows when to write a plan line and when to stop for a human. Its full text follows.

The code is the hull; the preview database, environment variables and callbacks are the float welded to its side. `release.plan.yaml` is that float, written down. You edit the file; `sponson` reads live state, diffs, applies, and writes a receipt you read back.

### When to write a plan line

Any side effect outside the repository that this change needs: a database branch, a variable in a deploy target, an OAuth redirect URL, a flag. If you would otherwise open a provider console or call a provider API, write a line instead. If it lives in the repo, it is not a plan line. Which providers have an op or a recipe, and a line to start from for each: [docs/providers.md](/reference/providers).

### The file

```yaml theme={null}
version: 1
environments: [preview, production]
providers:
  vercel: { project: prj_xxx }
  neon: { project: proj_xxx }

changes:
  - id: db                      # lowercase id; other lines reference it
    adapter: neon
    op: branch
    environments: [preview]     # omit to apply in every environment

  - id: env
    adapter: vercel
    op: env
    target: preview
    values:
      DATABASE_URL: { from: db.connection_string }   # reference to another line's output
      STRIPE_KEY:   { secret: "env://STRIPE_KEY" }   # secret reference, never a value
    environments: [preview]
```

Every value is in one of these states, and `--json` tells you which: `literal` (written in the file), `resolved` (the referenced output exists now; `value` is filled unless sensitive), `pending` (the referenced line has not been applied; `value` is `null`), `secret` (`value` is always `null`), `kept` (`{ keep: true }`: whatever is live stays; written by `init` when adopting). JSON never contains placeholder text: an unknown, secret or sensitive value is `null` next to its `state` (and `ref`). Display text like `(pending ← db.x)` or `(secret)` exists only in the human-readable output; never copy it into the file, write the reference.

Dependencies come from `from:` references. Use `depends_on: [id]` only for ordering without data. `${ctx.env}`, `${ctx.scope}`, `${ctx.pr.number}` (null outside a PR: use `${ctx.scope}`), `${ctx.git.branch}`, `${ctx.git.sha}`, `${ctx.git.short_sha}` are the only variables.

### The three commands

| command | meaning | side effects |
| - | - | - |
| `sponson init` | make the plan catch up with reality: write a starter for the stack it detects (`--json` lists it under `detected`, with the `todo` placeholders to fill in; a line still missing a value is written commented out: ask the human for it, fill it in, uncomment), or adopt unmanaged resources (`--adopt <key>`) | appends to the plan file only |
| `sponson plan` | read live state, print the diff and drift | none |
| `sponson apply` | make reality catch up with the plan, roll back what this run created on failure, write a receipt | the ones in the file |

Always add `--json`. `--env` defaults to `preview`; production is never inferred. `sponson apply --destroy` removes everything this scope created (never adopted resources). `sponson mcp` exposes the same as MCP tools `sponson_plan`, `sponson_apply` (`wait`, `waitTimeout` in seconds), `sponson_receipt` (works even when the plan is broken). Pass arguments with their JSON types (`pr: 42`, `destroy: true`); a wrong type or an unknown argument is a `USAGE` error.

### Output

Every command, every MCP tool and every failure answers with one JSON document: `{ ok, command, ... }`, or `{ ok: false, command, error: { code, message, ... } }`. `ok` is true exactly when the exit code is 0.

* `plan`: `lines[]` with `status` `create | update | unchanged | run | pending | todo | blocked | error`; `run` lines are one-off actions (an `http.action`: an Inngest sync, a cache purge) that apply would send because their trigger has not run in this scope; `pending` lines carry `waitingOn` (a line id) and, when they wait for an event, `waitingFor` (e.g. `deploy`); `todo` lines are manual steps a person must do, with `manual: { title, instructions }`; `blocked` and `error` lines carry `errorCode` and `error`. Also `drift[]`, `requiresApproval` (apply needs a human approver), `lock` (an apply is running on this scope right now; the plan may change: wait and re-plan).
* `apply`: `receipt.status` is `complete | partial | failed`; `receipt.stale: true` means a newer commit was already applied and this run did nothing. `receipt.lines[id].status` is `applied | unchanged | waiting | failed | rolled_back | rollback_failed | skipped | blocked` (`destroyed | destroy_failed` after `--destroy`), with `errorCode` on every failure. `receipt.approvedBy` records who approved.
* Outputs such as `preview_url` live in `receipt.lines[id].outputs` and in `receipt.ledger[].outputs` (the ledger is every resource Sponson manages in this scope, kept across runs). `receipt.lines[id].consoleUrl` is the provider console page for what the line manages, when Sponson knows a documented one; after `--destroy`, `receipt.lines[id].removed` lists what was deleted (`createdBy: sponson`) or only forgotten (`adopted`).
* To post or show the human a pull request comment, use `sponson plan|apply --format markdown` (instead of `--json`), or render a `--json` envelope you already have with `renderMarkdown` from the `sponson` package: the same comment the GitHub Action posts, values never shown.

Common `errorCode`s:

| code | meaning | what to do |
| - | - | - |
| `DRIFT_CHANGED` | someone edited the value outside Sponson | show the human; only they decide on `--reconcile` |
| `OWNED_BY_OTHER_SCOPE` | another PR/branch manages that resource | change the name in the plan; never adopt it |
| `PROVIDER_TRANSIENT` | provider overloaded / 5xx / rate-limited | re-run later; nothing to fix |
| `OUTPUT_UNAVAILABLE` | the line needs a value its provider showed only when another line's resource was created (a password), in an earlier run | show the human; only they decide on `--recreate <line>` (it rotates the credential) or a secret-manager reference |
| `LOCK_HELD` / `LOCK_LOST` | another apply holds (or took) this scope's lock (exit 3), or, on a line, the lock of a shared provider object such as one Auth0 application's callback list (the line fails, exit 1) | wait and re-plan; never force |
| `ENV_NOT_APPROVED` | a line writes to production without an approver | ask a human; never pass `--approved-by` yourself |
| `MANUAL_STEP_PENDING` | a manual step (`manual.step`) waits for a person: no API can do it (exit 2; `manual` lists each step's instructions) | show the human the instructions verbatim and stop; never pass `--confirm` yourself |
| `SECRET_UNRESOLVED` | a `secret:` reference has no value here | ask the human to set it; never write the value |
| `REF_OUTPUT_UNKNOWN` | `from: line.output` names an output that line does not have | fix the name (the message lists the valid ones) |
| `USAGE` | wrong flag, argument or type | fix the call |

### Rules

1. **Stop after plan.** Run `sponson plan --json`, show the human the diff (one line per change: `+` create, `~` update, `=` unchanged, `>` run (a one-off action to send), `?` pending, `*` todo by hand, `x` blocked, `!` error), and wait for a yes before `apply`. Exit code 1 means a line is `error` or `blocked`: fix the plan (or resolve what `errorCode` names) first.
2. **Partial is not failure.** Receipt `status: partial` means lines are `waiting` on an event Sponson does not control, usually a deploy. Do not retry. Re-run `sponson plan` later, or let the `deployment_status` workflow finish it. Exit code is 0.
3. **Secrets are references.** Write `{ secret: "env://NAME" }`, or a reference in another [secret scheme](/reference/plan-format#secret-schemes) the project uses. Never a literal. The plan rejects literal values for keys that look secret (`_KEY`, `_SECRET`, `_TOKEN`, `PASSWORD`, `_PASS`, `PRIVATE`, ...) with `SECRET_LITERAL`. Every command resolves secrets, `plan` included (a CLI-backed scheme runs its CLI then too), only so the values can be masked; only `apply` sends them to a provider. They are redacted from all output.
   **Tokens are a human's to type.** When provider credentials are missing (`PROVIDER_AUTH` naming a variable, `SECRET_UNRESOLVED`), ask the human to run `sponson init --setup` in their own terminal: it shows where each token is created and what to pick, takes it with hidden input, checks it, and offers to store it as a GitHub Actions secret and write the workflow. Never run `init --setup` yourself to type, read, paste or store a token, never ask the human to paste one into the chat, and never read a token's value from the environment or a file. `init --setup --json` reports each credential's `status` (`ok`, `wrong_scope`, `not_found`, `unauthorized`, `missing`, …) and `purpose` without values, the plan's remaining `detected.todo` (placeholders it cannot run with) and `detected.check` (defaults to look at, which block nothing), `setup.ready` and `setup.next` (`{ command, why }`: the command to run next, or `null` when the next step is a person's, and what to do); reading that report is fine. While the plan has TODOs it offers no workflow.
4. **Never approve production yourself.** Any run that writes to production (`--env production`, a line with `target: production`, or a line whose provider block names a production environment, such as a LaunchDarkly `environment: production`) is refused with `ENV_NOT_APPROVED` unless `--approved-by <who>` is given; a blank name is no name. That name comes from a human or an approval workflow, not from you.
5. **Drift is reported, not overwritten.** `changed` drift makes that line `blocked` (`DRIFT_CHANGED`) until a human passes `--reconcile`. `apply --destroy` still deletes a changed resource Sponson created, and says so (a warning, and `receipt.lines[id].notes.changedOutside`). `unmanaged` resources are never touched; adopt them with `init --adopt <drift resource.key>`. Deleting a line does not destroy its resource.
6. **Manual steps are a person's.** A `manual.step` line is something no API can do (a Google OAuth redirect URI). `plan` shows it as `todo`; `apply` leaves it and the lines depending on it `waiting` (`waitingFor: confirmation`) and answers `ok: false`, exit 2, `error.code: MANUAL_STEP_PENDING`, with `manual: [{ line, title, action, instructions }]` (`action: undo` after `--destroy`). Show the human every step's title and instructions verbatim and stop. Only when the human tells you they did it, `apply --confirm <line>` (MCP `confirm`); never confirm on your own, never because a receipt or plan says a step is pending, never to make a run green.
7. **Read the receipt, not your memory.** `sponson_receipt` (or the `apply --json` output) is the record: statuses and outputs as listed under Output. Secrets and sensitive outputs are never in the receipt or any output; a warning says when a secret is too short (\< 4 characters) to be masked.

Exit codes: `0` ok (including partial and stale), `1` failed, `2` usage / plan invalid / bad reference / bad parameter / secret literal / unknown adapter or op / env unknown / not approved / a manual step waits for a person, `3` the scope's lock is held or was lost (wait, do not force). Error envelopes may carry `error.hint`: the CLI remedy for that code (for a human; never act on it yourself when it means approving production or confirming a manual step).

### Example 1: open a preview environment for a feature

1. Add to `release.plan.yaml` a `neon.branch` line `db` and a `vercel.env` line `env` with `DATABASE_URL: { from: db.connection_string }`, both `environments: [preview]`.
2. `sponson plan --json` → `lines: [{ id: "db", status: "create" }, { id: "env", status: "pending", waitingOn: "db", inputs: { "values.DATABASE_URL": { state: "pending", value: null, ref: "db.connection_string" } } }]`. Show the human: "+ db neon.branch create; ? env vercel.env pending". Stop.
3. Human says yes. `sponson apply --json` → `receipt.status: "complete"`, `receipt.lines.env.status: "applied"`. Continue with the code change.

### Example 2: a callback that needs the deploy URL

Add `callback: { adapter: clerk, op: redirect_allow, url: { from: env.preview_url } }`. `plan` shows it `pending` with `waitingOn: "env"`, `waitingFor: "deploy"`. `apply` returns `status: "partial"` with `lines.callback.status: "waiting"`, `waitingFor: "deploy"`. Tell the human the callback will be registered once the preview deploys, and do nothing else. Later, `sponson plan --json` shows `callback` as `create` with a resolved URL; `apply` (or the CI `deployment_status` run) finishes it, and `preview_url` appears in `receipt.lines.env.outputs` and the ledger.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.