Skip to content

Commit c1b8ead

Browse files
mcp: v0.8.0 — expand to all 6 resource types + claim_resource helper (#1)
The MCP server is the agent's primary entry point into instanode — it needs to expose every resource type and surface the response's claim CTA so agents can convert users into paying customers. Tools added (6 → 11): - create_cache POST /cache/new (Redis) - create_nosql POST /nosql/new (MongoDB) - create_queue POST /queue/new (NATS JetStream) - create_storage POST /storage/new (S3-compatible / DO Spaces) - claim_resource pure local helper — turns an upgrade JWT into https://instanode.dev/start?t=<jwt> Every create_* tool now surfaces the response's note + upgrade fields via appendUpgradeBlock() so the agent literally sees the claim CTA and URL in the tool response. Files: src/client.ts — 4 new endpoint methods + ProvisionResultBase typing src/index.ts — 5 new tools registered + shared helpers package.json — version 0.7.3 → 0.8.0, keywords expanded README.md — tools table rewritten, claim flow explained test.sh — expected-tools set updated to 11 Test gate: npm run build clean, npm test 6/6 pass against the live API at https://api.instanode.dev. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c074e1e commit c1b8ead

5 files changed

Lines changed: 575 additions & 128 deletions

File tree

README.md

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
# @instanode/mcp
22

33
MCP server for [instanode.dev](https://instanode.dev). Lets AI coding agents
4-
(Claude Code, Cursor, Windsurf, Continue, etc.) provision ephemeral Postgres
5-
databases and webhook receivers over HTTPS — no Docker, no signup required
6-
for the free tier.
7-
8-
- One tool call → one `postgres://` URL, usable immediately as `DATABASE_URL`.
9-
- pgvector is pre-installed on every database.
10-
- Free tier: 10 MB / 2 conn / 24h TTL. Paid (optional): 500 MB / 5 conn /
11-
permanent, unlocked by setting `INSTANODE_TOKEN`.
4+
(Claude Code, Cursor, Windsurf, Continue, etc.) provision the full bundle of
5+
ephemeral developer infrastructure over HTTPS — no Docker, no signup required
6+
for the free anonymous tier.
7+
8+
One tool call per resource type, each returning a drop-in connection string:
9+
10+
- **Postgres** (`create_postgres`) → `postgres://...` with pgvector pre-installed
11+
- **Redis** (`create_cache`) → `redis://...` with ACL-scoped user + namespace
12+
- **MongoDB** (`create_nosql`) → `mongodb://...` with role scoped to the DB
13+
- **NATS JetStream** (`create_queue`) → `nats://...` with scoped subject namespace
14+
- **S3-compatible storage** (`create_storage`) → endpoint + keys + prefix
15+
(backed by DigitalOcean Spaces)
16+
- **Webhook receiver** (`create_webhook`) → public URL that stores every
17+
inbound request
18+
19+
Every anonymous resource auto-expires in 24h. The provision response carries
20+
a `note` and `upgrade` field — the MCP server surfaces both verbatim so the
21+
agent can show the user the exact CTA + claim URL needed to keep the
22+
resource permanently. Run `claim_resource` on the returned `upgrade_jwt` to
23+
get the dashboard claim URL.
1224

1325
## Install
1426

@@ -18,7 +30,7 @@ for the free tier.
1830
claude mcp add instanode -- npx -y @instanode/mcp@latest
1931
```
2032

21-
To authenticate (unlock paid tier, `list_resources`, `delete_resource`):
33+
To authenticate (unlock paid-tier limits and the account-management tools):
2234

2335
```bash
2436
claude mcp add instanode \
@@ -80,21 +92,44 @@ to reach for this MCP, see <https://instanode.dev/agent.html>.
8092

8193
## Environment
8294

83-
| Variable | Required | Default | Purpose |
84-
|---------------------|----------|--------------------------------|-------------------------------------------------------------------------------------------------------------|
85-
| `INSTANODE_TOKEN` | No | — | Bearer JWT minted at <https://instanode.dev/dashboard>. Required for `list_resources`, `claim_token`, `delete_resource`, `get_api_token`. Unlocks paid-tier limits on `create_*`. |
86-
| `INSTANODE_API_URL` | No | `https://api.instanode.dev` | Override the API base URL. Only set this for local development. |
95+
| Variable | Required | Default | Purpose |
96+
|---------------------------|----------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
97+
| `INSTANODE_TOKEN` | No | — | Bearer JWT minted at <https://instanode.dev/dashboard>. Required for `list_resources`, `claim_token`, `delete_resource`, and `get_api_token`. Unlocks paid-tier limits on every `create_*`. |
98+
| `INSTANODE_API_URL` | No | `https://api.instanode.dev` | Override the API base URL. Only set this for local development against a k3s cluster. |
99+
| `INSTANODE_DASHBOARD_URL` | No | `https://instanode.dev` | Override the dashboard host that `claim_resource` builds claim URLs against. Only set this for staging. |
87100

88101
## Tools
89102

90-
| Tool | Description |
91-
|-------------------|---------------------------------------------------------------------------------------------------|
92-
| `create_postgres` | Provision a Postgres database (pgvector included). Returns a `postgres://` URL. `name` required. |
93-
| `create_webhook` | Provision an inbound webhook receiver URL. `name` required. |
94-
| `list_resources` | List resources on the caller's account. Requires `INSTANODE_TOKEN`. |
95-
| `claim_token` | Attach an anonymous token to the authenticated account. Requires `INSTANODE_TOKEN`. |
96-
| `delete_resource` | Hard-delete a resource you own. Paid tier only. Requires `INSTANODE_TOKEN`. |
97-
| `get_api_token` | Mint a fresh 30-day bearer JWT (for rotation). Requires an existing `INSTANODE_TOKEN`. |
103+
| Tool | Description |
104+
|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
105+
| `create_postgres` | `POST /db/new` — Provision a Postgres database (pgvector included). Returns `connection_url` + the `note`/`upgrade` claim URL. `name` required. |
106+
| `create_cache` | `POST /cache/new` — Provision a Redis cache (ACL-scoped user + namespace). Returns `connection_url` + `note`/`upgrade`. `name` required. |
107+
| `create_nosql` | `POST /nosql/new` — Provision a MongoDB database (per-resource user + DB-scoped role). Returns `connection_url` + `note`/`upgrade`. `name` required. |
108+
| `create_queue` | `POST /queue/new` — Provision a NATS JetStream queue (scoped subject namespace). Returns `connection_url` + `note`/`upgrade`. `name` required. |
109+
| `create_storage` | `POST /storage/new` — Provision an S3-compatible bucket prefix (DigitalOcean Spaces). Returns endpoint, access keys, prefix + `note`/`upgrade`. `name` required. |
110+
| `create_webhook` | `POST /webhook/new` — Provision an inbound webhook receiver URL. Returns `receive_url` + `note`/`upgrade`. `name` required. |
111+
| `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. |
112+
| `claim_token` | `POST /api/me/claim` — Programmatic claim: attach an anonymous resource to the authenticated account by its UUID `token`. Requires `INSTANODE_TOKEN`. |
113+
| `list_resources` | `GET /api/me/resources` — List resources on the caller's account. Requires `INSTANODE_TOKEN`. |
114+
| `delete_resource` | `DELETE /api/me/resources/{token}` — Hard-delete a resource you own. Paid tier only. Requires `INSTANODE_TOKEN`. |
115+
| `get_api_token` | `GET /api/me/token` — Mint a fresh 30-day bearer JWT (for rotation). Requires an existing `INSTANODE_TOKEN`. |
116+
117+
### How anonymous → claimed works
118+
119+
Every `create_*` tool returns three fields the agent should treat as
120+
load-bearing:
121+
122+
- `token` — the resource UUID (used for `claim_token` and `delete_resource`).
123+
- `note` — a one-sentence human-readable CTA, already mentions the upgrade URL.
124+
- `upgrade` — the full claim URL (`https://instanode.dev/start?t=<jwt>`). The
125+
user clicks it, signs in with GitHub/Google or a magic link, and the
126+
resource is attached to their account.
127+
128+
`upgrade_jwt` is also returned for callers that want to build their own UI
129+
around the claim flow. The `claim_resource` tool accepts that JWT and
130+
returns the same dashboard URL — useful if the agent wants to re-surface the
131+
claim URL later in the conversation after the original response has scrolled
132+
out of context.
98133

99134
## Example agent interactions
100135

@@ -104,12 +139,22 @@ to reach for this MCP, see <https://instanode.dev/agent.html>.
104139
>
105140
> **Claude:** *calls* `create_postgres({ name: "my-side-project" })`
106141
>
107-
> Returns a `connection_url` like `postgres://usr_a1b2:...@pg.instanode.dev:5432/db_a1b2?sslmode=require`.
142+
> Returns a `connection_url` like `postgres://usr_a1b2:...@pg.instanode.dev:5432/db_a1b2?sslmode=require`,
143+
> plus `note: "Works for 24h free. Claim to keep — from $9/mo: https://instanode.dev/start?t=..."`.
108144
>
109145
> **Claude then:** writes `DATABASE_URL=...` to `.env`, adds `.env` to
110-
> `.gitignore`, runs your migrations.
146+
> `.gitignore`, runs the migrations, **and shows the user the claim URL
147+
> verbatim** so they know how to keep the database past 24h.
148+
149+
### 2. "Spin up a Redis cache for rate limiting"
150+
151+
> **You:** Add a Redis cache so I can rate-limit my API.
152+
>
153+
> **Claude:** *calls* `create_cache({ name: "api-ratelimit" })`
154+
>
155+
> Returns a `connection_url` like `redis://usr_b2c3:...@redis.instanode.dev:6379/0`.
111156

112-
### 2. "Set up a webhook to catch Stripe events"
157+
### 3. "Set up a webhook to catch Stripe events"
113158

114159
> **You:** Give me a webhook URL I can point Stripe at.
115160
>
@@ -118,18 +163,33 @@ to reach for this MCP, see <https://instanode.dev/agent.html>.
118163
> Returns a `receive_url` that captures every request. `curl $receive_url`
119164
> pulls back the stored log.
120165

121-
### 3. "Make last night's database permanent"
166+
### 4. "Object storage for user uploads"
167+
168+
> **You:** I need S3-compatible storage for uploaded avatars.
169+
>
170+
> **Claude:** *calls* `create_storage({ name: "user-avatars" })`
171+
>
172+
> Returns endpoint, access key, secret key, and prefix. Claude wires the
173+
> AWS SDK with the returned credentials.
174+
175+
### 5. "Make last night's database permanent"
122176

123177
> **You:** I want to keep the database you made yesterday past 24h.
124178
>
125-
> **Claude:** *(with `INSTANODE_TOKEN` set)* *calls*
126-
> `claim_token({ token: "a1b2c3d4-..." })` → resource is now linked to your
127-
> account with `tier=paid` and no expiry.
179+
> **Claude (no INSTANODE_TOKEN):** *calls*
180+
> `claim_resource({ upgrade_jwt: "<the upgrade_jwt from yesterday's response>" })`
181+
> → shows you the dashboard claim URL. You click it, sign in, the resource
182+
> is attached.
183+
>
184+
> **Claude (with INSTANODE_TOKEN):** *calls*
185+
> `claim_token({ token: "a1b2c3d4-..." })` → resource is now linked to the
186+
> authenticated account, no browser round-trip needed.
128187

129188
## Authentication
130189

131-
The free tier works without any setup. To unlock permanent resources, paid
132-
limits, and the `list_resources` / `delete_resource` tools:
190+
The anonymous tier works without any setup. To unlock paid limits, permanent
191+
resources, and the account-management tools (`list_resources`,
192+
`delete_resource`, `claim_token`, `get_api_token`):
133193

134194
1. Sign up at <https://instanode.dev> with GitHub.
135195
2. Visit the dashboard and copy your bearer token.
@@ -143,8 +203,9 @@ Rotate any time by calling `get_api_token`, which mints a fresh 30-day JWT.
143203
```bash
144204
npm install
145205
npm run build
146-
# Integration test (optional — requires a running instanode.dev server):
147-
INSTANODE_API_URL=http://localhost:30080 npm test
206+
# Integration test (optional — requires a running instanode.dev server.
207+
# For local k8s, port-forward first: kubectl port-forward -n instant svc/instant-api 8080:8080):
208+
INSTANODE_API_URL=http://localhost:8080 npm test
148209
```
149210

150211
## License

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
{
22
"name": "@instanode/mcp",
3-
"version": "0.7.3",
4-
"description": "MCP server for instanode.dev \u2014 lets AI coding agents provision ephemeral Postgres databases and webhook receivers over HTTPS, with optional bearer-token auth for paid users.",
3+
"version": "0.8.0",
4+
"description": "MCP server for instanode.dev \u2014 lets AI coding agents provision ephemeral Postgres, Redis, MongoDB, NATS queues, S3-compatible object storage, and webhook receivers over HTTPS, with optional bearer-token auth for paid users.",
55
"keywords": [
66
"mcp",
77
"claude",
88
"cursor",
99
"windsurf",
1010
"instanode.dev",
1111
"postgres",
12+
"redis",
13+
"mongodb",
14+
"nats",
15+
"s3",
16+
"object-storage",
17+
"webhook",
1218
"provisioning",
1319
"ai-agent",
1420
"database"

src/client.ts

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@
88
* Bearer auth is read from INSTANODE_TOKEN on every call so the user can
99
* rotate the token without restarting the MCP process. Anonymous callers
1010
* simply leave the env var unset.
11+
*
12+
* Today's API surface (all live):
13+
* POST /db/new — Postgres
14+
* POST /cache/new — Redis
15+
* POST /nosql/new — MongoDB
16+
* POST /queue/new — NATS JetStream
17+
* POST /storage/new — S3-compatible bucket
18+
* POST /webhook/new — Webhook receiver
19+
* GET /api/me/resources, POST /api/me/claim, DELETE /api/me/resources/{token}
20+
* GET /api/me/token
1121
*/
1222

1323
const DEFAULT_BASE_URL = "https://api.instanode.dev";
24+
const DEFAULT_DASHBOARD_URL = "https://instanode.dev";
1425

1526
export interface ClientOptions {
1627
baseURL?: string;
@@ -35,26 +46,60 @@ export interface ProvisionLimits {
3546
[key: string]: unknown;
3647
}
3748

38-
export interface DatabaseProvisionResult {
49+
/**
50+
* Shared fields every /<resource>/new response carries. Some resources have
51+
* type-specific extras (receive_url for webhook, endpoint/access_key_id/etc
52+
* for storage).
53+
*/
54+
export interface ProvisionResultBase {
3955
ok: boolean;
4056
id: string;
4157
token: string;
4258
name?: string;
43-
connection_url: string;
4459
tier: string;
45-
limits: ProvisionLimits;
60+
limits?: ProvisionLimits;
61+
/** Human-readable CTA the agent should surface verbatim. */
4662
note?: string;
63+
/** https://instanode.dev/start?t=<jwt> — the dashboard claim URL. */
64+
upgrade?: string;
65+
/** Raw upgrade JWT (for callers that want to build their own claim URL). */
66+
upgrade_jwt?: string;
67+
expires_at?: string | null;
68+
env?: string;
4769
}
4870

49-
export interface WebhookProvisionResult {
50-
ok: boolean;
51-
id: string;
52-
token: string;
53-
name?: string;
71+
export interface DatabaseProvisionResult extends ProvisionResultBase {
72+
/** postgres://... connection string, drop-in DATABASE_URL. */
73+
connection_url: string;
74+
}
75+
76+
export interface CacheProvisionResult extends ProvisionResultBase {
77+
/** redis://user:pass@host:port — drop-in REDIS_URL. */
78+
connection_url: string;
79+
}
80+
81+
export interface NoSQLProvisionResult extends ProvisionResultBase {
82+
/** mongodb://user:pass@host:port/db — drop-in MONGODB_URI. */
83+
connection_url: string;
84+
}
85+
86+
export interface QueueProvisionResult extends ProvisionResultBase {
87+
/** nats://user:pass@host:port — drop-in NATS_URL. JetStream enabled. */
88+
connection_url: string;
89+
}
90+
91+
export interface StorageProvisionResult extends ProvisionResultBase {
92+
/** Public bucket URL prefix, e.g. https://nyc3.digitaloceanspaces.com/instant-shared/<prefix>/ */
93+
connection_url: string;
94+
endpoint: string;
95+
access_key_id: string;
96+
secret_access_key: string;
97+
prefix: string;
98+
}
99+
100+
export interface WebhookProvisionResult extends ProvisionResultBase {
101+
/** Public URL: POST anything to it, GET it to retrieve the captured log. */
54102
receive_url: string;
55-
tier: string;
56-
limits: ProvisionLimits;
57-
note?: string;
58103
}
59104

60105
export interface ClaimResult {
@@ -120,6 +165,18 @@ export class InstantClient {
120165
).replace(/\/$/, "");
121166
}
122167

168+
/**
169+
* Public dashboard URL — where the agent should direct the user to claim an
170+
* anonymous resource. Reads INSTANODE_DASHBOARD_URL on every call so the user
171+
* can override for staging without restarting the MCP process.
172+
*/
173+
dashboardURL(): string {
174+
return (process.env["INSTANODE_DASHBOARD_URL"] ?? DEFAULT_DASHBOARD_URL).replace(
175+
/\/$/,
176+
""
177+
);
178+
}
179+
123180
/** Read the bearer token fresh from the environment on every call. */
124181
private bearerToken(): string | undefined {
125182
const tok = process.env["INSTANODE_TOKEN"];
@@ -129,7 +186,7 @@ export class InstantClient {
129186
private headers(): Record<string, string> {
130187
const h: Record<string, string> = {
131188
"Content-Type": "application/json",
132-
"User-Agent": "instanode-mcp/0.7.0",
189+
"User-Agent": "instanode-mcp/0.8.0",
133190
};
134191
const tok = this.bearerToken();
135192
if (tok) {
@@ -197,6 +254,26 @@ export class InstantClient {
197254
return this.request<DatabaseProvisionResult>("POST", "/db/new", { name });
198255
}
199256

257+
/** POST /cache/new — provision a Redis cache. `name` is required. */
258+
async createCache(name: string): Promise<CacheProvisionResult> {
259+
return this.request<CacheProvisionResult>("POST", "/cache/new", { name });
260+
}
261+
262+
/** POST /nosql/new — provision a MongoDB database. `name` is required. */
263+
async createNoSQL(name: string): Promise<NoSQLProvisionResult> {
264+
return this.request<NoSQLProvisionResult>("POST", "/nosql/new", { name });
265+
}
266+
267+
/** POST /queue/new — provision a NATS JetStream queue. `name` is required. */
268+
async createQueue(name: string): Promise<QueueProvisionResult> {
269+
return this.request<QueueProvisionResult>("POST", "/queue/new", { name });
270+
}
271+
272+
/** POST /storage/new — provision an S3-compatible object storage bucket prefix. `name` is required. */
273+
async createStorage(name: string): Promise<StorageProvisionResult> {
274+
return this.request<StorageProvisionResult>("POST", "/storage/new", { name });
275+
}
276+
200277
/** POST /webhook/new — provision a webhook receiver. `name` is required. */
201278
async createWebhook(name: string): Promise<WebhookProvisionResult> {
202279
return this.request<WebhookProvisionResult>("POST", "/webhook/new", { name });

0 commit comments

Comments
 (0)