You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(mcp): operate tools — drive the full bundle lifecycle (vault/env/presign/pause/rotate/wake) (#41)
* feat(mcp): add get_capabilities + get_deployment_events read-only tools
Closes the P0 "agent flies blind" gap: the MCP had 19 tools covering
provision + deploy-create but no way to (a) read the live tier matrix
before a call 402s, or (b) autopsy a failed deploy to self-correct.
- get_capabilities → GET /api/v1/capabilities (auth-OPTIONAL public
discovery surface; iterates the api's live plans registry, renders
per-tier storage/connection/resource-count/deployment caps + pricing
+ backup/RPO/RTO promises, cheapest-first).
- get_deployment_events → GET /api/v1/deployments/:id/events (auth-required;
the rule-27 failure-timeline: kind/reason/exit_code/event/last_lines/hint/
created_at, newest-first, so an agent can fix a broken Dockerfile and
redeploy).
Follows the existing tool pattern: client methods in src/client.ts reuse
request<T>() so the error envelope (agent_action/upgrade_url/claim_url) is
preserved verbatim through ApiError → formatError; tools registered in
src/index.ts format output like the sibling deploy tools.
Tests: mock-api gains a public /capabilities route and a /deployments/:id/
events route (matched before the generic deployments-prefix block; seeds a
failure_autopsy timeline for "fail"-named deploys). Drift guards updated
19→21 (tool-coverage J20/J21, integration EXPECTED_TOOLS). New client-unit,
tools-unit, and tool-contract tests cover success/empty/limit/404/auth-gate
+ every fallback branch. README Tools table updated.
Gate: npm run build && npm test — 413 pass / 0 fail; client.js 100% line /
95.76% branch, index.js 99.81% line / 95.62% branch (only the pre-existing
server.connect binary block uncovered), all files 95.68% branch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(mcp): add operate tools so an agent can drive the full bundle lifecycle
Extends the capabilities/deploy-events branch with 9 "operate" tools wrapping
existing api endpoints (each route verified against api/internal/router/router.go
before wiring). Preserves the ApiError → agent_action/upgrade_url passthrough
and mirrors the existing tool/client pattern.
New tools (J22-J30):
- set_vault_key → PUT /api/v1/vault/:env/:key (closes D4: lets an
agent WRITE the vault://env/KEY refs create_deploy advertises)
- rotate_vault_key → POST /api/v1/vault/:env/:key/rotate
- update_deploy_env → PATCH /deploy/:id/env (note: real route is /deploy/:id/env,
NOT /api/v1/deployments/:id/env as the brief stated)
- update_stack_env → PATCH /stacks/:slug/env
- presign_storage → POST /storage/:token/presign (token-in-path broker auth)
- pause_resource → POST /api/v1/resources/:id/pause (Pro+)
- resume_resource → POST /api/v1/resources/:id/resume (Pro+)
- rotate_credentials → POST /api/v1/resources/:id/rotate-credentials
- wake_deployment → POST /deploy/:id/wake (flag-gated; 501 when scale-to-zero off)
Tool descriptions stay accurate vs api/plans.yaml (vault tiers, Pro+ pause/resume,
deployments_apps caps) with no hardcoded drift.
Tests:
- mock-api: 9 new routes + vault/stackEnv stores + seedResource/seedDeployment seams
- operate-tools-unit.test.ts: success + 401/402/404/409/501/zod-validation per tool
- tool-contract.test.ts: J22-J30 agent-facing error-mapping contract
- prod-cohort.test.ts: live PROD integration — mints a real is_test_cohort account
(POST /internal/e2e/account), drives get_capabilities + set_vault_key/rotate_vault_key
against https://api.instanode.dev, then reaps the cohort. Gated behind
INSTANODE_PROD_COHORT=1 + E2E_ACCOUNT_TOKEN; skips clean otherwise.
- drift guards updated: 21 → 30 tools (tool-coverage MAPPED_TOOLS + integration
EXPECTED_TOOLS).
Gate: npm run build && npm test green (451 tests, 0 fail; client.js 100% lines,
index.js 99.85%). Live-verified: PROD cohort run passed end-to-end and reaped clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: 100% patch coverage on operate-tool error branches
The patch-coverage gate (diff-cover, 100% of changed lines) failed on
src/index.ts: Missing lines 1859-1860,1906-1907,1940-1943,1958-1959,
1994-1997,2012-2013,2081-2082,2155-2156,2196-2197 (22 lines) — the
catch(err)→formatError branches and the empty-env early-returns in the 9
new operate tools (set/rotate_vault_key, update_deploy/stack_env,
presign_storage, resume_resource, rotate_credentials, etc).
Root cause: the coverage.yml "Generate lcov mapped to TS sources" step
omitted operate-tools-unit.test.js from its file list, so the operate
tools' error paths were never executed during the lcov run even though
operate-tools-unit.test.ts already asserts every one of those branches
(401/402/404/409/501/empty-env). The main `npm test` script DID include
the file; only the lcov-generation command was out of sync.
Add operate-tools-unit.test.js to the lcov step (matching the package.json
test-script order). diff-cover now reports src/index.ts (100%), 0 missing
lines, overall 100%. Also gitignore the transient coverage/ artifact CI
writes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+11Lines changed: 11 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,13 +124,24 @@ to reach for this MCP, see <https://instanode.dev/agent.html>.
124
124
| `get_stack` | `GET /stacks/{stack_id}` — Poll a stack's per-service status + URLs. Anonymous-friendly. `stack_id` required. |
125
125
| `list_deployments`| `GET /api/v1/deployments` — List all deployments on the caller's team. Requires `INSTANODE_TOKEN`. |
126
126
| `get_deployment` | `GET /api/v1/deployments/:id` — Fetch one deployment (poll until `status="running"`). Requires `INSTANODE_TOKEN`. |
127
+
| `get_deployment_events` | `GET /api/v1/deployments/:id/events` — Read the failure-timeline autopsy for a deployment (`kind`/`reason`/`exit_code`/`event`/`last_lines`/`hint`/`created_at`, newest first) so an agent can self-correct a broken Dockerfile. Optional `limit`. Requires `INSTANODE_TOKEN`. |
127
128
| `redeploy` | `POST /deploy/:id/redeploy` — Push updated code to an existing deployment BY ID. Same URL, new build. Requires `tarball_base64` (same shape as `create_deploy`) — the api never reuses the original tarball. For the more common "update by name" path prefer `create_deploy({ name, redeploy: true, tarball_base64 })`. Requires `INSTANODE_TOKEN`. |
128
129
| `delete_deployment` | `DELETE /deploy/:id` — Tear down a running deployment. Irreversible. Requires `INSTANODE_TOKEN`. |
129
130
| `claim_resource` | Helper — turn an `upgrade_jwt` from any `create_*` response into the dashboard claim URL the user should click. No API call. No auth required. |
130
131
| `claim_token` | `POST /claim` — Programmatic claim: attach an anonymous resource to the authenticated account using its `upgrade_jwt` + `email`. No auth required. |
131
132
| `list_resources` | `GET /api/v1/resources` — List resources on the caller's account. Requires `INSTANODE_TOKEN`. |
132
133
| `delete_resource` | `DELETE /api/v1/resources/{token}` — Hard-delete a resource you own. Paid tier only. Requires `INSTANODE_TOKEN`. |
133
134
| `get_api_token` | `POST /api/v1/auth/api-keys` — Mint a fresh bearer Personal Access Token (PAT). Requires an existing user-session `INSTANODE_TOKEN` (PATs cannot mint other PATs — the API returns 403 in that case). |
135
+
| `get_capabilities`| `GET /api/v1/capabilities` — Read the live per-tier capability matrix (storage / connection / resource-count / deployment caps, pricing, backup + RPO/RTO promises) in upgrade order so an agent can plan a provision before a call `402`s. **Auth optional** (public discovery surface). |
136
+
| `set_vault_key` | `PUT /api/v1/vault/{env}/{key}` — Write a secret to the team vault (always a new version). Reference it from a deploy as `vault://{env}/{key}` in `env_vars`; the API decrypts it at deploy time. Vault is paid (Hobby+ = 20 entries, Pro/Team = unlimited; Hobby/Pro restrict env to `production`). Requires `INSTANODE_TOKEN`. |
137
+
| `rotate_vault_key`| `POST /api/v1/vault/{env}/{key}/rotate` — Rotate a vault secret's value (new version, recorded under a distinct audit action). Redeploy referencing apps to apply. Requires `INSTANODE_TOKEN`. |
138
+
| `update_deploy_env` | `PATCH /deploy/{id}/env` — Merge env vars into an existing deployment (incoming wins; values may be `vault://env/KEY` refs). Returns the merged map with secrets redacted. Redeploy to apply. Requires `INSTANODE_TOKEN`. |
139
+
| `update_stack_env`| `PATCH /stacks/{slug}/env` — Merge env vars into an existing stack (row-locked; an empty-string value deletes a key). Redeploy the stack to apply. Requires `INSTANODE_TOKEN`. |
140
+
| `presign_storage` | `POST /storage/{token}/presign` — Mint a short-lived (≤1h) presigned S3 URL (`GET`/`PUT`/`HEAD`) scoped to a storage prefix. Auth is the storage token in the path — works for anonymous-tier storage. `DELETE` is not offered (a leaked URL must not wipe a prefix). |
141
+
| `pause_resource` | `POST /api/v1/resources/{id}/pause` — Suspend a resource without deleting it (storage + connection URL preserved; new connections refused). **Pro tier or higher.** Requires `INSTANODE_TOKEN`. |
| `rotate_credentials` | `POST /api/v1/resources/{id}/rotate-credentials` — Rotate a resource's password; returns the NEW `connection_url` in plaintext (host + DB unchanged). Locks out a leaked old URL. Requires `INSTANODE_TOKEN`. |
144
+
| `wake_deployment` | `POST /deploy/{id}/wake` — Explicitly wake a scaled-to-zero deployment (scales to 1 replica; cold-start before serving). Flag-gated on the platform: returns 501 `scale_to_zero_disabled` when the feature is off. Requires `INSTANODE_TOKEN`. |
0 commit comments