Skip to content

Run codegen in the browser: the hosted playground - #7

Merged
SouravInsights merged 1 commit into
mainfrom
feat/hosted-playground
Sep 18, 2026
Merged

SouravInsights merged 1 commit into
mainfrom
feat/hosted-playground

Conversation

@SouravInsights

Copy link
Copy Markdown
Owner

What this is

The dev dashboard was reachable only by installing the CLI and running webmcp-codegen dev. This adds /playground to the site: paste an OpenAPI spec, upload a file, or point at a URL, and the tools it finds land in the same dashboard the CLI serves, each with its generated source and a form that calls the endpoint. GraphiQL's move, for WebMCP tools.

Three steps, all real code:

  1. Spec in. The browser reads it. Paste, upload, or fetch the URL you typed.
  2. Generate. POST /api/playground writes the spec to a temp directory, runs runGenerate with the real openapi and tools outputs in dry-run mode, and returns dashboard state. Nothing is written, the temp directory is deleted before the response returns.
  3. Play. dashboardHtml(state, { mode: "playground" }) mounts in a shadow root, and a small in-page bridge answers the dashboard's three requests: /api/state, /api/override (edits stay in the tab), /api/run (the call leaves from the visitor's browser).

Why this shape

The site never fetches a visitor's URL, and never proxies their calls. A demo that fetched any URL a visitor typed would be an open proxy into whatever network the site runs in. Both the spec read and the tool call happen in the browser, so CORS is the only boundary and the page can explain it in one sentence. The cost is real and stated in the UI: an API that refuses cross-origin requests cannot be run from the playground.

One UI, one state mapping, one request planner. dashboardState and buildToolRequest moved out of dev/server.ts into dev/state.ts and dev/request.ts, exported together from a new @webmcp-stack/codegen/dev subpath. Both hosts import them, which is what keeps the hosted demo from becoming a lookalike. dashboardHtml gained mode: "playground" for the five lines whose meaning depends on where the dashboard runs (edits saved or not, calls server-side or browser-side).

A bug this surfaced. The dashboard's run-it test resolved /pet/{id} against the spec's server with new URL(path, base), which drops a base path: the Petstore spec ships servers: ["/api/v3"], so every test call 404'd. Generated callApi(...) concatenates and keeps it. The planner now concatenates too, and a spec loaded from a URL resolves its relative server URLs against its own location (what OpenAPI says a relative server means).

Verification

  • pnpm lint, pnpm typecheck, pnpm test, pnpm build all pass. Package tests: 240. Site tests: 11 (new).
  • Route, live on a dev server: Petstore spec → 19 tools, 0 errors, 16 warnings, correct risk grouping; the Immich excerpt → 6 tools with the write, destructive and admin ones withheld; a malformed spec → 422 with the YAML parser's own message; empty body → 400; 21st generation in a minute → 429.
  • Run path, end to end: the planner builds GET https://petstore3.swagger.io/api/v3/pet/findByStatus?status=available and the real API answers 200 with JSON.
  • The mount and the bridge have tests that boot the dashboard's own script against a shadow root and drive the run form through the bridge (site/lib/dashboard-mount.test.ts). happy-dom does not evaluate runtime-appended scripts, so the test runs the script explicitly, with a comment saying why.

New test infrastructure (worth a look)

site had no test script. This adds vitest.config.ts (happy-dom) and a test script, following the codegen package's pattern of resolving vitest from the workspace root. Turbo's test task now runs the site tests in CI. Happy to drop this if you would rather keep the site test-free.

Also in here

  • The landing demo now mounts through the same helper as the playground, so both use one code path.
  • site/lib/demo-data.ts regenerated: it had drifted from the generator (legacy box-drawing markers, no title or consequentialHint, pre-fetch* helper bodies). The landing demo was showing generated source users no longer get.
  • The Immich excerpt moved to site/public/demo/ so the playground can load the same file the landing demo is built from.
  • Docs: a Playground page, a pointer from Quickstart, a nav link, and a hero link. Decision record in docs/notes/2026-09-18-hosted-playground.md. Changeset: minor on @webmcp-stack/codegen (new subpath).

Follow-ups, not in this PR

  • No server-side spec fetching, even behind an allowlist. It adds an SSRF surface for something the paste box already covers.
  • No shareable playground links or saved sessions: both need storage, which the page promises not to have.
  • /playground is not in sitemap.ts (a tool, not a page to rank).

@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
webmcp-stack Ready Ready Preview Sep 18, 2026 9:35am UTC

The dev dashboard was reachable only by installing and running a command. The
site now serves the same product at /playground: paste a spec, upload a file, or
point at a URL, and the tools it finds land in the same dashboard, each with its
generated source and a form that calls the endpoint.

- packages/codegen: a new `dev` subpath holds the pieces both hosts need.
  dashboardState (a pipeline run, shaped for the UI) and buildToolRequest (a tool
  call, as a real HTTP request) moved out of dev/server.ts, which now imports
  them. dashboardHtml takes mode: "playground" for the few lines whose meaning
  depends on where the dashboard runs.
- buildToolRequest concatenates a spec server's base path instead of resolving
  the path against it, which is what generated callApi(...) does. The run-it test
  and the shipped tool now hit the same URL: /api/v3 used to be dropped.
- site: POST /api/playground runs the real pipeline in a temporary directory and
  returns dashboard state. The browser reads the spec and makes the test calls,
  so the site is never a proxy into its own network, and the page says where the
  limits bite (CORS, sessions).
- The landing demo and the playground mount through one helper, so the demo
  cannot drift from the dashboard it advertises.
- Tests: the shared state mapping and request planner in the package, the
  shadow-root mount and page-side bridge in the site (happy-dom).
- Regenerated the landing demo data, which had drifted from the generator.
@SouravInsights

Copy link
Copy Markdown
Owner Author

Preview notes: the Vercel preview build and the CI verify job both pass on this branch. The preview URL itself sits behind Vercel deployment protection, so it redirects anonymous requests to SSO and answers 401 for /api/playground. To try the flow on the preview, log in to Vercel or run it locally: pnpm --filter site dev then open http://localhost:3001/playground and click the Petstore example. That example is the end-to-end path: the browser reads the spec, the route generates 19 tools, and the run form calls the real Petstore API from your browser.

@SouravInsights
SouravInsights merged commit 9509b9e into main Sep 18, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 7cf9c17e Deployed Sep 18, 2026 by vercel[bot]
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