Skip to content

fix(mcp): BugBash 2026-05-20 Wave 2 — pin mock to openapi.json + 5 client fixes#10

Closed
mastermanas805 wants to merge 2 commits into
masterfrom
fix/bugbash-2026-05-20-wave2
Closed

fix(mcp): BugBash 2026-05-20 Wave 2 — pin mock to openapi.json + 5 client fixes#10
mastermanas805 wants to merge 2 commits into
masterfrom
fix/bugbash-2026-05-20-wave2

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Resolves the six T17 findings (2 P0s, 4 P1s) flagged in
BUGHUNT-2026-05-20-T17.md. Every fix carries a CI-run regression test under
BugBash 2026-05-20 T17 ... in test/integration.test.ts so a future revert
fails the gate immediately. Branched off origin/master.

Findings fixed

ID Theme Fix Regression test
T17 P0-1 redeploy crashes on real API (expects {ok,item}, API returns bare 202) client.redeploy() returns {ok, id} and tolerates the empty 2xx body; index.ts redeploy handler no longer dereferences result.item.app_id; mock now emits a bare 202 with no body redeploy resolves successfully when the api returns 202 with no body (no TypeError on result.item.app_id)
T17 P0-2 get_api_token 403s because PATs can't mint PATs (the typical INSTANODE_TOKEN is a PAT) formatError maps pat_cannot_mint_pat to a clear "use a session JWT — sign in at https://instanode.dev/dashboard" headline; tool description rewritten to state the constraint plainly; mock models the 403 against a new PAT_TOKEN fixture get_api_token with a PAT bearer surfaces the 'use a session JWT' message (not a generic 403) + get_api_token tool description mentions the session-vs-PAT requirement
T17 P1-1 Name schema regex — min(1).max(64) doesn't mirror the api's ^[A-Za-z0-9][A-Za-z0-9 _-]*$ Added the regex (as a named constant API_NAME_PATTERN) to the shared nameArg AND create_deploy.name; mock now enforces the same pattern with invalid_name 13 tests: 6 rejected names + 5 accepted names on create_postgres, 1 case on create_deploy, plus a registry-iterating coverage test that fails if any future create_* ships without the pattern
T17 P1-3 4 npm audit vulns (1 high fast-uri, 3 moderate) npm audit fix → 0 vulnerabilities CI gate output (npm audit runs in npm install step)
T17 P1-6 User-Agent hardcodes instanode-mcp/0.11.0 in two places Sourced from package.json at module load (PKG_VERSION + USER_AGENT constants); both headers() and authHeaders() read the same const client UA string equals 'instanode-mcp/<package.json version>' (no hardcoded 0.11.0 literal)
T17 P0/P1-5 The integration mock is not faithful to live openapi.json — masks the bugs above Fetched the live spec from https://api.instanode.dev/openapi.json and aligned mock-api.ts shapes: /deploy/{id}/redeploy bare 202; /api/v1/auth/api-keys 403 for PATs / 400 missing name / 400 invalid scope; /claim 200 ClaimResponse {ok, team_id, user_id, session_token, message} (not the legacy 201 direct-claim); provisioning routes enforce the live name regex 6 contract-pinning tests under mock-api contract pinning

Gate output

$ npm install && npm run build && npm test

# install
> found 0 vulnerabilities

# build
> instanode-mcp@0.11.1 build
> tsc
# (tsc clean, 0 errors)

# test
✔ instanode-mcp integration suite (15.3 s)
﹣ live smoke (provision-then-teardown) (0.75 ms) # SKIP
ℹ tests 62
ℹ suites 16
ℹ pass 62
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Previously 39 pass / 0 fail — the 23 new tests all map to BugBash findings
(redeploy bare-202, get_api_token PAT, name regex × 13, registry coverage,
UA from package.json, plus 6 mock contract-pin tests).

Versioning

Bumped 0.11.0 → 0.11.1 in package.json + server.json. The User-Agent
test reads the resolved version dynamically so it stays green across future
bumps.

