Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ Drop in as REDIS_URL with any Redis client (ioredis, node-redis, go-redis, etc.)
Without INSTANODE_TOKEN: anonymous tier — 5 MB, 24h TTL. The response carries
'note' + 'upgrade' (claim URL) — surface both verbatim.
With INSTANODE_TOKEN (paid): hobby 50 MB / hobby_plus 50 MB / pro 512 MB /
growth 1024 MB / team unlimited (per api/plans.yaml), permanent.
growth 1024 MB / team 1536 MB (per api/plans.yaml), permanent.

Cleanup: anonymous resources auto-expire after 24h — there is no on-demand
delete for anonymous tokens, by design. On a paid tier, call
Expand Down Expand Up @@ -487,7 +487,8 @@ mongodb driver (mongoose, pymongo, etc.).

Without INSTANODE_TOKEN: anonymous tier — 5 MB, 2 connections, 24h TTL.
'note' + 'upgrade' fields in the response surface the claim URL.
With INSTANODE_TOKEN (paid): hobby 100 MB / pro 2 GB / team unlimited, permanent.
With INSTANODE_TOKEN (paid): hobby 100 MB / hobby_plus 1 GB / pro 5 GB /
growth 20 GB / team 40 GB (per api/plans.yaml), permanent.

Cleanup: anonymous resources auto-expire after 24h — there is no on-demand
delete for anonymous tokens, by design. On a paid tier, call
Expand Down Expand Up @@ -966,7 +967,7 @@ agent can route the user to the dashboard instead of guessing.`,

server.tool(
"create_deploy",
`Create a new deploy — OR set \`redeploy: true\` to update an existing deployment with the same name (preserves app_id + URL). Optionally set \`private: true\` + \`allowed_ips: ['1.2.3.4', '10.0.0.0/8']\` to restrict access to specific IPs. Requires Pro tier or higher. Useful when an agent is asked to deploy a CRM, internal dashboard, or staging app that should only be reachable by the user.
`Create a new deploy — OR set \`redeploy: true\` to update an existing deployment with the same name (preserves app_id + URL). Optionally set \`private: true\` + \`allowed_ips: ['1.2.3.4', '10.0.0.0/8']\` to restrict access to specific IPs. Deploying requires a paid plan: Hobby tier or higher (Hobby = 1 app, Hobby Plus = 2, Pro = 10, Growth = 50, Team = 100 — per api/plans.yaml deployments_apps); anonymous and free tiers cannot deploy and get HTTP 402. The PRIVATE-deploy option (private: true + allowed_ips) additionally requires Pro tier or higher. Useful when an agent is asked to deploy a CRM, internal dashboard, or staging app that should only be reachable by the user.

Deploys a containerized application on instanode.dev (POST /deploy/new).

Expand Down
68 changes: 68 additions & 0 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,74 @@ describe("instanode-mcp integration suite", () => {
await close();
}
});

// BUGHUNT-iter2: the strict-≥80%-margin tier redesign (2026-06-05) retired
// every "unlimited" (-1) limit. Team's redis cap is now a finite 1536 MB
// (api/plans.yaml). The description used to say "team unlimited" — a stale
// claim an agent would relay to a user as a false promise. Guard the
// honest finite number AND assert the word "unlimited" never reappears.
it("create_cache description shows team's finite 1536 MB cap, never 'unlimited'", async () => {
const { client, close } = await connectClient(mock.url, "none");
try {
const { tools } = await client.listTools();
const cache = tools.find((t) => t.name === "create_cache")!;
const desc = cache.description ?? "";
assert.match(desc, /team 1536 MB/i, "expected the finite team cap 'team 1536 MB' in create_cache description");
assert.doesNotMatch(desc, /unlimited/i, "create_cache description must not advertise an 'unlimited' tier (strict-80 redesign retired all -1 limits)");
} finally {
await close();
}
});
});

// ── BUGHUNT-iter2: nosql + deploy description honesty ───────────────────────

describe("BUGHUNT-iter2 — create_nosql description honesty", () => {
// Pre-fix the description said "hobby 100 MB / pro 2 GB / team unlimited" —
// pro was wrong (plans.yaml mongodb_storage_mb pro = 5120 MB = 5 GB, not 2 GB)
// and team is no longer unlimited (strict-80: 40960 MB = 40 GB).
it("create_nosql description quotes the live plans.yaml mongodb numbers (5 GB pro, finite team)", async () => {
const { client, close } = await connectClient(mock.url, "none");
try {
const { tools } = await client.listTools();
const nosql = tools.find((t) => t.name === "create_nosql")!;
const desc = nosql.description ?? "";
assert.match(desc, /hobby 100 MB/i, "expected hobby 100 MB in create_nosql description");
assert.match(desc, /pro 5 GB/i, "expected pro 5 GB (5120 MB) in create_nosql description — pre-fix wrongly said 2 GB");
assert.match(desc, /team 40 GB/i, "expected the finite team cap 'team 40 GB' in create_nosql description");
assert.doesNotMatch(desc, /unlimited/i, "create_nosql description must not advertise an 'unlimited' tier (strict-80 redesign retired all -1 limits)");
} finally {
await close();
}
});
});

describe("BUGHUNT-iter2 — create_deploy tier-gate honesty", () => {
// Pre-fix the opening line said "Requires Pro tier or higher", conflating
// the base-deploy gate with the PRIVATE-deploy gate. Hobby CAN deploy
// (plans.yaml deployments_apps: hobby=1). An agent reading the old copy
// would wrongly refuse a legitimate Hobby deployment. The fix states the
// base gate is Hobby+ while keeping the Pro requirement for private deploys.
it("create_deploy description says Hobby can deploy and reserves Pro for private deploys", async () => {
const { client, close } = await connectClient(mock.url, "none");
try {
const { tools } = await client.listTools();
const deploy = tools.find((t) => t.name === "create_deploy")!;
const desc = deploy.description ?? "";
// Base deploy gate is Hobby+, not Pro+.
assert.match(desc, /Hobby tier or higher/i, "create_deploy must state the base deploy gate is Hobby tier or higher");
// The Pro mention must survive (private deploys genuinely need Pro+).
assert.match(desc, /pro tier/i, "create_deploy must still mention the Pro gate (for private deploys)");
// It must NOT claim Pro is required to deploy at all.
assert.doesNotMatch(
desc,
/restrict access to specific IPs\. Requires Pro tier or higher\./i,
"create_deploy must not claim a blanket 'Requires Pro tier or higher' base gate — Hobby can deploy",
);
} finally {
await close();
}
});
});
});

Expand Down
Loading