@lensmcp/vite-plugin
v1.16.8
Published
LensMCP Vite plugin — valtio alias, source transform, and browser event bridge.
Maintainers
Readme
@lensmcp/vite-plugin
The single Vite plugin that makes a frontend app observable by LensMCP.
LensMCP is an observability lens for coding agents. @lensmcp/vite-plugin is how a Vite-based frontend opts into that lens: add it to your vite.config.ts and your app starts streaming React renders, Valtio state changes, console output, network calls, route changes, runtime errors, and build events back to LensMCP — with no other source changes.
Under the hood the plugin does three things: it aliases valtio to the instrumented drop-in @lensmcp/valtio-instrumentation, it applies the @lensmcp/react-instrumentation Babel source transform to your JSX/TSX, and it injects a browser event bridge (a client runtime) that ships captured events to the lens over a local WebSocket. The plugin is passive — it never mutates your app's state or behaviour — and is meant to run in dev only.
Install
yarn add -D @lensmcp/vite-pluginvite is a peer dependency (^5 || ^6 || ^7 || ^8).
Usage
Add lensmcpVitePlugin() to your plugins array. Zero config is the intended path — every capability is on by default:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { lensmcpVitePlugin } from '@lensmcp/vite-plugin';
export default defineConfig({
plugins: [
react(),
lensmcpVitePlugin(),
],
});The plugin runs at enforce: 'pre', so its source transform sees raw JSX/TSX before @vitejs/plugin-react (Babel), @vitejs/plugin-react@6 (oxc), or @vitejs/plugin-react-swc (SWC) turn JSX into calls — it coexists with whichever React compiler you use.
To keep it dev-only, gate it on the command:
export default defineConfig(({ command }) => ({
plugins: [
react(),
lensmcpVitePlugin({ enabled: command === 'serve' }),
],
}));What it does
valtio→@lensmcp/valtio-instrumentationalias. Aconfig()hook rewrites bareimport … from 'valtio'to the instrumented drop-in, so existing Valtio stores become observable without edits. The alias uses an exact/^valtio$/match so the drop-in's ownvaltio/vanillaandvaltio/reactimports are left untouched. (Toggle withaliasValtio.)@lensmcp/react-instrumentationBabel transform. Atransform()hook runs the LensMCP Babel plugin (lensmcpBabelPlugin) over every.jsx/.tsxfile outsidenode_modules, wrapping the app root, instrumenting hooks, and stamping JSX identity props. The output is plain JSX/TSX that downstream React compilers preserve. (Toggle withtransformSource.)- Browser event bridge / client runtime injection.
transformIndexHtml()injects<script type="module" src="/@lensmcp/client-runtime">into the page head; the plugin resolves that virtual module to a small client runtime. The runtime opens a WebSocket back to the plugin and forwards browser events — runtime errors, unhandled rejections,console.*,fetch, History/route changes, plus the pre-formedpublishenvelopes from the in-page React and Valtio instrumentation (viawindow.__LENSMCP_PUBLISH__). Each tab gets a stable per-tab id so multiple tabs stay distinct. - Build + HMR events.
handleHotUpdate()emits an HMR event with the affected files, andgenerateBundle()emits a bundle-size report (per-chunk and per-module byte sizes). Vite's nativevite:errorstream is forwarded too.
Incoming browser envelopes and Vite/Rollup events are normalised into LensMCP's BaseEvent shape and published. When an in-process EventBus is bound the plugin publishes directly; otherwise it forwards events cross-process over the same env-configured sink as the NestJS instrumentation — LENSMCP_EVENT_FILE, LENSMCP_UDS, or LENSMCP_IPC_SOCKET (host:port) — falling back to console.debug only for error-severity events when no sink is set.
Options
lensmcpVitePlugin(options?) accepts LensmcpViteOptions — all fields are optional:
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| enabled | boolean | true | Whether the plugin attaches anything. Set false for production. |
| wsPort | number | ephemeral | WebSocket port the client runtime posts events to. An unused port is chosen automatically when omitted. |
| wsHost | string | '127.0.0.1' | Bind address for the WebSocket. |
| injectClient | boolean | true | Inject the client runtime via a <script> tag. |
| aliasValtio | boolean | true | Alias valtio to the instrumented drop-in in dev. |
| transformSource | boolean | true | Run the LensMCP source transform (root-wrap + hooks + JSX ids). |
The returned plugin also exposes a wsPort(): number \| undefined accessor (LensmcpViteRuntime), available after Vite's configureServer hook fires.
How it fits
@lensmcp/vite-plugin is the front door for the browser-side of LensMCP. It pulls in @lensmcp/react-instrumentation (the Babel transform) and @lensmcp/valtio-instrumentation (the Valtio drop-in), then injects the bridge that those instrumentation layers publish through. The bridge ships the resulting events to the LensMCP runtime, where the gateway / MCP server collect and reduce them into resources (react://, valtio://, runtime://browser/*, browser://tabs) that a coding agent can query through the lens.
You typically add this one plugin to a frontend app, pair it with the NestJS instrumentation on the backend, and the agent sees both sides of the running system.
Part of LensMCP. Apache-2.0.
