{
  "markdown": "# instanode-mcp\n\nMCP server for [instanode.dev](https://instanode.dev). Lets AI coding agents\n(Claude Code, Cursor, Windsurf, Continue, etc.) provision the full bundle of\nephemeral developer infrastructure over HTTPS — no Docker, no signup required\nfor the free anonymous tier.\n\nOne tool call per resource type, each returning a drop-in connection string:\n\n- **Postgres** (`create_postgres`) → `postgres://...` with pgvector pre-installed\n- **Redis** (`create_cache`) → `redis://...` with ACL-scoped user + namespace\n- **MongoDB** (`create_nosql`) → `mongodb://...` with role scoped to the DB\n- **NATS JetStream** (`create_queue`) → `nats://...` with scoped subject namespace\n- **S3-compatible storage** (`create_storage`) → endpoint + keys + prefix\n  (backed by DigitalOcean Spaces)\n- **Webhook receiver** (`create_webhook`) → public URL that stores every\n  inbound request\n- **Container deployment** (`create_deploy`) → upload a base64 gzip tarball\n  (Dockerfile + source), get back a public URL in ~30s. Bind any of the\n  resources above by passing their tokens as `resource_bindings` — the API\n  resolves tokens to connection URLs server-side.\n- **Multi-service stack** (`create_stack`) → declare 1..N services in an\n  `instant.yaml` manifest, ship them as a bundle in a single MCP call. Anonymous\n  callers get a 6h-TTL stack with a live URL on\n  `*.deployment.instanode.dev` — no card required. Cross-service refs\n  (`service://<name>`) resolve cluster-internally at deploy time. Poll\n  status with `get_stack`.\n\nEvery anonymous resource auto-expires in 24h. The provision response carries\na `note` and `upgrade` field — the MCP server surfaces both verbatim so the\nagent can show the user the exact CTA + claim URL needed to keep the\nresource permanently. Run `claim_resource` on the returned `upgrade_jwt` to\nget the dashboard claim URL.\n\n## Install\n\n### Claude Code\n\n```bash\nclaude mcp add instanode -- npx -y instanode-mcp@latest\n```\n\nTo authenticate (unlock paid-tier limits and the account-management tools):\n\n```bash\nclaude mcp add instanode \\\n  --env INSTANODE_TOKEN=<paste from https://instanode.dev/dashboard> \\\n  -- npx -y instanode-mcp@latest\n```\n\n### Cursor\n\nAdd to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):\n\n```json\n{\n  \"mcpServers\": {\n    \"instanode\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"instanode-mcp@latest\"],\n      \"env\": {\n        \"INSTANODE_TOKEN\": \"<optional — paste from dashboard for paid tier>\"\n      }\n    }\n  }\n}\n```\n\n### Windsurf\n\nAdd to `~/.codeium/windsurf/mcp_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"instanode\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"instanode-mcp@latest\"],\n      \"env\": {\n        \"INSTANODE_TOKEN\": \"<optional>\"\n      }\n    }\n  }\n}\n```\n\n### Continue.dev\n\nAdd to your `~/.continue/config.yaml`:\n\n```yaml\nmcpServers:\n  - name: instanode\n    command: npx\n    args: [\"-y\", \"instanode-mcp@latest\"]\n    env:\n      INSTANODE_TOKEN: \"<optional>\"\n```\n\nFor a drop-in `CLAUDE.md` / `.cursorrules` that tells the agent exactly when\nto reach for this MCP, see <https://instanode.dev/agent.html>.\n\n## Environment\n\n| Variable                  | Required | Default                       | Purpose                                                                                                                                                                                  |\n|---------------------------|----------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `INSTANODE_TOKEN`         | No       | —                             | Bearer JWT minted at <https://instanode.dev/dashboard>. Required for `list_resources`, `claim_token`, `delete_resource`, `get_api_token`, and all deploy tools (`create_deploy`, `list_deployments`, `get_deployment`, `redeploy`, `delete_deployment`). Unlocks paid-tier limits on every `create_*`. |\n| `INSTANODE_API_URL`       | No       | `https://api.instanode.dev`   | Override the API base URL. Only set this for local development against a k3s cluster.                                                                                                    |\n| `INSTANODE_DASHBOARD_URL` | No       | `https://instanode.dev`       | Override the dashboard host that `claim_resource` builds claim URLs against. Only set this for staging.                                                                                  |\n\n## Tools\n\n| Tool              | Description                                                                                                                                                       |\n|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `create_postgres` | `POST /db/new` — Provision a Postgres database (pgvector included). Returns `connection_url` + the `note`/`upgrade` claim URL. `name` required.                   |\n| `create_vector`   | `POST /vector/new` — Provision a pgvector-enabled Postgres database (embedding store). Returns `connection_url` + `extension`/`dimensions` + `note`/`upgrade`. `name` required; optional `dimensions` is a documentation hint. |\n| `create_cache`    | `POST /cache/new` — Provision a Redis cache (ACL-scoped user + namespace). Returns `connection_url` + `note`/`upgrade`. `name` required.                          |\n| `create_nosql`    | `POST /nosql/new` — Provision a MongoDB database (per-resource user + DB-scoped role). Returns `connection_url` + `note`/`upgrade`. `name` required.              |\n| `create_queue`    | `POST /queue/new` — Provision a NATS JetStream queue (scoped subject namespace). Returns `connection_url` + `note`/`upgrade`. `name` required.                    |\n| `create_storage`  | `POST /storage/new` — Provision an S3-compatible bucket prefix (DigitalOcean Spaces). Returns endpoint, access keys, prefix + `note`/`upgrade`. `name` required.  |\n| `create_webhook`  | `POST /webhook/new` — Provision an inbound webhook receiver URL. Returns `receive_url` + `note`/`upgrade`. `name` required.                                       |\n| `create_deploy`   | `POST /deploy/new` — Upload a base64 gzip tarball (with Dockerfile) and deploy a container. Returns `deploy_id`, `status`, `url`, `build_logs_url`. `name` required. Pass `redeploy: true` (with the SAME `name`) to update an existing deployment IN PLACE (same app_id + URL). Requires `INSTANODE_TOKEN`. |\n| `create_stack`    | `POST /stacks/new` — Multi-service bundle. Upload an `instant.yaml` manifest plus one base64 gzip tarball per service; returns `stack_id`, per-service URLs, and the 6h-TTL claim block on the anonymous tier. **Anonymous-friendly** (the wedge). `name`, `manifest`, `service_tarballs` required. |\n| `get_stack`       | `GET /stacks/{stack_id}` — Poll a stack's per-service status + URLs. Anonymous-friendly. `stack_id` required.                                                     |\n| `list_deployments`| `GET /api/v1/deployments` — List all deployments on the caller's team. Requires `INSTANODE_TOKEN`.                                                                |\n| `get_deployment`  | `GET /api/v1/deployments/:id` — Fetch one deployment (poll until `status=\"running\"`). Requires `INSTANODE_TOKEN`.                                                 |\n| `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`. |\n| `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`. |\n| `delete_deployment` | `DELETE /deploy/:id` — Tear down a running deployment. Irreversible. Requires `INSTANODE_TOKEN`.                                                                |\n| `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.                    |\n| `claim_token`     | `POST /claim` — Programmatic claim: attach an anonymous resource to the authenticated account using its `upgrade_jwt` + `email`. No auth required.                |\n| `list_resources`  | `GET /api/v1/resources` — List resources on the caller's account. Requires `INSTANODE_TOKEN`.                                                                     |\n| `delete_resource` | `DELETE /api/v1/resources/{token}` — Hard-delete a resource you own. Paid tier only. Requires `INSTANODE_TOKEN`.                                                  |\n| `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). |\n| `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). |\n| `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`. |\n| `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`. |\n| `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`. |\n| `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`. |\n| `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). |\n| `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`. |\n| `resume_resource` | `POST /api/v1/resources/{id}/resume` — Un-pause a resource (same connection URL keeps working). **Pro tier or higher.** Requires `INSTANODE_TOKEN`. |\n| `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`. |\n| `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`. |\n\n### Container deployment (`create_deploy`)\n\nDeploying is a single multipart/form-data POST with a base64-encoded gzip\ntarball of the project (Dockerfile + source). The MCP tool handles the\nencoding plumbing; the agent's job is just to construct the tarball.\n\n**Building the tarball (any language):**\n\n```python\nimport base64, subprocess\ntar = subprocess.check_output([\"tar\", \"czf\", \"-\", \"-C\", project_dir, \".\"])\ntarball_base64 = base64.b64encode(tar).decode()\n```\n\n```js\nimport { execFileSync } from \"node:child_process\";\nconst tar = execFileSync(\"tar\", [\"czf\", \"-\", \"-C\", projectDir, \".\"]);\nconst tarball_base64 = tar.toString(\"base64\");\n```\n\nCap: 50 MB after decode. Honor `.dockerignore` — only ship what\n`docker build` needs. The `name` field is required (1–64 chars,\nletters/numbers/spaces/dashes) — it's the human-readable label shown\non the dashboard.\n\n**Binding provisioned resources:**\n\nProvision the resources first with `create_postgres` / `create_cache` / etc.\nto get their tokens (UUIDs), then pass the tokens as `resource_bindings`:\n\n```json\n{\n  \"tarball_base64\": \"...\",\n  \"name\": \"my-app\",\n  \"port\": 8080,\n  \"resource_bindings\": {\n    \"DATABASE_URL\": \"<token from create_postgres>\",\n    \"REDIS_URL\":    \"<token from create_cache>\"\n  }\n}\n```\n\nThe agent passes **resource tokens** (not connection URLs); the API\nresolves each token to its connection URL server-side at deploy time. The\nMCP server never pre-resolves tokens — pre-resolving would round-trip every\nbinding through `GET /credentials` and embed raw secrets into the tool\nparams, which the agent host may log.\n\n**Polling:**\n\n`create_deploy` returns `status=\"building\"` immediately. Poll\n`get_deployment({ id: deploy_id })` every few seconds until status flips to\n`\"running\"` (typical: ~30s). At that point the `url` field is the live URL.\n\n### Updating an existing deployment (same URL, new build)\n\nTo ship v2 of an app you already deployed without changing the URL or\n`app_id`, call `create_deploy` again with the **same `name`** plus\n`redeploy: true`:\n\n```json\n{\n  \"tarball_base64\": \"...\",\n  \"name\": \"my-app\",\n  \"redeploy\": true\n}\n```\n\nThe api finds the existing deployment by `(team_id, name)` and updates it\nin place — same `app_id`, same `*.deployment.instanode.dev` URL, status\nflips back to `building` while the new image rolls out.\n\nWithout `redeploy: true`, calling `create_deploy` with a name you've used\nbefore mints a **new** `app_id` and a **new** URL (the legacy behaviour).\nThis is the trap that caused the AGENT-UX issue where agents ended up\nwith two live deployments + two URLs for the same app.\n\nThe standalone `redeploy` tool (by `id`, not `name`) still works and also\nrequires a `tarball_base64` — the api never reuses the original tarball.\nPrefer the `create_deploy({ name, redeploy: true })` path when you have\nthe name; use `redeploy({ id, tarball_base64 })` when you only have the\ndeploy id.\n\n### Private deploys\n\nSet `private: true` and pass `allowed_ips` to restrict access to specific IPs\nor CIDR blocks at the Ingress. Useful when the agent is asked to deploy a\nCRM, internal dashboard, or staging app that should only be reachable by the\nuser.\n\n**Pro tier or higher required.** Hobby callers will see HTTP 402 with an\n`agent_action` field — the MCP server surfaces the upgrade URL so the agent\ncan prompt the user to upgrade.\n\n**Example prompt** (paste into Claude Code):\n\n> \"Deploy my CRM as a private app, only accessible from 1.2.3.4 and my office\n> subnet 10.0.0.0/8\"\n\nThe agent will then call:\n\n```json\n{\n  \"tarball_base64\": \"...\",\n  \"name\": \"my-crm\",\n  \"private\": true,\n  \"allowed_ips\": [\"1.2.3.4\", \"10.0.0.0/8\"]\n}\n```\n\n`get_deployment` and `list_deployments` surface `private` + `allowed_ips`\nback to the agent so it can confirm the policy to the user. To turn a\nprivate deploy public, redeploy without the flags.\n\n### How anonymous → claimed works\n\nEvery `create_*` tool returns three fields the agent should treat as\nload-bearing:\n\n- `token` — the resource UUID (used for `claim_token` and `delete_resource`).\n- `note` — a one-sentence human-readable CTA, already mentions the upgrade URL.\n- `upgrade` — the full claim URL (`https://instanode.dev/start?t=<jwt>`). The\n  user clicks it, signs in with GitHub/Google or a magic link, and the\n  resource is attached to their account.\n\n`upgrade_jwt` is also returned for callers that want to build their own UI\naround the claim flow. The `claim_resource` tool accepts that JWT and\nreturns the same dashboard URL — useful if the agent wants to re-surface the\nclaim URL later in the conversation after the original response has scrolled\nout of context.\n\n## Example agent interactions\n\n### 1. \"I need a Postgres for this project\"\n\n> **You:** Claude, I need a Postgres database for this project.\n>\n> **Claude:** *calls* `create_postgres({ name: \"my-side-project\" })`\n>\n> Returns a `connection_url` like `postgres://usr_a1b2:...@pg.instanode.dev:5432/db_a1b2?sslmode=require`,\n> plus `note: \"Works for 24h free. Claim to keep — from $9/mo: https://instanode.dev/start?t=...\"`.\n>\n> **Claude then:** writes `DATABASE_URL=...` to `.env`, adds `.env` to\n> `.gitignore`, runs the migrations, **and shows the user the claim URL\n> verbatim** so they know how to keep the database past 24h.\n\n### 2. \"Spin up a Redis cache for rate limiting\"\n\n> **You:** Add a Redis cache so I can rate-limit my API.\n>\n> **Claude:** *calls* `create_cache({ name: \"api-ratelimit\" })`\n>\n> Returns a `connection_url` like `redis://usr_b2c3:...@redis.instanode.dev:6379/0`.\n\n### 3. \"Set up a webhook to catch Stripe events\"\n\n> **You:** Give me a webhook URL I can point Stripe at.\n>\n> **Claude:** *calls* `create_webhook({ name: \"stripe-sandbox\" })`\n>\n> Returns a `receive_url` that captures every request. `curl $receive_url`\n> pulls back the stored log.\n\n### 4. \"Object storage for user uploads\"\n\n> **You:** I need S3-compatible storage for uploaded avatars.\n>\n> **Claude:** *calls* `create_storage({ name: \"user-avatars\" })`\n>\n> Returns endpoint, access key, secret key, and prefix. Claude wires the\n> AWS SDK with the returned credentials.\n\n### 5. \"Make last night's database permanent\"\n\n> **You:** I want to keep the database you made yesterday past 24h.\n>\n> **Claude (no INSTANODE_TOKEN):** *calls*\n> `claim_resource({ upgrade_jwt: \"<the upgrade_jwt from yesterday's response>\" })`\n> → shows you the dashboard claim URL. You click it, sign in, the resource\n> is attached.\n>\n> **Claude (with INSTANODE_TOKEN):** *calls*\n> `claim_token({ token: \"a1b2c3d4-...\" })` → resource is now linked to the\n> authenticated account, no browser round-trip needed.\n\n## Authentication\n\nThe anonymous tier works without any setup. To unlock paid limits, permanent\nresources, and the account-management tools (`list_resources`,\n`delete_resource`, `claim_token`, `get_api_token`):\n\n1. Sign up at <https://instanode.dev> with GitHub.\n2. Visit the dashboard and copy your bearer token.\n3. Set it as `INSTANODE_TOKEN` in the MCP server's `env` block (see examples\n   above).\n\nRotate any time by calling `get_api_token`, which mints a fresh Personal Access Token via `POST /api/v1/auth/api-keys`. PATs are revocation-based (not time-bound). NOTE: PATs cannot mint other PATs — `get_api_token` requires a user-session token (sign in via the dashboard), not an existing PAT, otherwise the API returns 403.\n\n## Development\n\n```bash\nnpm install\nnpm run build\n# Integration test (optional — requires a running instanode.dev server.\n# For local k8s, port-forward first: kubectl port-forward -n instant svc/instant-api 8080:8080):\nINSTANODE_API_URL=http://localhost:8080 npm test\n```\n\n## License\n\nMIT — (c) instanode.dev\n",
  "bytes": 19376,
  "sha": "bb27fbb1270f15ad5ad100a6ed7a885c6e5645c5d51e18ba7448f0a9353fdec2",
  "repo_slug": "instanode-dev/mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_instanode_dev_mcp_e4de7dd4/readme"
}