Skip to content

Commit 7fca698

Browse files
fix(mcp): correct stale tier numbers in create_cache/nosql/deploy descriptions (#38)
Three agent-facing tool descriptions carried tier limits that the strict-≥80%-margin redesign (common/api, 2026-06-05) made factually wrong. Agents read these verbatim to advise users, so each was a live mis-statement. - create_cache: "team unlimited" → "team 1536 MB". The redesign retired every "unlimited" (-1) limit; Team's redis cap is a finite 1536 MB (plans.yaml). - create_nosql: "pro 2 GB / team unlimited" → "hobby 100 MB / hobby_plus 1 GB / pro 5 GB / growth 20 GB / team 40 GB". Pro was wrong (mongodb_storage_mb pro = 5120 MB = 5 GB, not 2 GB) and Team is now a finite 40 GB. Also fills in the previously-omitted hobby_plus/growth rows. - create_deploy: the opening line said "Requires Pro tier or higher", conflating the base-deploy gate with the PRIVATE-deploy gate. Hobby CAN deploy (deployments_apps: hobby=1). The fix states the base gate is Hobby+ (with the per-tier app counts) and reserves the Pro requirement for private deploys. Adds registry-honesty regression guards mirroring the existing FINDING-12 create_cache test: each asserts the live finite number AND that the word "unlimited" never reappears in the cache/nosql descriptions, plus a deploy guard that Hobby is named as deploy-capable. All 393 tests pass; coverage 99.86% line / 96.13% branch (≥95% floor); patch lines covered by the new tests. Rule 22 surface: api/plans.yaml + common/plans defaultYAML already shipped the strict-80 numbers; this syncs the MCP agent-facing surface to match. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 42f5313 commit 7fca698

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ Drop in as REDIS_URL with any Redis client (ioredis, node-redis, go-redis, etc.)
443443
Without INSTANODE_TOKEN: anonymous tier — 5 MB, 24h TTL. The response carries
444444
'note' + 'upgrade' (claim URL) — surface both verbatim.
445445
With INSTANODE_TOKEN (paid): hobby 50 MB / hobby_plus 50 MB / pro 512 MB /
446-
growth 1024 MB / team unlimited (per api/plans.yaml), permanent.
446+
growth 1024 MB / team 1536 MB (per api/plans.yaml), permanent.
447447
448448
Cleanup: anonymous resources auto-expire after 24h — there is no on-demand
449449
delete for anonymous tokens, by design. On a paid tier, call
@@ -487,7 +487,8 @@ mongodb driver (mongoose, pymongo, etc.).
487487
488488
Without INSTANODE_TOKEN: anonymous tier — 5 MB, 2 connections, 24h TTL.
489489
'note' + 'upgrade' fields in the response surface the claim URL.
490-
With INSTANODE_TOKEN (paid): hobby 100 MB / pro 2 GB / team unlimited, permanent.
490+
With INSTANODE_TOKEN (paid): hobby 100 MB / hobby_plus 1 GB / pro 5 GB /
491+
growth 20 GB / team 40 GB (per api/plans.yaml), permanent.
491492
492493
Cleanup: anonymous resources auto-expire after 24h — there is no on-demand
493494
delete for anonymous tokens, by design. On a paid tier, call
@@ -966,7 +967,7 @@ agent can route the user to the dashboard instead of guessing.`,
966967

967968
server.tool(
968969
"create_deploy",
969-
`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.
970+
`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.
970971
971972
Deploys a containerized application on instanode.dev (POST /deploy/new).
972973

test/integration.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,74 @@ describe("instanode-mcp integration suite", () => {
15051505
await close();
15061506
}
15071507
});
1508+
1509+
// BUGHUNT-iter2: the strict-≥80%-margin tier redesign (2026-06-05) retired
1510+
// every "unlimited" (-1) limit. Team's redis cap is now a finite 1536 MB
1511+
// (api/plans.yaml). The description used to say "team unlimited" — a stale
1512+
// claim an agent would relay to a user as a false promise. Guard the
1513+
// honest finite number AND assert the word "unlimited" never reappears.
1514+
it("create_cache description shows team's finite 1536 MB cap, never 'unlimited'", async () => {
1515+
const { client, close } = await connectClient(mock.url, "none");
1516+
try {
1517+
const { tools } = await client.listTools();
1518+
const cache = tools.find((t) => t.name === "create_cache")!;
1519+
const desc = cache.description ?? "";
1520+
assert.match(desc, /team 1536 MB/i, "expected the finite team cap 'team 1536 MB' in create_cache description");
1521+
assert.doesNotMatch(desc, /unlimited/i, "create_cache description must not advertise an 'unlimited' tier (strict-80 redesign retired all -1 limits)");
1522+
} finally {
1523+
await close();
1524+
}
1525+
});
1526+
});
1527+
1528+
// ── BUGHUNT-iter2: nosql + deploy description honesty ───────────────────────
1529+
1530+
describe("BUGHUNT-iter2 — create_nosql description honesty", () => {
1531+
// Pre-fix the description said "hobby 100 MB / pro 2 GB / team unlimited" —
1532+
// pro was wrong (plans.yaml mongodb_storage_mb pro = 5120 MB = 5 GB, not 2 GB)
1533+
// and team is no longer unlimited (strict-80: 40960 MB = 40 GB).
1534+
it("create_nosql description quotes the live plans.yaml mongodb numbers (5 GB pro, finite team)", async () => {
1535+
const { client, close } = await connectClient(mock.url, "none");
1536+
try {
1537+
const { tools } = await client.listTools();
1538+
const nosql = tools.find((t) => t.name === "create_nosql")!;
1539+
const desc = nosql.description ?? "";
1540+
assert.match(desc, /hobby 100 MB/i, "expected hobby 100 MB in create_nosql description");
1541+
assert.match(desc, /pro 5 GB/i, "expected pro 5 GB (5120 MB) in create_nosql description — pre-fix wrongly said 2 GB");
1542+
assert.match(desc, /team 40 GB/i, "expected the finite team cap 'team 40 GB' in create_nosql description");
1543+
assert.doesNotMatch(desc, /unlimited/i, "create_nosql description must not advertise an 'unlimited' tier (strict-80 redesign retired all -1 limits)");
1544+
} finally {
1545+
await close();
1546+
}
1547+
});
1548+
});
1549+
1550+
describe("BUGHUNT-iter2 — create_deploy tier-gate honesty", () => {
1551+
// Pre-fix the opening line said "Requires Pro tier or higher", conflating
1552+
// the base-deploy gate with the PRIVATE-deploy gate. Hobby CAN deploy
1553+
// (plans.yaml deployments_apps: hobby=1). An agent reading the old copy
1554+
// would wrongly refuse a legitimate Hobby deployment. The fix states the
1555+
// base gate is Hobby+ while keeping the Pro requirement for private deploys.
1556+
it("create_deploy description says Hobby can deploy and reserves Pro for private deploys", async () => {
1557+
const { client, close } = await connectClient(mock.url, "none");
1558+
try {
1559+
const { tools } = await client.listTools();
1560+
const deploy = tools.find((t) => t.name === "create_deploy")!;
1561+
const desc = deploy.description ?? "";
1562+
// Base deploy gate is Hobby+, not Pro+.
1563+
assert.match(desc, /Hobby tier or higher/i, "create_deploy must state the base deploy gate is Hobby tier or higher");
1564+
// The Pro mention must survive (private deploys genuinely need Pro+).
1565+
assert.match(desc, /pro tier/i, "create_deploy must still mention the Pro gate (for private deploys)");
1566+
// It must NOT claim Pro is required to deploy at all.
1567+
assert.doesNotMatch(
1568+
desc,
1569+
/restrict access to specific IPs\. Requires Pro tier or higher\./i,
1570+
"create_deploy must not claim a blanket 'Requires Pro tier or higher' base gate — Hobby can deploy",
1571+
);
1572+
} finally {
1573+
await close();
1574+
}
1575+
});
15081576
});
15091577
});
15101578

0 commit comments

Comments
 (0)