Not in scope

  • T17 P1-2 (delete_resource description wording) — informational, no
    behavior change needed.
  • T17 P1-4 (claim_token chain documented as walking through
    get_api_token) — needs a product call on whether to remove the chain
    advice or wire a session-issuing flow; left for follow-up.
  • T17 P2/P3 items.

🤖 Generated with Claude Code

mastermanas805 and others added 2 commits May 20, 2026 03:08
Makes MCP-server testing a hard CI gate. Previously `npm test` ran a
shell smoke test (test.sh) against the live api.instanode.dev — not
hermetic, not runnable in CI without a cluster, and easy to skip. An
MCP-server change could silently break the tools agents depend on.

What this adds
--------------
- test/integration.test.ts — drives the REAL built server binary over
  the genuine MCP stdio protocol via the official SDK client. 39 tests
  covering all 16 registered tools: tool registry + every input schema,
  success responses, error envelopes (401/402/403/404/400), the
  multipart create_deploy upload, bearer-token auth (none/valid/bad),
  the full deploy lifecycle (create→get→redeploy→delete), private
  deploys + tier gating, and malformed-input rejection.
- test/mock-api.ts — hermetic in-process http.Server mock of the agent
  API (the https://api.instanode.dev/openapi.json contract). The suite
  runs in CI with zero network, zero cluster, zero secrets. It keeps a
  ledger of every resource/deployment so the cleanup sweep can assert
  nothing leaked.
- test/live-smoke.test.ts — optional, build-flagged (INSTANODE_LIVE_SMOKE=1)
  provision-then-teardown smoke test against a real backend; skipped by
  default. Deletes its resource in a finally block.

Resource cleanup (mandatory)
----------------------------
Every test that creates a paid resource or a deployment tears it down.
The after() hook runs a final sweep that deletes every still-live
deployment + paid resource on the mock, then asserts the deletable
ledger is empty. mock.close() runs in a finally so a failed assertion
can't leave the server open.

CI gate
-------
- package.json `test` script now runs the hermetic suite via
  `node --test` (pretest builds server + tests). Local `npm test` ==
  the CI gate exactly. Legacy shell smoke test moved to `test:smoke`.
- .github/workflows/ci.yml runs `npm test` on every push + PR to
  master/main; a non-zero exit blocks the merge.
- zod added as an explicit dependency (index.ts imports it directly;
  it was only resolving transitively via the MCP SDK).

npm test: 39 passed, 0 failed (live-smoke SKIP), exit 0, fully hermetic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ient fixes

Resolves the six T17 findings (2 P0s, 4 P1s) flagged in BUGHUNT-2026-05-20-T17.md.
Each fix carries a CI-run regression test under
"BugBash 2026-05-20 T17 ..." in test/integration.test.ts so a future revert
fails the gate immediately.

T17 P0-1 — redeploy(): the live api documents POST /deploy/{id}/redeploy as a
bare 202 with no body. The previous client typed it as DeployGetResult and
the index.ts handler dereferenced result.item.app_id, throwing
"TypeError: Cannot read properties of undefined (reading 'app_id')" on every
call against the real api. The hermetic mock fabricated {ok, item} so the
suite green-lit a contract violation. Now:
  - client.redeploy() returns {ok, id} and awaits the empty 2xx body
    without dereferencing anything;
  - index.ts redeploy handler prints "Redeploy accepted for <id>" and
    points the agent at get_deployment for the next status;
  - mock-api.ts emits a bare 202 (Content-Length: 0, no body) so the
    test path actually exercises the empty-body code.
Regression test: "redeploy resolves successfully when the api returns 202
with no body (no TypeError on result.item.app_id)".

T17 P0-2 — get_api_token PAT-creating-PAT: the api enforces "PATs cannot
mint other PATs" with a 403 + code=pat_cannot_mint_pat. Since the dashboard
and get_api_token itself both mint PATs, the typical INSTANODE_TOKEN IS a
PAT, so the tool 403'd 100% of the time in its documented "rotate as needed"
use case. The previous generic 403 error gave the agent no path forward.
Now formatError() maps the code to a clear "use a session JWT — sign in at
https://instanode.dev/dashboard, then create a key from the API token UI"
headline, and the tool description explicitly states PATs cannot mint PATs.
The mock now models a PAT_TOKEN fixture that 403s with the canonical code.
Regression tests:
  - "get_api_token with a PAT bearer surfaces the 'use a session JWT'
    message (not a generic 403)";
  - "get_api_token tool description mentions the session-vs-PAT requirement".

T17 P1-1 — name schema regex: the api enforces
^[A-Za-z0-9][A-Za-z0-9 _-]*$ (start-alnum then letters/digits/spaces/
underscores/hyphens). The previous zod schema was min(1).max(64) only —
names like "-bad" and "@x" passed locally and 400'd on the api. The shared
nameArg and create_deploy.name now both carry the regex, sourced as a
named constant API_NAME_PATTERN (matches the MEMORY rule "use named
constants, not inline strings"). The mock's name validator also enforces
the regex so a single-site fallacy in either layer fails the suite.
Regression tests: 11 cases (6 rejected + 5 accepted) per create_postgres,
1 case on create_deploy, plus a registry-iterating coverage test that fails
if any future create_* tool ships without the pattern.

T17 P1-3 — npm audit clean: 4 vulns (1 high fast-uri, 3 moderate hono +
ip-address + express-rate-limit) → 0 vulns after npm audit fix.
The CI gate "npm install && npm run build && npm test" now reports
"found 0 vulnerabilities".

T17 P1-6 — User-Agent sourced from package.json: client.ts no longer
contains the hardcoded literal "instanode-mcp/0.11.0" in two places.
A single PKG_VERSION + USER_AGENT constant is resolved at module load
from the installed package.json — every release naturally rolls forward.
Regression test: "client UA string equals 'instanode-mcp/<package.json
version>' (no hardcoded 0.11.0 literal)" reads the real package.json,
spawns the mcp against a UA-capturing http.Server, and asserts every
inbound request carries the resolved UA.

T17 P0/P1-5 — mock pinned to openapi.json: pulled the live spec from
https://api.instanode.dev/openapi.json and aligned mock-api.ts's
response shapes to it. Per-endpoint contract-pinning tests under
"BugBash 2026-05-20 T17 P0/P1 — mock-api contract pinning" lock down:
  - /deploy/:id/redeploy → bare 202, empty body
  - /api/v1/auth/api-keys → 403 pat_cannot_mint_pat for PATs
  - /api/v1/auth/api-keys → 400 on missing name, 400 on invalid scope
  - /claim → 200 ClaimResponse {ok, team_id, user_id, session_token,
    message}, NOT the legacy 201 direct-claim shape
  - /db/new (and siblings) → 400 invalid_name on names that fail the
    api regex

Other:
  - Bumped version 0.11.0 → 0.11.1 (the changes above are non-breaking
    for the tool surface but reshape internals).
  - Updated server.json version to match.
  - Added PAT_TOKEN fixture export to mock-api.ts so future PAT-specific
    tests can reuse it.

Gate output (npm install && npm run build && npm test):
  - npm install: 0 vulnerabilities
  - npm run build: tsc clean, 0 errors
  - npm test: 62 pass / 0 fail / 0 skip (live-smoke SKIP by design)
  - Previously: 39 pass — the 23 new tests all map to BugBash findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805
Copy link
Copy Markdown
Member Author

Closing as superseded. This PR's fixes (T17 P0-1 redeploy crash, P0-2 PAT clarity, P1 name regex, P1 npm audit, P1 User-Agent) were re-landed on master directly via PR #12 (merged 2026-05-21) since this PR had been sitting open unmerged. The wave3 P2 fixes (tarball cap, allowed_ips invariant) from PR #11 are also on master. Nothing here is missing from master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants