Skip to content

fix(mcp): wave 3 — BugHunt 2026-05-20 T17 P2 fixes#11

Merged
mastermanas805 merged 1 commit into
masterfrom
fix/bugbash-2026-05-20-wave3
May 21, 2026
Merged

fix(mcp): wave 3 — BugHunt 2026-05-20 T17 P2 fixes#11
mastermanas805 merged 1 commit into
masterfrom
fix/bugbash-2026-05-20-wave3

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

Wave 3 of the BugHunt 2026-05-20 sweep — three T17 P2 findings on the MCP
surface. All fixes are local-only (no API contract changes), guarded by
4 new regression tests, build + test green.

Fixes

T17 P2 — client-side 50 MiB tarball cap in createDeploy

Pre-fix, an oversized base64 payload was decoded, uploaded in full via
multipart, and rejected server-side — wasting bandwidth and (depending
on the host) logging multi-MB strings. The cap now fails fast with a
clear error and a "shrink with .dockerignore" hint, before any
network I/O. Constant MAX_TARBALL_BYTES exported from client.ts as
a named source of truth.

T17 P2 — allowed_ipsprivate invariant enforced client-side

The api only consults allowed_ips when private===true; passing
allowed_ips without private silently leaves the deploy publicly
reachable — an agent believes it restricted access but did not. The
MCP now rejects both shapes locally with explicit errors:

  • allowed_ips without private:true → "set private:true to use the
    allowlist OR remove allowed_ips for a public deploy"
  • private:true with empty allowed_ips → "pass at least one IP or
    CIDR, e.g. allowed_ips: ['203.0.113.42/32']"

The create_deploy tool description now documents the coupling.

T17 P2 — anonymous-tier teardown contract documented

Per CLAUDE.md the free surface is throwaway-by-construction: anonymous
resources auto-expire after 24h, on-demand delete is paid-tier only
by design. This isn't a missing feature — it's the platform
contract. Updated:

  • delete_resource description: explicit "paid tier only", the
    contract reason ("the free surface is throwaway-by-construction"),
    and what an agent should do for cleanup (do nothing — the worker
    reaper handles it at 24h)
  • Every create_* tool description (postgres, cache, nosql, queue,
    storage, webhook): a Cleanup: line pointing callers at auto-expire
    (anonymous) or delete_resource (paid)

Tests (4 new regression tests in test.sh)

PASS: create_deploy rejects >50 MiB tarball client-side (T17 P2)
PASS: create_deploy rejects allowed_ips without private:true (T17 P2)
PASS: create_deploy rejects private:true with empty allowed_ips (T17 P2)
PASS: delete_resource description documents anonymous auto-expire (T17 P2)

Gate output

$ npm install && npm run build && npm test
> instanode-mcp@0.11.0 build
> tsc                                   # clean, 0 errors

> instanode-mcp@0.11.0 test
> bash test.sh                          # 16 PASS, 0 FAIL

Files changed

  • src/client.tsMAX_TARBALL_BYTES constant, client-side tarball
    cap + allowed_ips/private invariant in createDeploy().
  • src/index.ts — Tool descriptions updated (6 × create_*,
    delete_resource, create_deploy).
  • test.sh — 4 new T17 P2 regression tests.

NOT merged

Branch lives on fix/bugbash-2026-05-20-wave3 per the task brief —
review-only, no auto-merge.

🤖 Generated with Claude Code

T17 P2 — client-side 50 MiB tarball cap in createDeploy. Pre-fix, an
oversized base64 payload was decoded, uploaded in full via multipart,
and rejected server-side — wasting bandwidth and (depending on the host)
logging multi-MB strings. The cap now fails fast with a clear error and
a "shrink with .dockerignore" hint, BEFORE any network I/O. Constant
MAX_TARBALL_BYTES exported from client.ts as a named source of truth.

T17 P2 — `allowed_ips` ↔ `private` invariant enforced client-side. The
api only consults allowed_ips when private===true; passing allowed_ips
without private silently leaves the deploy publicly reachable (an agent
believes it restricted access but did not). Now the MCP rejects both
shapes locally with explicit errors:
  - allowed_ips without private:true → "set private:true to use the
    allowlist OR remove allowed_ips for a public deploy"
  - private:true with empty allowed_ips → "pass at least one IP or
    CIDR, e.g. allowed_ips: ['203.0.113.42/32']"
Tool description updated to document the coupling.

T17 P2 — anonymous-tier teardown contract documented. Per CLAUDE.md the
free surface is throwaway-by-construction: anonymous resources auto-
expire after 24h, on-demand delete is paid-tier only by design. This
isn't a missing feature — it's the platform contract. Updated:
  - delete_resource description: explicit "paid tier only", the
    contract reason, and what an agent should do for cleanup
  - every create_* tool description: a "Cleanup:" note pointing
    callers at auto-expire (anonymous) or delete_resource (paid)

Tests (test.sh, 4 new regression tests, hermetic against the live api):
  T17 P2: create_deploy rejects >50 MiB tarball client-side
  T17 P2: create_deploy rejects allowed_ips without private:true
  T17 P2: create_deploy rejects private:true with empty allowed_ips
  T17 P2: delete_resource description documents anonymous auto-expire

Build + test all green: 16 PASS, 0 FAIL.

