What we want to build
A local-dev-only capability that makes the data Query Monitor already collects — slow/duplicate DB queries, HTTP calls, PHP errors, each with the file and line that caused it — queryable by the AI coding agent the developer already has open next to their plugin/theme source (Claude Code, Cursor, Copilot).
The agent gets two things side by side: what happened at runtime (QM's telemetry, with host-resolved file paths) and the code that caused it (the repo it's already in). That pairing is what lets it fix things rather than just describe them.
We do not rebuild Query Monitor — QM already collects the data with file/line backtraces. The work is getting that data out of the single request it lives in and in front of the agent. There are two credible architectures for that pipe; this issue lays both out in detail for a decision.
The common core (both approaches need this)
A dev-only layer that:
- Captures QM's collectors at
shutdown (after QM's own dispatch), normalises them once, and persists the last N requests to a store — QM itself only ever sees the current request. Covers every request the developer makes, including authenticated admin screens, POST submissions and AJAX.
- Translates QM's container paths (
/var/www/…) to real host paths the agent can open; emits null for anything unmappable rather than a guessed path.
- Stays hard-gated to
wp_get_environment_type() === 'local' (plus a {PREFIX}_DEV_MODE constant) and no-ops everywhere else.
Storage: a custom MySQL table ($wpdb + dbDelta), not SQLite. The site already has MySQL; SQLite forced PDO plus a wp-content host-mapping hack in the prototype. A capped table (last N requests) is simpler and portable.
On top of that core, the normalised telemetry is exposed to the agent. How it reaches the agent is where the two approaches differ.
Approach A — inside wp-framework: Abilities API + official MCP Adapter
Ship the whole thing as a DEV_MODE-gated src/Dev/ layer inside wp-framework itself — no separate plugin. The captured telemetry is exposed through the Abilities API (Core 6.9) and bridged to MCP by the official MCP Adapter. No browser extension, no custom transport.
Why this fits wp-framework with zero new dependencies (the library's non-negotiable):
- The Abilities API is WordPress Core 6.9 (shipped Dec 2025) —
wp_register_ability() on wp_abilities_api_init. Registering abilities needs nothing in require.
- A Composer library hooks WordPress the moment the consuming skeleton autoloads and boots it — the same way the skeleton already wires up any framework class. So "the framework captures QM / registers abilities" means "the skeleton that depends on it boots the Dev layer under
DEV_MODE."
- Reading QM is dependency-free — guard with
class_exists( 'QM_Collectors' ); QM is the developer's own plugin, never a Composer dep.
- The table is created via
dbDelta on a schema-version check (libraries have no activation hook — this is the standard pattern).
- The MCP Adapter stays out of
require — it's a separate install (plugin or Composer package, WP ≥ 6.9). The framework's abilities flagged 'meta' => array( 'mcp' => array( 'public' => true ) ) are auto-exposed by the adapter's default server with no glue code. List the adapter under Composer suggest so it's discoverable without becoming a dependency.
Abilities exposed:
| Ability |
Purpose |
list-requests |
Recent captures with headline metrics |
get-telemetry |
Full normalised QM data (queries+stacks, HTTP, errors, assets, timing) with host paths |
compare-requests |
Before/after metric deltas — proof a fix worked |
profile-url |
Capture fresh telemetry via HTTP loopback, multi-sample medians |
Transport: STDIO for local dev (Claude Code via WP-CLI), or HTTP via a WordPress application password. Correlation ("which capture is the page I'm on?") via list-requests (latest / by URL), optionally aided by an X-<Tool>-Request-Id response header.
Dev browses local site (QM + framework Dev layer active)
│ every request captured at shutdown → MySQL store
▼
wp-framework abilities ──► MCP Adapter ──MCP (STDIO/HTTP)──► IDE / Claude Code ──opens──► source
The loop: profile-url → get-telemetry → agent reads the offending source file → writes the fix → re-profiles → compare-requests confirms it.
One detail: abilities are invoked in a separate request from the page being profiled (the agent calls them over STDIO/HTTP), so the data must be persisted for the ability to read it back. Capture happens on the developer's browser requests; abilities read from the store. WP-CLI requests don't run QM collectors — but they don't need to here, they only read.
Setup for a developer: skeleton already has wp-framework → set DEV_MODE → install Query Monitor → install the MCP Adapter → one mcp.json entry.
Pros
- Lives in the framework every skeleton already pulls in — no separate plugin to build, distribute or version, and zero new runtime dependencies.
- Official tooling end-to-end (matches the "prefer official tooling" rule); nothing custom on the transport.
- Already prototyped.
Cons
- Selection is manual: the agent picks what was captured (via the header +
list-requests); it isn't auto-pointed at "the page I'm looking at now".
profile-url re-runs a URL as a fresh GET, so it can't reproduce a logged-in admin screen, a stateful cart→checkout, a POST or an AJAX action. Those are covered only by passive capture, then manual selection.
Approach B — Chrome extension + local MCP bridge (browser-driven)
Keep the same dev-only capture core, but instead of the official adapter, add a Chrome extension and a small local Node MCP bridge, following the proven extension → localhost WebSocket → local MCP server → IDE pattern.
- Capture core additionally serves the normalised telemetry over an authenticated REST route (
register_rest_route — core, still zero-dep). This core can still live in wp-framework.
- Chrome extension (MV3) watches the page the dev is on, reads its
X-<Tool>-Request-Id header, pulls that capture from the REST route (application-password auth), and shows a popup: connection status + a "send this page to my IDE" button (or auto-send on every page).
- Local Node bridge receives the audit over a localhost WebSocket and exposes it to the IDE as MCP tools (
get_latest_audit, get_request, compare_requests). The IDE connects to it like any MCP server.
Dev browses local site (QM + capture core active)
│ response carries X-<Tool>-Request-Id
▼
Chrome extension ──REST fetch (app pwd)──► capture core ──reads──► QM data
│ WebSocket (localhost, nothing leaves the machine)
▼
Local Node MCP bridge ◄── MCP (stdio/http) ── IDE / Claude Code ──opens──► source
Pros
- Captures the exact page the dev actually clicks through — including authenticated, POST and AJAX flows the agent cannot reproduce on its own — and selects it automatically.
- One-click "this page is slow, go fix it" from the browser, where the dev already is. Lowest friction in daily use.
- A natural home for browser-side signals QM can't see (real frontend timing, JS console errors), if we ever want them.
Cons
- Three custom components to build and own long-term: an MV3 extension, a Node MCP bridge, and the WebSocket protocol between them — plus the REST route.
- The Node bridge is a second, parallel MCP server that duplicates what the official MCP Adapter already does in Approach A.
- More setup per developer: install extension, run bridge, mint app password, wire two MCP entries.
- Partially overlaps Chrome DevTools MCP (Google, official) for the frontend-signals angle.
Side by side
|
A — framework + MCP Adapter |
B — Extension + bridge |
| Transport |
official MCP Adapter |
custom Node bridge over WebSocket |
| Custom components |
none (abilities only) |
extension + bridge + REST route |
| New runtime deps |
zero (Abilities API is core) |
zero in PHP, but a Node bridge to ship |
| Capture of authed/POST/AJAX |
passive (captured, manual select) |
passive and auto-selected on view |
| "Fix the exact page I'm on" |
manual (header + list-requests) |
one click |
| Dev setup |
framework + DEV_MODE + adapter + 1 MCP entry |
+ extension + bridge + auth |
| Frontend signals |
defer to Chrome DevTools MCP |
possible to add in-extension |
| Maintenance owned by rtCamp |
minimal |
extension + bridge ongoing |
| Status |
prototyped, working |
designed, not built |
| "Prefer official tooling" rule |
satisfied |
partially diverges |
Distribution
A dev-environment capability, shipped inside the framework every skeleton already pulls in and switched on with {PREFIX}_DEV_MODE — a dev-only mode, not a separate plugin. Query Monitor and (for A) the MCP Adapter are the developer's own local installs; MCP is the wire between the running site and the agent. The Dev layer is hard-gated to local + DEV_MODE and no-ops otherwise — the raw data (real SQL, paths, errors) must never be reachable in production or over a public network.
Open questions
- A vs B, or a phased path (A first, B later if the browse-driven ergonomic proves necessary).
- Storage shape and retention (capped table, last N requests).
- Whether
profile-url's fresh-GET limitation (can't reproduce authed/POST/AJAX) is acceptable for v1, or B's passive auto-capture is needed sooner.
- Final tool name (drives the header name and the ability category/namespace).
What already exists (so we don't reinvent)
- Abilities API (Core 6.9) + MCP Adapter (WP ≥ 6.9, plugin or Composer package) — the official, on-machine browser→agent transport; abilities flagged
meta.mcp.public are auto-exposed by the adapter's default server. Approach A is built directly on this.
- Browser MCP / browser-tools-mcp — prove the extension → localhost WebSocket → local MCP server → IDE pattern (the shape Approach B follows).
- Chrome DevTools MCP (Google) — already gives agents the frontend half (page load, Core Web Vitals, JS, network). The server-side half — DB queries, hooks, PHP errors with backtraces into PHP files — is exactly what this adds.
What we want to build
A local-dev-only capability that makes the data Query Monitor already collects — slow/duplicate DB queries, HTTP calls, PHP errors, each with the file and line that caused it — queryable by the AI coding agent the developer already has open next to their plugin/theme source (Claude Code, Cursor, Copilot).
The agent gets two things side by side: what happened at runtime (QM's telemetry, with host-resolved file paths) and the code that caused it (the repo it's already in). That pairing is what lets it fix things rather than just describe them.
We do not rebuild Query Monitor — QM already collects the data with file/line backtraces. The work is getting that data out of the single request it lives in and in front of the agent. There are two credible architectures for that pipe; this issue lays both out in detail for a decision.
The common core (both approaches need this)
A dev-only layer that:
shutdown(after QM's own dispatch), normalises them once, and persists the last N requests to a store — QM itself only ever sees the current request. Covers every request the developer makes, including authenticated admin screens, POST submissions and AJAX./var/www/…) to real host paths the agent can open; emitsnullfor anything unmappable rather than a guessed path.wp_get_environment_type() === 'local'(plus a{PREFIX}_DEV_MODEconstant) and no-ops everywhere else.Storage: a custom MySQL table (
$wpdb+dbDelta), not SQLite. The site already has MySQL; SQLite forced PDO plus a wp-content host-mapping hack in the prototype. A capped table (last N requests) is simpler and portable.On top of that core, the normalised telemetry is exposed to the agent. How it reaches the agent is where the two approaches differ.
Approach A — inside
wp-framework: Abilities API + official MCP AdapterShip the whole thing as a
DEV_MODE-gatedsrc/Dev/layer insidewp-frameworkitself — no separate plugin. The captured telemetry is exposed through the Abilities API (Core 6.9) and bridged to MCP by the official MCP Adapter. No browser extension, no custom transport.Why this fits
wp-frameworkwith zero new dependencies (the library's non-negotiable):wp_register_ability()onwp_abilities_api_init. Registering abilities needs nothing inrequire.DEV_MODE."class_exists( 'QM_Collectors' ); QM is the developer's own plugin, never a Composer dep.dbDeltaon a schema-version check (libraries have no activation hook — this is the standard pattern).require— it's a separate install (plugin or Composer package, WP ≥ 6.9). The framework's abilities flagged'meta' => array( 'mcp' => array( 'public' => true ) )are auto-exposed by the adapter's default server with no glue code. List the adapter under Composersuggestso it's discoverable without becoming a dependency.Abilities exposed:
list-requestsget-telemetrycompare-requestsprofile-urlTransport: STDIO for local dev (Claude Code via WP-CLI), or HTTP via a WordPress application password. Correlation ("which capture is the page I'm on?") via
list-requests(latest / by URL), optionally aided by anX-<Tool>-Request-Idresponse header.The loop:
profile-url→get-telemetry→ agent reads the offending source file → writes the fix → re-profiles →compare-requestsconfirms it.One detail: abilities are invoked in a separate request from the page being profiled (the agent calls them over STDIO/HTTP), so the data must be persisted for the ability to read it back. Capture happens on the developer's browser requests; abilities read from the store. WP-CLI requests don't run QM collectors — but they don't need to here, they only read.
Setup for a developer: skeleton already has
wp-framework→ setDEV_MODE→ install Query Monitor → install the MCP Adapter → onemcp.jsonentry.Pros
Cons
list-requests); it isn't auto-pointed at "the page I'm looking at now".profile-urlre-runs a URL as a fresh GET, so it can't reproduce a logged-in admin screen, a stateful cart→checkout, a POST or an AJAX action. Those are covered only by passive capture, then manual selection.Approach B — Chrome extension + local MCP bridge (browser-driven)
Keep the same dev-only capture core, but instead of the official adapter, add a Chrome extension and a small local Node MCP bridge, following the proven extension → localhost WebSocket → local MCP server → IDE pattern.
register_rest_route— core, still zero-dep). This core can still live inwp-framework.X-<Tool>-Request-Idheader, pulls that capture from the REST route (application-password auth), and shows a popup: connection status + a "send this page to my IDE" button (or auto-send on every page).get_latest_audit,get_request,compare_requests). The IDE connects to it like any MCP server.Pros
Cons
Side by side
list-requests)Distribution
A dev-environment capability, shipped inside the framework every skeleton already pulls in and switched on with
{PREFIX}_DEV_MODE— a dev-only mode, not a separate plugin. Query Monitor and (for A) the MCP Adapter are the developer's own local installs; MCP is the wire between the running site and the agent. The Dev layer is hard-gated tolocal+DEV_MODEand no-ops otherwise — the raw data (real SQL, paths, errors) must never be reachable in production or over a public network.Open questions
profile-url's fresh-GET limitation (can't reproduce authed/POST/AJAX) is acceptable for v1, or B's passive auto-capture is needed sooner.What already exists (so we don't reinvent)
meta.mcp.publicare auto-exposed by the adapter's default server. Approach A is built directly on this.