Integration report from wiring ui-debugger into the developerz.ai monorepo (dashboard SPA on :5173, marketing SSR on :3000) and writing a team skill that teaches agents to drive it.
These are friction points found while reading the source and building the caller-side workflow — not a bug hunt against a running instance. Each is cited to source so they can be triaged independently. Happy to split into separate issues or send PRs.
1. No auth/persona concept — the single biggest cost per run
debugLogin (src/config/schema.ts:19-21) skips captcha only, not auth. There is no place in .ui-debugger-mcp.json to say how to log into this app, so every single goal string has to re-explain the login flow:
"Go to /login, type user@dev.local in the email field, type the password, click Submit, then …"
That is re-typed for every run, burns driver steps before the actual goal starts, and puts credentials into a free-text field that gets logged.
Proposal — named personas on the target, referenced per-run:
Then start_debug({ target: "dashboard", as: "admin", goal: "open Audit and …" }), with the recipe composed into the system prompt as its own section (alongside the existing address/story/criteria sections in src/agent/prompts/compose.ts:57-100), and persona names surfaced by describe. Values should be redacted in logs/agent.log the way log-format.ts:51 already redacts password/token query params.
This matters most for exactly the case the README sells — an AI verifying its own app — because almost every interesting screen is behind a login.
2. timeout is seconds but wait is milliseconds
start_debug.timeout — "Wall-clock cap in SECONDS" (src/mcp/tools/start-debug.ts:60-68)
get_findings.wait — long-poll "up to this many ms" (src/mcp/tools/get-findings.ts:55-62)
Two adjacent time parameters on the same workflow in different units. timeout: 120000 is silently accepted (it's under the 2_147_483 ceiling) and yields a 33-hour run. I had to call this out explicitly as a gotcha in our skill doc, which is a smell.
Suggest either normalising both to ms, or naming them timeout_s / wait_ms.
3. describe.operational doesn't mean the target is reachable
operational is documented as "whether this adapter is wired" (src/services/debug-service.ts:98-99, src/mcp/tools/output.ts:44) — and all three adapters are shipped, so it is effectively always true. It says nothing about whether http://localhost:5173 is actually serving.
The failure mode: dev server is down → the driver launches, navigates, sees nothing, and burns a full run reporting an empty/broken page. To a caller this is indistinguishable from a genuine UI bug.
Suggest either a cheap reachability probe (liveness: "up" | "down" | "unknown" on the describe entry) or a fail-fast in start_debug when the first navigation gets a connection error, with an error that names the port rather than a generic finding.
4. A forgotten end_session wedges the project
One run per cwd (start_debug description; SessionManager keyed by cwd, debug-service.ts:130-131). If the caller crashes, loses context, or simply forgets end_session, the next start_debug is blocked and the only recovery is out-of-band ui-debugger-mcp stop.
An MCP client that dropped the session id mid-conversation has no way back in-band. Suggest start_debug({ replace: true }), or having describe report the active session id so a caller can adopt/end it.
5. Whole-object get_findings caps lists at 20
toToolResult(..., { capLists: args.fields === undefined }) (src/mcp/tools/get-findings.ts:78-79). The steering note pointing at fields is good design, but the default read is the one an agent makes first, and on a long run it silently under-reports — a caller that doesn't notice the note concludes there were 20 issues.
Suggest including an explicit truncated: true + total counts in the capped payload so it's structurally visible, not just prose.
6. Empty data reads as a broken UI
Not a code bug, but a recurring false positive: a freshly-migrated app with no seed data renders empty states everywhere, and the driver correctly reports "the table is empty" as a defect. There is no way to declare preconditions ("this app needs seeded data; empty tables are expected on /new").
A notes / context field on the target config (distinct from the per-run goal) that gets composed into the prompt once would cover this, and would also carry things like "this app is dark-mode by default" or "the first load shows an onboarding modal — dismiss it".
7. Smaller notes
VISION_SYSTEM_PROMPT tells the vision model to reply with only a JSON object (src/agent/belt/look.ts:98-140) but nothing in the shown path appears to enforce a JSON response mode — a chatty model that wraps it in prose costs a step. Worth generateObject/schema-constrained decoding if the router supports it.
- The README's
.mcp.json example uses OPENAI_API_KEY/OPENAI_BASE_URL; for a server whose whole value proposition is model-per-role, the env names read as single-provider. Not worth breaking, but documenting a per-role key override would help.
docs/idea/ is genuinely good and I leaned on it heavily. Linking mcp-tools.md and config.md directly from the README's Setup section would shorten the ramp for a caller who only wants to use the server.
Filed rather than PR'd because there was active uncommitted work in the checkout at the time and I didn't want to collide with it.
8. npx without @latest pins callers to a stale cached version
The README's .mcp.json snippet and init's printed snippet both use:
npx -y <pkg> will reuse an already-cached version rather than resolving the newest one, so a caller who installed at 1.2.0 keeps launching 1.2.0 after you ship 1.5.0 — silently, with no warning that their tool schemas are out of date. Suggest @developerz.ai/ui-debugger-mcp@latest in the README, in init's printed snippet, and in .mcp.example.json.
Integration report from wiring ui-debugger into the
developerz.aimonorepo (dashboard SPA on :5173, marketing SSR on :3000) and writing a team skill that teaches agents to drive it.These are friction points found while reading the source and building the caller-side workflow — not a bug hunt against a running instance. Each is cited to source so they can be triaged independently. Happy to split into separate issues or send PRs.
1. No auth/persona concept — the single biggest cost per run
debugLogin(src/config/schema.ts:19-21) skips captcha only, not auth. There is no place in.ui-debugger-mcp.jsonto say how to log into this app, so every singlegoalstring has to re-explain the login flow:That is re-typed for every run, burns driver steps before the actual goal starts, and puts credentials into a free-text field that gets logged.
Proposal — named personas on the target, referenced per-run:
{ "targets": { "dashboard": { "adapter": "browser", "url": "http://localhost:5173", "auth": { "admin": { "path": "/login", "fields": { "email": "admin@dev.local", "password": "admin" }, "submit": "Sign in" }, "user": { "path": "/login", "fields": { "email": "user@dev.local", "password": "user" }, "submit": "Sign in" } } } } }Then
start_debug({ target: "dashboard", as: "admin", goal: "open Audit and …" }), with the recipe composed into the system prompt as its own section (alongside the existing address/story/criteria sections insrc/agent/prompts/compose.ts:57-100), and persona names surfaced bydescribe. Values should be redacted inlogs/agent.logthe waylog-format.ts:51already redactspassword/tokenquery params.This matters most for exactly the case the README sells — an AI verifying its own app — because almost every interesting screen is behind a login.
2.
timeoutis seconds butwaitis millisecondsstart_debug.timeout— "Wall-clock cap in SECONDS" (src/mcp/tools/start-debug.ts:60-68)get_findings.wait— long-poll "up to this many ms" (src/mcp/tools/get-findings.ts:55-62)Two adjacent time parameters on the same workflow in different units.
timeout: 120000is silently accepted (it's under the2_147_483ceiling) and yields a 33-hour run. I had to call this out explicitly as a gotcha in our skill doc, which is a smell.Suggest either normalising both to ms, or naming them
timeout_s/wait_ms.3.
describe.operationaldoesn't mean the target is reachableoperationalis documented as "whether this adapter is wired" (src/services/debug-service.ts:98-99,src/mcp/tools/output.ts:44) — and all three adapters are shipped, so it is effectively alwaystrue. It says nothing about whetherhttp://localhost:5173is actually serving.The failure mode: dev server is down → the driver launches, navigates, sees nothing, and burns a full run reporting an empty/broken page. To a caller this is indistinguishable from a genuine UI bug.
Suggest either a cheap reachability probe (
liveness: "up" | "down" | "unknown"on the describe entry) or a fail-fast instart_debugwhen the first navigation gets a connection error, with an error that names the port rather than a generic finding.4. A forgotten
end_sessionwedges the projectOne run per cwd (
start_debugdescription;SessionManagerkeyed by cwd,debug-service.ts:130-131). If the caller crashes, loses context, or simply forgetsend_session, the nextstart_debugis blocked and the only recovery is out-of-bandui-debugger-mcp stop.An MCP client that dropped the session id mid-conversation has no way back in-band. Suggest
start_debug({ replace: true }), or havingdescribereport the active session id so a caller can adopt/end it.5. Whole-object
get_findingscaps lists at 20toToolResult(..., { capLists: args.fields === undefined })(src/mcp/tools/get-findings.ts:78-79). The steering note pointing atfieldsis good design, but the default read is the one an agent makes first, and on a long run it silently under-reports — a caller that doesn't notice the note concludes there were 20 issues.Suggest including an explicit
truncated: true+ total counts in the capped payload so it's structurally visible, not just prose.6. Empty data reads as a broken UI
Not a code bug, but a recurring false positive: a freshly-migrated app with no seed data renders empty states everywhere, and the driver correctly reports "the table is empty" as a defect. There is no way to declare preconditions ("this app needs seeded data; empty tables are expected on /new").
A
notes/contextfield on the target config (distinct from the per-rungoal) that gets composed into the prompt once would cover this, and would also carry things like "this app is dark-mode by default" or "the first load shows an onboarding modal — dismiss it".7. Smaller notes
VISION_SYSTEM_PROMPTtells the vision model to reply with only a JSON object (src/agent/belt/look.ts:98-140) but nothing in the shown path appears to enforce a JSON response mode — a chatty model that wraps it in prose costs a step. WorthgenerateObject/schema-constrained decoding if the router supports it..mcp.jsonexample usesOPENAI_API_KEY/OPENAI_BASE_URL; for a server whose whole value proposition is model-per-role, the env names read as single-provider. Not worth breaking, but documenting a per-role key override would help.docs/idea/is genuinely good and I leaned on it heavily. Linkingmcp-tools.mdandconfig.mddirectly from the README's Setup section would shorten the ramp for a caller who only wants to use the server.Filed rather than PR'd because there was active uncommitted work in the checkout at the time and I didn't want to collide with it.
8.
npxwithout@latestpins callers to a stale cached versionThe README's
.mcp.jsonsnippet andinit's printed snippet both use:npx -y <pkg>will reuse an already-cached version rather than resolving the newest one, so a caller who installed at 1.2.0 keeps launching 1.2.0 after you ship 1.5.0 — silently, with no warning that their tool schemas are out of date. Suggest@developerz.ai/ui-debugger-mcp@latestin the README, ininit's printed snippet, and in.mcp.example.json.