Branch: fix/bugbash-2026-05-20-wave3 (off origin/master).

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@mastermanas805 mastermanas805 force-pushed the fix/bugbash-2026-05-20-wave3 branch from 2087213 to cda95f6 Compare May 21, 2026 16:07
@mastermanas805 mastermanas805 merged commit c2b03dd into master May 21, 2026
1 check passed
mastermanas805 added a commit that referenced this pull request May 21, 2026
…pe, sdk pin, create_vector tool (#13)

Squash-merged via triage. Rebased onto master (after #12 + #11). Added test fixture fixes: EXPECTED_TOOLS 16→17, /vector/new route in mock. Closes B16-F7 (README), F8 (server.json), F9 (SDK pin), F10 (create_vector).
mastermanas805 added a commit that referenced this pull request May 29, 2026
* feat(mcp): add create_stack + get_stack tools (CEO wedge unblock)

Closes the wedge gap surfaced by hello-world.sh QA round 2: an MCP-only
agent could provision postgres/cache/etc one at a time, but had no path
to ship a *bundle* in a single call. /stacks/new on the api is
OptionalAuth — anonymous callers get a 24h-TTL stack with a live URL on
*.deployment.instanode.dev, no card, no dashboard round-trip — but the
MCP exposed no wrapper for it. This PR ships:

* create_stack — POST /stacks/new (multipart): name + manifest YAML +
  one base64 gzip tarball per service. Anonymous-friendly (no
  INSTANODE_TOKEN required). Returns stack_id, per-service URL /
  status, env, expires_in, plus the same upgrade_jwt / claim-URL block
  every create_* surfaces. Per-tarball 50 MiB cap enforced client-side,
  mirroring create_deploy.
* get_stack — GET /stacks/{slug}: anonymous-friendly poll for stack +
  per-service status. Uses the public /stacks/{slug} route (StackResponse
  shape, no auth) — not the dashboard-only /api/v1/stacks/{slug}
  (flatter, requires auth).

Bundled fixes (same file touch, ship-while-we're-here, all from the
CLI-MCP QA backlog):

* FINDING-7 — create_deploy's `env` param description said
  `defaults to "production"`. Per CLAUDE.md convention #11 / mig 026 the
  server default is "development". Description + client.ts typedoc
  updated.
* FINDING-8 — all 7 provisioning tools (create_postgres / vector /
  cache / nosql / queue / storage / webhook) silently dropped the `env`
  param. Now they accept `env` and forward it to /<resource>/new as a
  JSON body field. New `nameAndEnvArg` / `envArg` shared schemas keep
  the tool signatures uniform. Empty-string env stays off the wire so
  the server default still applies.
* FINDING-12 / BIZ FINDING-4 — create_cache description quoted halved
  Redis limits ("hobby 25 MB / pro 256 MB"). Real values per
  api/plans.yaml: hobby 50 MB / hobby_plus 50 MB / pro 512 MB /
  growth 1024 MB / team unlimited. Fixed.

Tests
-----
+39 tests across client-unit, tools-unit, integration. Mock-api gains
/stacks/new multipart route + /stacks/{slug} GET. EXPECTED_TOOLS bumped
17→19. Anonymous-friendly path pinned via a no-INSTANODE_TOKEN test
that asserts the Authorization header never goes out. Per-handler
branch tests cover all three urlPart ternaries (exposed-with-url /
exposed-pending / cluster-internal) and every optional-field render
path for both create_stack and get_stack.

Local: 287/287 pass, 99.83% line / 95.71% branch coverage. diff-cover
100% patch coverage on the 420 changed src/ lines.

Live verify (rule-13, partial)
------------------------------
MCP server spawned (node dist/index.js), pointed at port-forwarded
api.instanode.dev (used ONLY to bypass the anonymous /24 daily cap which
prior QA depleted — request shape identical). tools/list returns 19
tools incl. create_stack + get_stack. tools/call create_stack succeeded
HTTP 202 with stack_id stk-2a0cbb2e at the anonymous tier, env defaulted
to "development". Kaniko image build completed (~33s).

NOT verified: the final *.deployment.instanode.dev URL. Build phase
succeeded; deploy phase failed at a pre-existing api/k8s RBAC gap
(`system:serviceaccount:instant:instant-api` cannot update namespaces it
just created — see api/internal/handlers/stack.go:366). This same RBAC
gap blocks every /deploy/new + /stacks/new anonymous flow today and is
out of scope of this MCP PR. Follow-up filed for the api repo.

Evidence: /tmp/qa-session/CLI/hello-world-success.log

Surface checklist (rule 22)
---------------------------
- mcp/package.json ............... 0.11.1 → 0.12.0 (new tool surface)
- mcp/README.md .................. create_stack/get_stack rows added
- mcp/src/index.ts + client.ts ... new tool + client method + types
- mcp/test/ ...................... mock, unit, integration coverage
- api/plans.yaml ................. no change (limits already correct;
                                   only the MCP description drifted)
- content/llms.txt ............... follow-up — add create_stack +
                                   get_stack rows in a separate PR
                                   on the content repo
- /Users/manassrivastava/Documents/InstaNode/CLAUDE.md
                                  no change — wedge tools live in the
                                  mcp repo's surface, not CLAUDE.md

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

* test(mock-api): bound multipart boundary regex to silence CodeQL polynomial-redos

CodeQL flagged /boundary=(.+)$/ on the parseMultipart helper (pre-existing
on master; only flagged on this PR because the file shifted). The regex
runs on a Content-Type header — controlled by tests today, but bounding
the capture group to [^;\s]{1,200} ensures it can't backtrack on an
adversarial input. Same semantics for every real multipart boundary token
(RFC 7578 caps them at 70 chars).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant