feat(mcp): add create_stack + get_stack tools (CEO wedge unblock)#30
Merged
Conversation
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>
…nomial-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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the wedge gap surfaced by the QA round-2
hello-world.shrun: an MCP-only agent had no path to ship a bundle (postgres + redis + deploy) in a single tool call./stacks/newon the API is OptionalAuth — anonymous callers get a 24h-TTL stack with a live URL on*.deployment.instanode.dev, no card required — but the MCP exposed no wrapper for it. This PR:create_stack—POST /stacks/new(multipart). Acceptsname,manifestYAML, andservice_tarballs(map of service-name → base64 gzip tarball). Anonymous-friendly. Returnsstack_id, per-service URLs + status, env, expires_in, and the sameupgrade_jwt/ claim-URL block everycreate_*surfaces.get_stack—GET /stacks/{slug}. Polls a stack until every service ishealthy. Anonymous-friendly (uses the public/stacks/{slug}route, not the dashboard-only/api/v1/stacks/{slug}).Bundled fixes (same file touch — ship-while-we're-here)
create_deploy.envdescription claimeddefaults to "production". Per CLAUDE.md convention fix(mcp): wave 3 — BugHunt 2026-05-20 T17 P2 fixes #11 / mig 026 the server default is"development". Description +client.tstypedoc updated.create_postgres/vector/cache/nosql/queue/storage/webhook) silently dropped theenvparam. Now they accept and forwardenvto/<resource>/newas a JSON body field. NewnameAndEnvArg/envArgshared schemas keep tool signatures uniform. Empty/undefinedenvstays off the wire so the server-side default still applies.create_cachedescription quoted halved Redis limits ("hobby 25 MB / pro 256 MB"). Real values perapi/plans.yaml: hobby 50 MB / hobby_plus 50 MB / pro 512 MB / growth 1024 MB / team unlimited. Fixed.Why
CEO ask: prove or disprove the bundle thesis. Pre-this-PR, an MCP-only agent walking the documented surface could only stand up resources individually; assembling them into a running app required
create_deploy(gated byINSTANODE_TOKEN) — which broke the "no account, no Docker, no setup" promise the wedge marketing makes.create_stackcollapses the multi-call dance into one anonymous-friendly call.Tests
client-unit,tools-unit,integration).if (!INSTANODE_MCP_NO_LISTEN)listen block remains uncovered.src/lines.New harness:
test/mock-api.tsgains/stacks/new(multipart) +/stacks/{slug}(GET) routes mirroring the live API contract (services-section YAML parse, declared-service file-part matching, build→healthy auto-flip on poll).EXPECTED_TOOLSbumped 17 → 19.Authorizationis never sent.Live verify (rule-13, partial — see hello-world-success.log)
Spawned the rebuilt MCP (
node dist/index.js), pointed at port-forwarded prod API:tools/list→ 19 tools incl.create_stack,get_stack, with correct schemas.tools/call create_stack(anonymous, no INSTANODE_TOKEN) → HTTP 202,stack_id stk-2a0cbb2e, env defaulted todevelopment.tools/call get_stackpolled status correctly throughbuilding → failed.NOT verified: the final
*.deployment.instanode.devURL. The deploy phase failed at a pre-existing api/k8s RBAC gap (system:serviceaccount:instant:instant-apicannot update namespaces — seeapi/internal/handlers/stack.go:366). This same gap blocks every anonymous/deploy/newand/stacks/newtoday and is out of scope of this MCP PR. Filing a follow-up against the api repo.Note on port-forward: used ONLY to bypass the anonymous /24 daily cap that prior QA round depleted. Request shape is identical to the public
api.instanode.devendpoint (same handler, same OptionalAuth route).Surface checklist (rule 22)
mcp/package.json— 0.11.1 → 0.12.0 (new tool surface)mcp/README.md—create_stack/get_stackrows addedmcp/src/index.ts+client.ts— new tool + client + typesmcp/test/— mock + unit + integration coverageapi/plans.yaml— limits already correct; only the MCP description driftedcontent/llms.txt— needscreate_stack+get_stackrowsCLAUDE.md— wedge tools live in the mcp repo surface, not CLAUDE.mdFollow-ups (separate PRs / repos)
instant-apiServiceAccountnamespaces.updateoninstant-stack-*namespaces. Until this lands, the wedge produces astack_idbut no live URL.llms.txtrow updates forcreate_stack/get_stack.instant stack newonce the api gap above is resolved.🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com