@lensmcp/react-instrumentation
v1.16.8
Published
Zero-config React instrumentation for LensMCP (Babel transform + flow/render runtime).
Maintainers
Readme
@lensmcp/react-instrumentation
Zero-config React instrumentation for LensMCP — a Babel transform plus a render/flow runtime that lets the LensMCP lens see a running React app: which components rendered, which hooks re-ran, and how a click in the browser stitches together with the server request and DB query it triggered.
This package is the React-side half of LensMCP's observability lens. A small Babel plugin rewrites your JSX at build time (stamping component identity, wrapping useEffect/useMemo/useCallback, wrapping interaction handlers, and wrapping the root render). A matching runtime — traced hooks, a <LensmcpRoot> wrapper, and a fetch wrapper (flow-fetch) — emits structured events and propagates a flowId across the network boundary so a single user action can be read end to end (click → fetch → server span → DB query).
You normally don't import any of this by hand. @lensmcp/vite-plugin wires the Babel plugin into your React build, injects the publish channel into the served HTML, and bridges the emitted events to the lens. The exports below are available for projects that can't run the global transform (e.g. opt-in tracing) or want to start a flow manually.
Install
yarn add @lensmcp/react-instrumentationIn practice you don't add it directly — it's pulled in and configured by @lensmcp/vite-plugin, which applies the Babel plugin and sets up the transport. Install it explicitly only for the opt-in, hand-wired path described under Usage.
Usage
The realistic path (automatic). Add @lensmcp/vite-plugin to your Vite config. It loads this package's Babel plugin, so every component, hook, conditional, and interaction handler in your source is instrumented with no app-code changes — and it installs the publish channel that the runtime emits into.
If you can't run the global transform, the runtime is usable directly:
import { createRoot } from 'react-dom/client';
import { LensmcpRoot, installFlowFetch } from '@lensmcp/react-instrumentation';
// Wrap browser fetches so they carry the active flow's headers.
installFlowFetch();
createRoot(document.getElementById('root')!).render(
<LensmcpRoot page="home" route="/">
<App />
</LensmcpRoot>,
);(This is exactly what the Babel plugin generates automatically when autoWrapRoot is on.) From there you can opt individual pieces in:
import { withTraceComponent, withFlow, tracedUseEffect } from '@lensmcp/react-instrumentation';
// Trace a single component's renders without the global transform.
const TracedPanel = withTraceComponent(Panel, { source: 'src/Panel.tsx' });
// Start a correlated flow from a handler the transform didn't reach.
<button onClick={withFlow(save, { originType: 'user-click', originNodeId: 'ui:save' })}>
Save
</button>;
// Drop-in useEffect that emits effect-run / effect-cleanup events.
tracedUseEffect('Panel:42:0', () => subscribe(), [topic]);If you build your own bundler integration, point the runtime at your transport with setPublisher(fn); until a publisher is set, every event is buffered (capped) so the library is safe to import in tests and SSR.
What it captures
- Component renders — the Babel transform stamps each JSX element with
data-agent-component(the component name) anddata-agent-source(file:line), and wraps the root in<LensmcpRoot>, whose React<Profiler>emits arenderevent (mount/update, durations, commit times) on every commit.withTraceComponentdoes the same for a single opt-in component. - Hooks —
useEffect/useMemo/useCallbackare rewritten to traced variants that emiteffect-run(pluseffect-cleanup) andhook-runevents, including a cheapdepsHashso the lens can tell whether a hook re-ran because its dependencies changed. - Conditional slots —
<TraceSlot>emitsslot-content-changedwhen the type of the rendered child changes (e.g. a branch swap in a JSX conditional). - Browser→server flow correlation (flow-fetch) — interaction handlers are wrapped with
withFlow, which opens a flow with a generatedflowId. The patchedfetchinjectsx-lensmcp-flow-id+x-lensmcp-origin-node-idheaders so the server-side@lensmcp/nest-instrumentationstamps its spans and DB queries with the same flowId, and it publishesfetch-start/fetch-done/fetch-errorlegs. A short causality window (and an auto-startedpage-loadflow) attributes async post-response re-renders to the action that caused them. - Identity — stable
logicalIds for components, hooks, and slots plus per-mountinstanceIds, so the lens can attribute every event to a specific component instance across re-renders.
API
Exports from the package entry (@lensmcp/react-instrumentation):
| Export | Description |
| --- | --- |
| LensmcpRoot / LensmcpRootProps | Root wrapper: a <Profiler> + context provider that emits render events and tags hooks with renderer/page/route. |
| LensmcpContext / useLensmcpContext / LensmcpIdentityContext | React context (and hook) carrying the active renderer/page/route and component instance identity. |
| tracedUseEffect / tracedUseMemo / tracedUseCallback | Drop-in hook replacements that emit effect-run/hook-run events; the transform rewrites bare hooks to these. |
| TraceSlot / TraceSlotProps | Wrapper that emits slot-content-changed when its primary child's type changes. |
| withTraceComponent / WithTraceOptions | HOC for opt-in render tracing when the global transform isn't available. |
| installFlowFetch / FlowFetchOptions | Monkeypatches globalThis.fetch to inject flow headers and publish network legs; returns an uninstall function. |
| withFlow | Wraps an event handler so each call runs inside a fresh correlated flow (idempotent; passes non-functions through). |
| runInFlow / beginBackgroundFlow / currentFlow / activeFlow / extendFlowWindow | Flow-stack and causality-window primitives for starting/inspecting/extending flows manually. |
| publish / setPublisher / drainQueue | The publish channel: emit an event, install the transport, or drain the buffered queue (tests). |
| PublishEnvelope / PublishCategory / FlowSpec | Types for the publish envelope, event category, and a flow descriptor. |
| componentLogicalId / hookLogicalId / slotLogicalId / makeInstanceId / depsHash / resetIdentityState | Identity helpers — build stable logical ids, per-mount instance ids, and dependency hashes. |
| RenderRecord / EffectRecord / HookRecord / SlotChangeRecord / ComponentMountRecord / ReactPublishPayload | Wire-shape types for the events the library publishes. |
Subpath export @lensmcp/react-instrumentation/babel-plugin (also re-exported from the entry as lensmcpBabelPlugin, with LensmcpBabelOptions): the JSX identity transform. Options: production, includeSource, rootDir, autoWrapHooks, autoWrapRoot, autoFlowHandlers, flowEvents. It's dev-only — a no-op when production is true or NODE_ENV === 'production' — and honors // @lensmcp-ignore (file) and data-lensmcp-ignore (element) opt-outs.
// vite.config.ts — applied for you by @lensmcp/vite-plugin
import lensmcpBabel from '@lensmcp/react-instrumentation/babel-plugin';
react({ babel: { plugins: [lensmcpBabel] } });How it fits
- Configured by
@lensmcp/vite-plugin. The Vite plugin runslensmcpBabelPluginover your React source and injects a client runtime that setswindow.__LENSMCP_PUBLISH__— the transport this package'spublish()looks for. In dev, events flow over a WebSocket to the lens; in production, over the FrontMCP server transport. - Pairs with
@lensmcp/valtio-instrumentation. The Vite plugin aliasesvaltio→@lensmcp/valtio-instrumentationso state mutations publish into the same channel, andflowIdcorrelation lets a render here be tied to the state change (and the fetch) that caused it. - Server side:
@lensmcp/nest-instrumentation. ItsTraceInterceptorreads thex-lensmcp-flow-idheader set by flow-fetch and stamps server-request, span, and DB-query events with the matching flowId — completing the browser→server story. - Events use
@lensmcp/protocol-types. The published record shapes (RenderPhase,RenderWhy, etc.) mirror the shared protocol so the lens and reducers can consume them.
Part of LensMCP. Apache-2.0.
