1# tools/mcp-e2e 2 3The hosted MCP (`worker/src/hosted-mcp.ts`, `/ai/` on the site) end to end, 4as a listener's own AI would use it. `e2e.mjs` is a real MCP client (the 5TypeScript SDK's) driving a real browser (headless Chromium, with a virtual 6passkey authenticator): 7 81. The client connects, is refused, and is sent to authorize; the browser 9 makes an account with a passkey on the authorize page, allows the app, 10 and returns a code to the client's loopback redirect, as Claude Code 11 does; the client swaps it for a token. 122. The same browser opens the site signed in (`?session=e2ea1`), and the 13 header shows the tab is reachable. 143. The client lists the tools, reads the site's songs, plays one into the 15 tab, and checks it plays in the sandbox and not the page, that the 16 scheduler advances and samples load, that Jev answers, and that Jev's 17 calls go out with the listener's session (charged to their budget). 184. It publishes a song, which Jev screens, and finds it in `my_content`. 195. A second listener connects their own app and sees only their own tab; 20 playing into the first's tab by its id is refused. 216. Disconnecting the first app in the account panel's API ends its token. 22 23It runs against a dev server that is already up: the site with its Worker 24beside it, the Worker with a Jev key (`op-env-run`). From a worktree, on 25ports of its own: 26 27 op-env-run -- node tools/dev/worker.mjs --env dev --port 4392 --inspector-port 9392 \ 28 --assets worker/.wrangler/dev-assets --test-scheduled \ 29 --var SONGS_URL:http://localhost:4391/jev/songs.json 30 (cd website && JEV_WORKER_PORT=4392 pnpm exec astro dev --port 4391) 31 32then, in the devshell, with Playwright and its browsers as the `measure` 33app has them (the devshell does not set them: `nix build .#measure 34--print-out-paths` and copy the three `export PLAYWRIGHT_…` lines of its 35`bin/jevstrudel-measure`), and the site's `doc.json` built once 36(`pnpm run jsdoc-json` at the root; the dev server's reference reads it): 37 38 cd tools/mcp-e2e && npm install && SITE=http://localhost:4391 node e2e.mjs 39 40`tools.mjs` is the tools both MCPs share (worker/README.md, The tools 41both MCPs share), on the same servers, with the real Jev: a listener's AI 42changes the tab's settings (opening the sounds tab), the browser imports a 43sound folder there, then `list_sounds` per category (the import under user 44and imported) and in pages, `ask_jev_sound` among the drum machines (in 45banks) and the synths, charged to the listener, `api_reference`, 46`play_song` for a site song (in the page) and a listener's (in the sandbox), 47`get_logs` from the console tab, `react`, `vote` (in the summary), 48`ask_jev_song` playing its pick, and `comment`; then the dev hub's MCP on 49the same tab (`WORKER`, the Worker's own address, for `/mcp`): 50 51 SITE=http://localhost:4391 WORKER=http://localhost:4392 node tools.mjs 52 53`person.mjs` is both scripts' listener: a passkey account made on the 54authorize page, an MCP client with its token, and the signed-in tab. 55 56`proxy.mjs` is the stdio proxy's prod mode (`tools/mcp/`) end to end: the 57SDK's client starts `tools/mcp/strudel-mcp.mjs` over stdio, as Claude Code 58does, with its prod endpoint pointed at a local production Worker. It 59switches with `use_environment` while the headless browser signs in on the 60authorize page the proxy logs, checks `tools/list_changed` and prod's tool 61list, calls `get_status` and `my_content` through the proxy, disconnects 62the app and signs in again from the error that follows, switches back to 63dev, and checks the grant is revoked when the proxy exits. The Worker needs 64no Jev key or built site for this; from a worktree root: 65 66 node tools/dev/worker.mjs --env "" --port 4397 --inspector-port 9397 \ 67 --assets <an empty folder> --persist-to worker/.wrangler/standin-prod 68 69after `migrateLocal({ env: '', persistTo: 'worker/.wrangler/standin-prod' })` 70(`tools/dev/migrate.mjs`), then `npm install` in `tools/mcp` and here, and 71 72 SITE=http://localhost:4397 node proxy.mjs 73 74`e2e.mjs` makes two accounts and a public listener song in that Worker's local 75database each run (`tools.mjs` one account, a song, a comment and a vote; 76`proxy.mjs` one account). Jev's answers to a request it has answered in the last 77hour come from the relay's cache and are not charged, so a second run in 78the hour shows its calls as cache hits.