@0x1f320.sh/why-did-you-render-mcp 1.1.0-dev.1 → 1.1.0-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -169,6 +169,8 @@ Once both the MCP server and your React dev server are running, interact with yo
169
169
  | `get_tracked_components` | Lists components currently tracked by why-did-you-render. |
170
170
  | `get_projects` | Lists all active projects (identified by their origin URL). |
171
171
  | `clear_renders` | Clears all stored render data. Optionally scope to a specific project. |
172
+ | `pause_renders` | Pauses render data collection in the browser. Clients stop reporting until resumed. |
173
+ | `resume_renders` | Resumes render data collection previously paused with `pause_renders`. |
172
174
 
173
175
  When multiple projects are active, tools accept an optional `project` parameter (the browser's origin URL, e.g. `http://localhost:3000`). If omitted and only one project exists, it is auto-selected.
174
176
 
@@ -204,8 +206,8 @@ Each render report is tagged with a React **commit ID**, allowing agents to insp
204
206
  Browser (project-a) ──┐
205
207
  Browser (project-b) ──┤
206
208
 
207
- MCP #1 → WS(:4649) (first instance binds)
208
- MCP #2 → WS(:4649) → skip (EADDRINUSE)
209
+ MCP #1 → WS(:4649) (first instance binds, "owner")
210
+ MCP #2 → WS(:4649) → relay client ──▶ MCP #1
209
211
 
210
212
 
211
213
  ~/.wdyr-mcp/renders/ (JSONL files, shared across instances)
@@ -213,9 +215,9 @@ Browser (project-b) ──┤
213
215
  └─ http___localhost_5173.jsonl
214
216
  ```
215
217
 
216
- - **Multiple MCP instances** can run simultaneously. Only the first instance starts the WebSocket server; others gracefully skip. All instances share the same JSONL data directory.
218
+ - **Multiple MCP instances** can run simultaneously. Only the first instance (the "owner") starts the WebSocket server; others connect as socket.io clients to relay commands (e.g. pause/resume) to the owner. All instances share the same JSONL data directory.
217
219
  - **Multi-project support** — Each project is identified by `location.origin`. Render data is stored in per-project JSONL files.
218
- - **No daemon required** — Each MCP instance is independent. The WebSocket server is opportunistically claimed by whichever instance starts first.
220
+ - **No daemon required** — Each MCP instance is independent. The WebSocket server is opportunistically claimed by whichever instance starts first. Commands requiring WS access are relayed to the owner.
219
221
  - **Value dictionary deduplication** — Render reports often repeat the same `prevValue`/`nextValue` objects across thousands of entries. Each JSONL file stores a content-addressed dictionary on its first line, mapping xxhash-wasm hashes to unique values. Render lines reference them via `@@ref:<hash>` sentinels instead of inlining the full object, dramatically reducing file size. Reads hydrate refs transparently.
220
222
 
221
223
  ## Configuration
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`socket.io-client`),l=require(`error-stack-parser`);l=s(l);let u=require(`@jridgewell/trace-mapping`);const d=new Map;function f(e){let t=d.get(e);if(t)return t;let n=(async()=>{try{let t=await fetch(`${e}.map`);return t.ok?new u.TraceMap(await t.json()):null}catch{return null}})();return d.set(e,n),n}async function p(e,t,n){let r=await f(e);if(!r)return{path:e,line:t};let i=(0,u.originalPositionFor)(r,{line:t,column:n});return i.source?{path:i.source,line:i.line??t}:{path:e,line:t}}const m=[`whyDidYouRender`,`react-dom`,`react.development`,`react.production`,`scheduler.`,`installHook`,`console.`],h=[`trackHookChanges`,`WDYRFunctionalComponent`,`Object.notifier`,`notifier`,`console.trace`],g=new Set(`renderWithHooks.mountIndeterminateComponent.updateFunctionComponent.updateForwardRef.updateMemoComponent.updateSimpleMemoComponent.beginWork.beginWork$1.completeWork.completeUnitOfWork.performUnitOfWork.runWithFiberInDEV.callComponentInDEV.workLoopSync.workLoopConcurrent.renderRootSync.renderRootConcurrent.performWorkOnRoot.performSyncWorkOnRoot.performConcurrentWorkOnRoot.commitRoot.commitRootImpl.commitMutationEffects.commitMutationEffectsOnFiber.commitLayoutEffects.commitLayoutEffectOnFiber.flushPassiveEffects.flushPassiveEffectsImpl.flushSyncWorkAcrossRoots_impl.processRootScheduleInMicrotask.scheduleUpdateOnFiber.ensureRootIsScheduled.react_stack_bottom_frame.dispatchSetState.dispatchReducerAction.dispatchAction.mountState.updateState.mountReducer.updateReducer.mountMemo.updateMemo.mountEffect.updateEffect.mountLayoutEffect.updateLayoutEffect.mountRef.updateRef.flushWork.performWorkUntilDeadline`.split(`.`));function _(e){return m.some(t=>e.includes(t))}function v(e){return h.some(t=>e===t)}function y(e){return e.replace(/WDYR$/,``)}function b(e){let t=e.startsWith(`new `)?e.slice(4):e,n=t.lastIndexOf(`.`);return y(n>=0?t.slice(n+1):t)}function x(e){return/^use[A-Z]/.test(e)}function S(e){let t;try{t=l.default.parse(e)}catch{return[]}let n=[];for(let e of t){let t=e.fileName??``;if(!t||_(t))continue;let r=e.functionName;if(!r||v(r))continue;let i=b(r);i&&(g.has(i)||n.push({type:x(i)?`hook`:`component`,name:i,file:t,line:e.lineNumber??0,column:e.columnNumber??0}))}return n}async function C(e){let t=S(e);return t.length===0?[]:Promise.all(t.map(async e=>({type:e.type,name:e.name,location:await p(e.file,e.line,e.column)})))}const w=Symbol.for(`react.element`),T=Symbol.for(`react.transitional.element`),E=Symbol.for(`react.memo`),D=Symbol.for(`react.forward_ref`);function O(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.$$typeof===w||t.$$typeof===T||t.$$typeof===60103}function k(e){let t=!1,n=!1,r=e;for(let e=0;e<5&&!(typeof r!=`object`||!r);e++){let e=r;if(e.$$typeof===E)t=!0,r=e.type;else if(e.$$typeof===D)n=!0,r=e.render;else break}let i=`Unknown`;return typeof r==`string`?i=r:typeof r==`function`&&(i=r.displayName||r.name||`Anonymous`),{name:i,memo:t,forwardRef:n}}function A(e,t,n){let r=k(e.type),i={};if(e.props&&typeof e.props==`object`)for(let r of Object.keys(e.props))r!==`children`&&(i[r]=j(e.props[r],t,n+1));return{type:`react-node`,component:r,props:i}}function j(e,t,n){if(e==null)return null;if(typeof e==`function`)return{type:`function`,name:e.name||`anonymous`};if(typeof e==`boolean`)return e;if(typeof e==`number`)return Number.isNaN(e)?`NaN`:Number.isFinite(e)?Object.is(e,-0)?`-0`:e:e>0?`Infinity`:`-Infinity`;if(typeof e==`string`)return e;if(typeof e==`bigint`||typeof e==`symbol`)return e.toString();if(t.has(e))return`[Circular]`;if(n>=8)return`[MaxDepth]`;if(t.add(e),O(e))return A(e,t,n);if(Array.isArray(e))return e.map(e=>j(e,t,n+1));let r=Object.getPrototypeOf(e)?.constructor?.name;if(r&&r!==`Object`){if(e instanceof Date)return e.toISOString();if(e instanceof RegExp)return String(e);if(e instanceof Map){let r={};for(let[i,a]of e.entries())r[String(i)]=j(a,t,n+1);return{type:`Map`,entries:r}}if(e instanceof Set)return{type:`Set`,values:[...e].map(e=>j(e,t,n+1))};if(e instanceof Promise)return`Promise`;if(e instanceof Error)return{type:`Error`,name:e.name,message:e.message};if(typeof Node<`u`&&e instanceof Node&&e instanceof Element){let t={};for(let n of e.attributes)t[n.name]=n.value;return{type:`dom`,tagName:e.tagName.toLowerCase(),attrs:t}}return{type:`class`,name:r}}let i={};for(let r of Object.keys(e))i[r]=j(e[r],t,n+1);return i}function M(e){return j(e,new WeakSet,0)}function N(e){return Array.isArray(e)?e.map(e=>({pathString:e.pathString,diffType:e.diffType,prevValue:M(e.prevValue),nextValue:M(e.nextValue)})):!1}function P(e){return{propsDifferences:N(e.propsDifferences),stateDifferences:N(e.stateDifferences),hookDifferences:N(e.hookDifferences)}}function F(e){console.log(`%c[WDYR MCP]%c ${e}`,`color: #38bdf8; font-weight: bold`,`color: inherit; font-weight: normal`)}function I(e){globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__||(globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__={supportsFiber:!0,inject(){},onCommitFiberRoot(){},onCommitFiberUnmount(){}});let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,n=t.onCommitFiberRoot.bind(t);t.onCommitFiberRoot=(...t)=>(e(),n(...t))}function L(e){let t={};return e.include&&(t.include=e.include.map(e=>e.source)),e.exclude&&(t.exclude=e.exclude.map(e=>e.source)),e.trackAllPureComponents!=null&&(t.trackAllPureComponents=e.trackAllPureComponents),e.trackHooks!=null&&(t.trackHooks=e.trackHooks),e.trackExtraHooks&&(t.trackExtraHooks=e.trackExtraHooks.map(([,e])=>e)),e.logOnDifferentValues!=null&&(t.logOnDifferentValues=e.logOnDifferentValues),e.logOwnerReasons!=null&&(t.logOwnerReasons=e.logOwnerReasons),t}function R(e){let{wsUrl:t,projectId:n,notifier:r,...i}=e??{},a=t??`http://localhost:4649`,o=n??globalThis.location?.origin??`default`,s=0;I(()=>{s++});let l=(0,c.io)(a,{reconnection:!0,reconnectionDelay:1e3,reconnectionDelayMax:3e4,transports:[`websocket`]}),u=!1;l.on(`connect`,()=>{F(`Connected to ${a}`),e&&l.emit(`config`,L(e),o)}),l.on(`disconnect`,()=>{F(`Disconnected, reconnecting...`)}),l.on(`pause`,()=>{u=!0,F(`Render collection paused`)}),l.on(`resume`,()=>{u=!1,F(`Render collection resumed`)});let d=null,f=!1;async function p(){if(f=!1,!d||d.items.length===0)return;let e=d;d=null;let t=await Promise.all(e.items.map(async({info:e,error:t})=>{let n=await C(t);return{displayName:e.displayName,reason:P(e.reason),hookName:e.hookName,...n.length>0&&{stackFrames:n}}}));l.emit(`render-batch`,t,o,e.commitId)}return{...i,notifier(e){if(u){r&&r(e);return}let t=Error();d&&d.commitId===s?d.items.push({info:e,error:t}):(d&&p(),d={commitId:s,items:[{info:e,error:t}]}),f||(f=!0,queueMicrotask(p)),r&&r(e)}}}exports.buildOptions=R;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`socket.io-client`),l=require(`error-stack-parser`);l=s(l);let u=require(`@jridgewell/trace-mapping`);const d=new Map;function f(e){let t=d.get(e);if(t)return t;let n=(async()=>{try{let t=await fetch(`${e}.map`);return t.ok?new u.TraceMap(await t.json()):null}catch{return null}})();return d.set(e,n),n}async function p(e,t,n){let r=await f(e);if(!r)return{path:e,line:t};let i=(0,u.originalPositionFor)(r,{line:t,column:n});return i.source?{path:i.source,line:i.line??t}:{path:e,line:t}}const m=[`whyDidYouRender`,`react-dom`,`react.development`,`react.production`,`scheduler.`,`installHook`,`console.`],h=[`trackHookChanges`,`WDYRFunctionalComponent`,`Object.notifier`,`notifier`,`console.trace`],g=new Set(`renderWithHooks.mountIndeterminateComponent.updateFunctionComponent.updateForwardRef.updateMemoComponent.updateSimpleMemoComponent.beginWork.beginWork$1.completeWork.completeUnitOfWork.performUnitOfWork.runWithFiberInDEV.callComponentInDEV.workLoopSync.workLoopConcurrent.renderRootSync.renderRootConcurrent.performWorkOnRoot.performSyncWorkOnRoot.performConcurrentWorkOnRoot.commitRoot.commitRootImpl.commitMutationEffects.commitMutationEffectsOnFiber.commitLayoutEffects.commitLayoutEffectOnFiber.flushPassiveEffects.flushPassiveEffectsImpl.flushSyncWorkAcrossRoots_impl.processRootScheduleInMicrotask.scheduleUpdateOnFiber.ensureRootIsScheduled.react_stack_bottom_frame.dispatchSetState.dispatchReducerAction.dispatchAction.mountState.updateState.mountReducer.updateReducer.mountMemo.updateMemo.mountEffect.updateEffect.mountLayoutEffect.updateLayoutEffect.mountRef.updateRef.flushWork.performWorkUntilDeadline`.split(`.`));function _(e){return m.some(t=>e.includes(t))}function v(e){return h.some(t=>e===t)}function y(e){return e.replace(/WDYR$/,``)}function b(e){let t=e.startsWith(`new `)?e.slice(4):e,n=t.lastIndexOf(`.`);return y(n>=0?t.slice(n+1):t)}function x(e){return/^use[A-Z]/.test(e)}function S(e){let t;try{t=l.default.parse(e)}catch{return[]}let n=[];for(let e of t){let t=e.fileName??``;if(!t||_(t))continue;let r=e.functionName;if(!r||v(r))continue;let i=b(r);i&&(g.has(i)||n.push({type:x(i)?`hook`:`component`,name:i,file:t,line:e.lineNumber??0,column:e.columnNumber??0}))}return n}async function C(e){let t=S(e);return t.length===0?[]:Promise.all(t.map(async e=>({type:e.type,name:e.name,location:await p(e.file,e.line,e.column)})))}const w=Symbol.for(`react.element`),T=Symbol.for(`react.transitional.element`),E=Symbol.for(`react.memo`),D=Symbol.for(`react.forward_ref`);function O(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.$$typeof===w||t.$$typeof===T||t.$$typeof===60103}function k(e){let t=!1,n=!1,r=e;for(let e=0;e<5&&!(typeof r!=`object`||!r);e++){let e=r;if(e.$$typeof===E)t=!0,r=e.type;else if(e.$$typeof===D)n=!0,r=e.render;else break}let i=`Unknown`;return typeof r==`string`?i=r:typeof r==`function`&&(i=r.displayName||r.name||`Anonymous`),{name:i,memo:t,forwardRef:n}}function A(e,t,n){let r=k(e.type),i={};if(e.props&&typeof e.props==`object`)for(let r of Object.keys(e.props))r!==`children`&&(i[r]=j(e.props[r],t,n+1));return{type:`react-node`,component:r,props:i}}function j(e,t,n){if(e==null)return null;if(typeof e==`function`)return{type:`function`,name:e.name||`anonymous`};if(typeof e==`boolean`)return e;if(typeof e==`number`)return Number.isNaN(e)?`NaN`:Number.isFinite(e)?Object.is(e,-0)?`-0`:e:e>0?`Infinity`:`-Infinity`;if(typeof e==`string`)return e;if(typeof e==`bigint`||typeof e==`symbol`)return e.toString();if(t.has(e))return`[Circular]`;if(n>=8)return`[MaxDepth]`;if(t.add(e),O(e))return A(e,t,n);if(Array.isArray(e))return e.map(e=>j(e,t,n+1));let r=Object.getPrototypeOf(e)?.constructor?.name;if(r&&r!==`Object`){if(e instanceof Date)return e.toISOString();if(e instanceof RegExp)return String(e);if(e instanceof Map){let r={};for(let[i,a]of e.entries())r[String(i)]=j(a,t,n+1);return{type:`Map`,entries:r}}if(e instanceof Set)return{type:`Set`,values:[...e].map(e=>j(e,t,n+1))};if(e instanceof Promise)return`Promise`;if(e instanceof Error)return{type:`Error`,name:e.name,message:e.message};if(typeof Node<`u`&&e instanceof Node&&e instanceof Element){let t={};for(let n of e.attributes)t[n.name]=n.value;return{type:`dom`,tagName:e.tagName.toLowerCase(),attrs:t}}return{type:`class`,name:r}}let i={};for(let r of Object.keys(e))i[r]=j(e[r],t,n+1);return i}function M(e){return j(e,new WeakSet,0)}function N(e){return Array.isArray(e)?e.map(e=>({pathString:e.pathString,diffType:e.diffType,prevValue:M(e.prevValue),nextValue:M(e.nextValue)})):!1}function P(e){return{propsDifferences:N(e.propsDifferences),stateDifferences:N(e.stateDifferences),hookDifferences:N(e.hookDifferences)}}function F(e){console.log(`%c[WDYR MCP]%c ${e}`,`color: #38bdf8; font-weight: bold`,`color: inherit; font-weight: normal`)}function I(e,t){if(e==null)return;let n=e.actualDuration;if(typeof n==`number`&&n>0){let r=e.type?.displayName??e.type?.name;r&&(t[r]||(t[r]=[]),t[r].push(n))}I(e.child,t),I(e.sibling,t)}function L(e){globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__||(globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__={supportsFiber:!0,inject(){},onCommitFiberRoot(){},onCommitFiberUnmount(){}});let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,n=t.onCommitFiberRoot.bind(t);t.onCommitFiberRoot=(...t)=>{let r={},i=t[1];return i?.current&&I(i.current,r),e(r),n(...t)}}function R(e){let t={};return e.include&&(t.include=e.include.map(e=>e.source)),e.exclude&&(t.exclude=e.exclude.map(e=>e.source)),e.trackAllPureComponents!=null&&(t.trackAllPureComponents=e.trackAllPureComponents),e.trackHooks!=null&&(t.trackHooks=e.trackHooks),e.trackExtraHooks&&(t.trackExtraHooks=e.trackExtraHooks.map(([,e])=>e)),e.logOnDifferentValues!=null&&(t.logOnDifferentValues=e.logOnDifferentValues),e.logOwnerReasons!=null&&(t.logOwnerReasons=e.logOwnerReasons),t}function z(e){let{wsUrl:t,projectId:n,notifier:r,...i}=e??{},a=t??`http://localhost:4649`,o=n??globalThis.location?.origin??`default`,s=0,l={};L(e=>{s++,l=e});let u=(0,c.io)(a,{reconnection:!0,reconnectionDelay:1e3,reconnectionDelayMax:3e4,transports:[`websocket`]}),d=!1;u.on(`connect`,()=>{F(`Connected to ${a}`),e&&u.emit(`config`,R(e),o)}),u.on(`disconnect`,()=>{F(`Disconnected, reconnecting...`)}),u.on(`pause`,()=>{d=!0,F(`Render collection paused`)}),u.on(`resume`,()=>{d=!1,F(`Render collection resumed`)});let f=null,p=!1;async function m(){if(p=!1,!f||f.items.length===0)return;let e=f;f=null;let t={},n=await Promise.all(e.items.map(async({info:n,error:r})=>{let i=await C(r),a=e.durations[n.displayName],o=t[n.displayName]??0;t[n.displayName]=o+1;let s=a?.[o];return{displayName:n.displayName,reason:P(n.reason),hookName:n.hookName,...i.length>0&&{stackFrames:i},...typeof s==`number`&&{actualDuration:s}}}));u.emit(`render-batch`,n,o,e.commitId)}return{...i,notifier(e){if(d){r&&r(e);return}let t=Error();f&&f.commitId===s?f.items.push({info:e,error:t}):(f&&m(),f={commitId:s,items:[{info:e,error:t}],durations:l}),p||(p=!0,queueMicrotask(m)),r&&r(e)}}}exports.buildOptions=z;
@@ -1 +1 @@
1
- import{io as e}from"socket.io-client";import t from"error-stack-parser";import{TraceMap as n,originalPositionFor as r}from"@jridgewell/trace-mapping";const i=new Map;function a(e){let t=i.get(e);if(t)return t;let r=(async()=>{try{let t=await fetch(`${e}.map`);return t.ok?new n(await t.json()):null}catch{return null}})();return i.set(e,r),r}async function o(e,t,n){let i=await a(e);if(!i)return{path:e,line:t};let o=r(i,{line:t,column:n});return o.source?{path:o.source,line:o.line??t}:{path:e,line:t}}const s=[`whyDidYouRender`,`react-dom`,`react.development`,`react.production`,`scheduler.`,`installHook`,`console.`],c=[`trackHookChanges`,`WDYRFunctionalComponent`,`Object.notifier`,`notifier`,`console.trace`],l=new Set(`renderWithHooks.mountIndeterminateComponent.updateFunctionComponent.updateForwardRef.updateMemoComponent.updateSimpleMemoComponent.beginWork.beginWork$1.completeWork.completeUnitOfWork.performUnitOfWork.runWithFiberInDEV.callComponentInDEV.workLoopSync.workLoopConcurrent.renderRootSync.renderRootConcurrent.performWorkOnRoot.performSyncWorkOnRoot.performConcurrentWorkOnRoot.commitRoot.commitRootImpl.commitMutationEffects.commitMutationEffectsOnFiber.commitLayoutEffects.commitLayoutEffectOnFiber.flushPassiveEffects.flushPassiveEffectsImpl.flushSyncWorkAcrossRoots_impl.processRootScheduleInMicrotask.scheduleUpdateOnFiber.ensureRootIsScheduled.react_stack_bottom_frame.dispatchSetState.dispatchReducerAction.dispatchAction.mountState.updateState.mountReducer.updateReducer.mountMemo.updateMemo.mountEffect.updateEffect.mountLayoutEffect.updateLayoutEffect.mountRef.updateRef.flushWork.performWorkUntilDeadline`.split(`.`));function u(e){return s.some(t=>e.includes(t))}function d(e){return c.some(t=>e===t)}function f(e){return e.replace(/WDYR$/,``)}function p(e){let t=e.startsWith(`new `)?e.slice(4):e,n=t.lastIndexOf(`.`);return f(n>=0?t.slice(n+1):t)}function m(e){return/^use[A-Z]/.test(e)}function h(e){let n;try{n=t.parse(e)}catch{return[]}let r=[];for(let e of n){let t=e.fileName??``;if(!t||u(t))continue;let n=e.functionName;if(!n||d(n))continue;let i=p(n);i&&(l.has(i)||r.push({type:m(i)?`hook`:`component`,name:i,file:t,line:e.lineNumber??0,column:e.columnNumber??0}))}return r}async function g(e){let t=h(e);return t.length===0?[]:Promise.all(t.map(async e=>({type:e.type,name:e.name,location:await o(e.file,e.line,e.column)})))}const _=Symbol.for(`react.element`),v=Symbol.for(`react.transitional.element`),y=Symbol.for(`react.memo`),b=Symbol.for(`react.forward_ref`);function x(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.$$typeof===_||t.$$typeof===v||t.$$typeof===60103}function S(e){let t=!1,n=!1,r=e;for(let e=0;e<5&&!(typeof r!=`object`||!r);e++){let e=r;if(e.$$typeof===y)t=!0,r=e.type;else if(e.$$typeof===b)n=!0,r=e.render;else break}let i=`Unknown`;return typeof r==`string`?i=r:typeof r==`function`&&(i=r.displayName||r.name||`Anonymous`),{name:i,memo:t,forwardRef:n}}function C(e,t,n){let r=S(e.type),i={};if(e.props&&typeof e.props==`object`)for(let r of Object.keys(e.props))r!==`children`&&(i[r]=w(e.props[r],t,n+1));return{type:`react-node`,component:r,props:i}}function w(e,t,n){if(e==null)return null;if(typeof e==`function`)return{type:`function`,name:e.name||`anonymous`};if(typeof e==`boolean`)return e;if(typeof e==`number`)return Number.isNaN(e)?`NaN`:Number.isFinite(e)?Object.is(e,-0)?`-0`:e:e>0?`Infinity`:`-Infinity`;if(typeof e==`string`)return e;if(typeof e==`bigint`||typeof e==`symbol`)return e.toString();if(t.has(e))return`[Circular]`;if(n>=8)return`[MaxDepth]`;if(t.add(e),x(e))return C(e,t,n);if(Array.isArray(e))return e.map(e=>w(e,t,n+1));let r=Object.getPrototypeOf(e)?.constructor?.name;if(r&&r!==`Object`){if(e instanceof Date)return e.toISOString();if(e instanceof RegExp)return String(e);if(e instanceof Map){let r={};for(let[i,a]of e.entries())r[String(i)]=w(a,t,n+1);return{type:`Map`,entries:r}}if(e instanceof Set)return{type:`Set`,values:[...e].map(e=>w(e,t,n+1))};if(e instanceof Promise)return`Promise`;if(e instanceof Error)return{type:`Error`,name:e.name,message:e.message};if(typeof Node<`u`&&e instanceof Node&&e instanceof Element){let t={};for(let n of e.attributes)t[n.name]=n.value;return{type:`dom`,tagName:e.tagName.toLowerCase(),attrs:t}}return{type:`class`,name:r}}let i={};for(let r of Object.keys(e))i[r]=w(e[r],t,n+1);return i}function T(e){return w(e,new WeakSet,0)}function E(e){return Array.isArray(e)?e.map(e=>({pathString:e.pathString,diffType:e.diffType,prevValue:T(e.prevValue),nextValue:T(e.nextValue)})):!1}function D(e){return{propsDifferences:E(e.propsDifferences),stateDifferences:E(e.stateDifferences),hookDifferences:E(e.hookDifferences)}}function O(e){console.log(`%c[WDYR MCP]%c ${e}`,`color: #38bdf8; font-weight: bold`,`color: inherit; font-weight: normal`)}function k(e){globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__||(globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__={supportsFiber:!0,inject(){},onCommitFiberRoot(){},onCommitFiberUnmount(){}});let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,n=t.onCommitFiberRoot.bind(t);t.onCommitFiberRoot=(...t)=>(e(),n(...t))}function A(e){let t={};return e.include&&(t.include=e.include.map(e=>e.source)),e.exclude&&(t.exclude=e.exclude.map(e=>e.source)),e.trackAllPureComponents!=null&&(t.trackAllPureComponents=e.trackAllPureComponents),e.trackHooks!=null&&(t.trackHooks=e.trackHooks),e.trackExtraHooks&&(t.trackExtraHooks=e.trackExtraHooks.map(([,e])=>e)),e.logOnDifferentValues!=null&&(t.logOnDifferentValues=e.logOnDifferentValues),e.logOwnerReasons!=null&&(t.logOwnerReasons=e.logOwnerReasons),t}function j(t){let{wsUrl:n,projectId:r,notifier:i,...a}=t??{},o=n??`http://localhost:4649`,s=r??globalThis.location?.origin??`default`,c=0;k(()=>{c++});let l=e(o,{reconnection:!0,reconnectionDelay:1e3,reconnectionDelayMax:3e4,transports:[`websocket`]}),u=!1;l.on(`connect`,()=>{O(`Connected to ${o}`),t&&l.emit(`config`,A(t),s)}),l.on(`disconnect`,()=>{O(`Disconnected, reconnecting...`)}),l.on(`pause`,()=>{u=!0,O(`Render collection paused`)}),l.on(`resume`,()=>{u=!1,O(`Render collection resumed`)});let d=null,f=!1;async function p(){if(f=!1,!d||d.items.length===0)return;let e=d;d=null;let t=await Promise.all(e.items.map(async({info:e,error:t})=>{let n=await g(t);return{displayName:e.displayName,reason:D(e.reason),hookName:e.hookName,...n.length>0&&{stackFrames:n}}}));l.emit(`render-batch`,t,s,e.commitId)}return{...a,notifier(e){if(u){i&&i(e);return}let t=Error();d&&d.commitId===c?d.items.push({info:e,error:t}):(d&&p(),d={commitId:c,items:[{info:e,error:t}]}),f||(f=!0,queueMicrotask(p)),i&&i(e)}}}export{j as buildOptions};
1
+ import{io as e}from"socket.io-client";import t from"error-stack-parser";import{TraceMap as n,originalPositionFor as r}from"@jridgewell/trace-mapping";const i=new Map;function a(e){let t=i.get(e);if(t)return t;let r=(async()=>{try{let t=await fetch(`${e}.map`);return t.ok?new n(await t.json()):null}catch{return null}})();return i.set(e,r),r}async function o(e,t,n){let i=await a(e);if(!i)return{path:e,line:t};let o=r(i,{line:t,column:n});return o.source?{path:o.source,line:o.line??t}:{path:e,line:t}}const s=[`whyDidYouRender`,`react-dom`,`react.development`,`react.production`,`scheduler.`,`installHook`,`console.`],c=[`trackHookChanges`,`WDYRFunctionalComponent`,`Object.notifier`,`notifier`,`console.trace`],l=new Set(`renderWithHooks.mountIndeterminateComponent.updateFunctionComponent.updateForwardRef.updateMemoComponent.updateSimpleMemoComponent.beginWork.beginWork$1.completeWork.completeUnitOfWork.performUnitOfWork.runWithFiberInDEV.callComponentInDEV.workLoopSync.workLoopConcurrent.renderRootSync.renderRootConcurrent.performWorkOnRoot.performSyncWorkOnRoot.performConcurrentWorkOnRoot.commitRoot.commitRootImpl.commitMutationEffects.commitMutationEffectsOnFiber.commitLayoutEffects.commitLayoutEffectOnFiber.flushPassiveEffects.flushPassiveEffectsImpl.flushSyncWorkAcrossRoots_impl.processRootScheduleInMicrotask.scheduleUpdateOnFiber.ensureRootIsScheduled.react_stack_bottom_frame.dispatchSetState.dispatchReducerAction.dispatchAction.mountState.updateState.mountReducer.updateReducer.mountMemo.updateMemo.mountEffect.updateEffect.mountLayoutEffect.updateLayoutEffect.mountRef.updateRef.flushWork.performWorkUntilDeadline`.split(`.`));function u(e){return s.some(t=>e.includes(t))}function d(e){return c.some(t=>e===t)}function f(e){return e.replace(/WDYR$/,``)}function p(e){let t=e.startsWith(`new `)?e.slice(4):e,n=t.lastIndexOf(`.`);return f(n>=0?t.slice(n+1):t)}function m(e){return/^use[A-Z]/.test(e)}function h(e){let n;try{n=t.parse(e)}catch{return[]}let r=[];for(let e of n){let t=e.fileName??``;if(!t||u(t))continue;let n=e.functionName;if(!n||d(n))continue;let i=p(n);i&&(l.has(i)||r.push({type:m(i)?`hook`:`component`,name:i,file:t,line:e.lineNumber??0,column:e.columnNumber??0}))}return r}async function g(e){let t=h(e);return t.length===0?[]:Promise.all(t.map(async e=>({type:e.type,name:e.name,location:await o(e.file,e.line,e.column)})))}const _=Symbol.for(`react.element`),v=Symbol.for(`react.transitional.element`),y=Symbol.for(`react.memo`),b=Symbol.for(`react.forward_ref`);function x(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.$$typeof===_||t.$$typeof===v||t.$$typeof===60103}function S(e){let t=!1,n=!1,r=e;for(let e=0;e<5&&!(typeof r!=`object`||!r);e++){let e=r;if(e.$$typeof===y)t=!0,r=e.type;else if(e.$$typeof===b)n=!0,r=e.render;else break}let i=`Unknown`;return typeof r==`string`?i=r:typeof r==`function`&&(i=r.displayName||r.name||`Anonymous`),{name:i,memo:t,forwardRef:n}}function C(e,t,n){let r=S(e.type),i={};if(e.props&&typeof e.props==`object`)for(let r of Object.keys(e.props))r!==`children`&&(i[r]=w(e.props[r],t,n+1));return{type:`react-node`,component:r,props:i}}function w(e,t,n){if(e==null)return null;if(typeof e==`function`)return{type:`function`,name:e.name||`anonymous`};if(typeof e==`boolean`)return e;if(typeof e==`number`)return Number.isNaN(e)?`NaN`:Number.isFinite(e)?Object.is(e,-0)?`-0`:e:e>0?`Infinity`:`-Infinity`;if(typeof e==`string`)return e;if(typeof e==`bigint`||typeof e==`symbol`)return e.toString();if(t.has(e))return`[Circular]`;if(n>=8)return`[MaxDepth]`;if(t.add(e),x(e))return C(e,t,n);if(Array.isArray(e))return e.map(e=>w(e,t,n+1));let r=Object.getPrototypeOf(e)?.constructor?.name;if(r&&r!==`Object`){if(e instanceof Date)return e.toISOString();if(e instanceof RegExp)return String(e);if(e instanceof Map){let r={};for(let[i,a]of e.entries())r[String(i)]=w(a,t,n+1);return{type:`Map`,entries:r}}if(e instanceof Set)return{type:`Set`,values:[...e].map(e=>w(e,t,n+1))};if(e instanceof Promise)return`Promise`;if(e instanceof Error)return{type:`Error`,name:e.name,message:e.message};if(typeof Node<`u`&&e instanceof Node&&e instanceof Element){let t={};for(let n of e.attributes)t[n.name]=n.value;return{type:`dom`,tagName:e.tagName.toLowerCase(),attrs:t}}return{type:`class`,name:r}}let i={};for(let r of Object.keys(e))i[r]=w(e[r],t,n+1);return i}function T(e){return w(e,new WeakSet,0)}function E(e){return Array.isArray(e)?e.map(e=>({pathString:e.pathString,diffType:e.diffType,prevValue:T(e.prevValue),nextValue:T(e.nextValue)})):!1}function D(e){return{propsDifferences:E(e.propsDifferences),stateDifferences:E(e.stateDifferences),hookDifferences:E(e.hookDifferences)}}function O(e){console.log(`%c[WDYR MCP]%c ${e}`,`color: #38bdf8; font-weight: bold`,`color: inherit; font-weight: normal`)}function k(e,t){if(e==null)return;let n=e.actualDuration;if(typeof n==`number`&&n>0){let r=e.type?.displayName??e.type?.name;r&&(t[r]||(t[r]=[]),t[r].push(n))}k(e.child,t),k(e.sibling,t)}function A(e){globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__||(globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__={supportsFiber:!0,inject(){},onCommitFiberRoot(){},onCommitFiberUnmount(){}});let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,n=t.onCommitFiberRoot.bind(t);t.onCommitFiberRoot=(...t)=>{let r={},i=t[1];return i?.current&&k(i.current,r),e(r),n(...t)}}function j(e){let t={};return e.include&&(t.include=e.include.map(e=>e.source)),e.exclude&&(t.exclude=e.exclude.map(e=>e.source)),e.trackAllPureComponents!=null&&(t.trackAllPureComponents=e.trackAllPureComponents),e.trackHooks!=null&&(t.trackHooks=e.trackHooks),e.trackExtraHooks&&(t.trackExtraHooks=e.trackExtraHooks.map(([,e])=>e)),e.logOnDifferentValues!=null&&(t.logOnDifferentValues=e.logOnDifferentValues),e.logOwnerReasons!=null&&(t.logOwnerReasons=e.logOwnerReasons),t}function M(t){let{wsUrl:n,projectId:r,notifier:i,...a}=t??{},o=n??`http://localhost:4649`,s=r??globalThis.location?.origin??`default`,c=0,l={};A(e=>{c++,l=e});let u=e(o,{reconnection:!0,reconnectionDelay:1e3,reconnectionDelayMax:3e4,transports:[`websocket`]}),d=!1;u.on(`connect`,()=>{O(`Connected to ${o}`),t&&u.emit(`config`,j(t),s)}),u.on(`disconnect`,()=>{O(`Disconnected, reconnecting...`)}),u.on(`pause`,()=>{d=!0,O(`Render collection paused`)}),u.on(`resume`,()=>{d=!1,O(`Render collection resumed`)});let f=null,p=!1;async function m(){if(p=!1,!f||f.items.length===0)return;let e=f;f=null;let t={},n=await Promise.all(e.items.map(async({info:n,error:r})=>{let i=await g(r),a=e.durations[n.displayName],o=t[n.displayName]??0;t[n.displayName]=o+1;let s=a?.[o];return{displayName:n.displayName,reason:D(n.reason),hookName:n.hookName,...i.length>0&&{stackFrames:i},...typeof s==`number`&&{actualDuration:s}}}));u.emit(`render-batch`,n,s,e.commitId)}return{...a,notifier(e){if(d){i&&i(e);return}let t=Error();f&&f.commitId===c?f.items.push({info:e,error:t}):(f&&m(),f={commitId:c,items:[{info:e,error:t}],durations:l}),p||(p=!0,queueMicrotask(m)),i&&i(e)}}}export{M as buildOptions};
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as n}from"zod";import{existsSync as r,mkdirSync as i,readFileSync as a,readdirSync as o,unlinkSync as s,writeFileSync as c}from"node:fs";import{homedir as l}from"node:os";import{join as u}from"node:path";import d from"xxhash-wasm";import f from"node:http";import{Server as p}from"socket.io";const m=`@@dict`,h=`@@ref:`;let g;const _=d().then(e=>{g=e.h64ToString});function v(){return _}function y(e){return g(JSON.stringify(e))}function b(e){return typeof e==`object`&&!!e}function x(e,t){return e?e.map(e=>{let{prevValue:n,nextValue:r}=e;if(b(n)){let e=y(n);t[e]??=n,n=`${h}${e}`}if(b(r)){let e=y(r);t[e]??=r,r=`${h}${e}`}return n===e.prevValue&&r===e.nextValue?e:{...e,prevValue:n,nextValue:r}}):!1}function S(e,t){let{propsDifferences:n,stateDifferences:r,hookDifferences:i}=e.reason,a=x(n,t),o=x(r,t),s=x(i,t);return a===n&&o===r&&s===i?e:{...e,reason:{propsDifferences:a,stateDifferences:o,hookDifferences:s}}}function C(e,t){return e?e.map(e=>{let{prevValue:n,nextValue:r}=e;return typeof n==`string`&&n.startsWith(h)&&(n=t[n.slice(6)]??n),typeof r==`string`&&r.startsWith(h)&&(r=t[r.slice(6)]??r),n===e.prevValue&&r===e.nextValue?e:{...e,prevValue:n,nextValue:r}}):!1}function w(e,t){let{propsDifferences:n,stateDifferences:r,hookDifferences:i}=e.reason,a=C(n,t),o=C(r,t),s=C(i,t);return a===n&&o===r&&s===i?e:{...e,reason:{propsDifferences:a,stateDifferences:o,hookDifferences:s}}}function T(e){if(!r(e))return[];let t=a(e,`utf-8`).split(`
3
- `).filter(Boolean);if(t.length===0)return[];let n,i=0,o=JSON.parse(t[0]);`@@dict`in o&&(n=o[m],i=1);let s=t.slice(i).map(e=>JSON.parse(e));return n?s.map(e=>w(e,n)):s}function E(e){return e.replaceAll(/[^a-zA-Z0-9_.-]/g,`_`)}function D(e){return{project:e.projectId,displayName:e.displayName,reason:e.reason,...e.hookName!=null&&{hookName:e.hookName},...e.commitId!=null&&{commitId:e.commitId},...e.timestamp!=null&&{timestamp:e.timestamp},...e.stackFrames!=null&&e.stackFrames.length>0&&{stackFrames:e.stackFrames}}}const O=`nocommit`,k=new class{dir;buffers=new Map;timers=new Map;dicts=new Map;bufferMeta=new Map;trackedComponents=new Map;wdyrConfigs=new Map;constructor(e){this.dir=e??u(l(),`.wdyr-mcp`,`renders`),i(this.dir,{recursive:!0})}addRender(e,t,n){let r={...e,projectId:t,timestamp:Date.now(),...n!=null&&{commitId:n}},i=this.bufferKey(t,n),a=this.buffers.get(i);a||(a=[],this.buffers.set(i,a),this.bufferMeta.set(i,{projectId:t,commitId:n})),a.push(r);let o=this.timers.get(i);o&&clearTimeout(o),this.timers.set(i,setTimeout(()=>{this.flushAsync(t,n).catch(e=>console.error(`[wdyr-mcp] flush error for ${i}:`,e))},200))}async flushAsync(e,t){await v(),this.flush(e,t)}flush(e,t){if(e!=null&&t!==void 0)this.flushBuffer(this.bufferKey(e,t));else if(e!=null)for(let t of this.bufferKeysForProject(e))this.flushBuffer(t);else for(let e of[...this.buffers.keys()])this.flushBuffer(e)}flushBuffer(e){let t=this.buffers.get(e);if(!t||t.length===0)return;let n=this.bufferMeta.get(e);if(!n)return;let r=this.dicts.get(e);r||(r={},this.dicts.set(e,r));let i=t.map(e=>S(e,r)),a=this.commitFile(n.projectId,n.commitId),o=this.readDataLines(a),s=i.map(e=>JSON.stringify(e)),l=[...o,...s];c(a,`${(Object.keys(r).length>0?[JSON.stringify({[m]:r}),...l]:l).join(`
4
- `)}\n`),t.length=0;let u=this.timers.get(e);u&&(clearTimeout(u),this.timers.delete(e))}readDataLines(e){return r(e)?a(e,`utf-8`).split(`
5
- `).filter(e=>!(!e||e.startsWith(`{"@@dict"`))):[]}getAllRenders(e){return this.flush(e),e?this.projectFiles(e).flatMap(e=>T(u(this.dir,e)).map(D)):this.jsonlFiles().flatMap(e=>T(u(this.dir,e)).map(D))}getRendersByComponent(e,t){return this.getAllRenders(t).filter(t=>t.displayName===e)}clearRenders(e){if(e){for(let t of this.bufferKeysForProject(e)){this.buffers.delete(t),this.dicts.delete(t),this.bufferMeta.delete(t);let e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}for(let t of this.projectFiles(e))s(u(this.dir,t))}else{for(let[,e]of this.timers)clearTimeout(e);this.buffers.clear(),this.timers.clear(),this.dicts.clear(),this.bufferMeta.clear();for(let e of this.jsonlFiles())s(u(this.dir,e))}}clearRendersByComponent(e,t){this.flush(t);let n=t?this.projectFiles(t):this.jsonlFiles(),r=0;for(let t of n){let n=u(this.dir,t),i=T(n),a=i.length,o=i.filter(t=>t.displayName!==e);r+=a-o.length,o.length===0?(s(n),this.clearBuffersForFile(t)):o.length<a&&(this.rewriteFile(n,o),this.clearBuffersForFile(t))}return r}clearRendersByCommit(e,t){this.flush(t);let n=t?this.projectFiles(t):this.jsonlFiles(),r=0;for(let t of n){let n=this.parseFilename(t);n?.commitId!=null&&n.commitId<e&&(s(u(this.dir,t)),this.clearBuffersForFile(t),r++)}return r}getProjects(){this.flush();let e=new Set,t=new Set;for(let n of this.jsonlFiles()){let r=this.parseFilename(n);if(!r||t.has(r.projectSanitized))continue;t.add(r.projectSanitized);let i=a(u(this.dir,n),`utf-8`).split(`
6
- `);for(let t of i){if(!t)continue;let n=JSON.parse(t);if(!(`@@dict`in n)){e.add(n.projectId);break}}}return[...e]}getCommitIds(e){this.flush(e);let t=e?this.projectFiles(e):this.jsonlFiles(),n=new Set;for(let e of t){let t=this.parseFilename(e);t?.commitId!=null&&n.add(t.commitId)}return[...n].sort((e,t)=>e-t)}getCommits(e){this.flush(e);let t=e?this.projectFiles(e):this.jsonlFiles(),n=[];for(let e of t){let t=this.parseFilename(e);if(t?.commitId==null)continue;let r=T(u(this.dir,e));r.length!==0&&n.push({commitId:t.commitId,timestamp:r.find(e=>e.timestamp!=null)?.timestamp??null,renderCount:r.length,components:[...new Set(r.map(e=>e.displayName))]})}return n.sort((e,t)=>e.commitId-t.commitId)}getRendersByCommit(e,t){if(t)return this.flush(t,e),T(this.commitFile(t,e)).map(D);this.flush();let n=`_commit_${e}.jsonl`;return this.jsonlFiles().filter(e=>e.endsWith(n)).flatMap(e=>T(u(this.dir,e)).map(D))}getSummary(e){let t=this.getAllRenders(e),n={};for(let e of t){n[e.project]??={};let t=n[e.project];t[e.displayName]??={count:0,reasons:{props:0,state:0,hooks:0}};let r=t[e.displayName];r.count++,Array.isArray(e.reason.propsDifferences)&&r.reasons.props++,Array.isArray(e.reason.stateDifferences)&&r.reasons.state++,Array.isArray(e.reason.hookDifferences)&&r.reasons.hooks++}return n}getSummaryByCommit(e){let t=this.getAllRenders(e),n={};for(let e of t){if(e.commitId==null)continue;n[e.project]??={},n[e.project][e.commitId]??={};let t=n[e.project][e.commitId];t[e.displayName]??={count:0,reasons:{props:0,state:0,hooks:0}};let r=t[e.displayName];r.count++,Array.isArray(e.reason.propsDifferences)&&r.reasons.props++,Array.isArray(e.reason.stateDifferences)&&r.reasons.state++,Array.isArray(e.reason.hookDifferences)&&r.reasons.hooks++}return n}setTrackedComponents(e,t){this.trackedComponents.set(t,e)}getTrackedComponents(e){let t={},n=e?[e]:this.getProjects();for(let e of n){let n=[...new Set(this.getAllRenders(e).map(e=>e.displayName))];t[e]={registered:this.trackedComponents.get(e)??[],observed:n}}return t}setWdyrConfig(e,t){this.wdyrConfigs.set(t,e)}getWdyrConfig(e){let t={};if(e){let n=this.wdyrConfigs.get(e);n&&(t[e]=n)}else for(let[e,n]of this.wdyrConfigs)t[e]=n;return t}rewriteFile(e,t){c(e,`${t.map(e=>JSON.stringify(e)).join(`
7
- `)}\n`)}clearBuffersForFile(e){let t=this.parseFilename(e);if(t)for(let[e,n]of this.bufferMeta){if(E(n.projectId)!==t.projectSanitized||!(t.commitId==null?n.commitId==null:n.commitId===t.commitId))continue;this.buffers.delete(e),this.dicts.delete(e),this.bufferMeta.delete(e);let r=this.timers.get(e);r&&(clearTimeout(r),this.timers.delete(e))}}bufferKey(e,t){return`${e}\0${t??O}`}bufferKeysForProject(e){let t=`${e}\0`;return[...this.buffers.keys()].filter(e=>e.startsWith(t))}commitFile(e,t){let n=E(e),r=t==null?`_${O}`:`_commit_${t}`;return u(this.dir,`${n}${r}.jsonl`)}projectFiles(e){let t=E(e);return o(this.dir).filter(e=>e.startsWith(t)&&e.endsWith(`.jsonl`))}parseFilename(e){if(!e.endsWith(`.jsonl`))return null;let t=e.slice(0,-6),n=t.match(/^(.+)_commit_(\d+)$/);if(n)return{projectSanitized:n[1],commitId:Number(n[2])};let r=t.match(/^(.+)_nocommit$/);return r?{projectSanitized:r[1]}:{projectSanitized:t}}jsonlFiles(){return o(this.dir).filter(e=>e.endsWith(`.jsonl`))}};function A(e){if(e)return{projectId:e};let t=k.getProjects();return t.length===0?{projectId:void 0}:t.length===1?{projectId:t[0]}:{projectId:void 0,error:[`Multiple projects are recording render data. Ask the user which project they are working on (e.g. their dev server URL like http://localhost:3000).`,``,`Active projects:`,...t.map(e=>`- ${e}`)].join(`
8
- `)}}function j(e){return{content:[{type:`text`,text:e}]}}function M(e){e.registerTool(`clear_renders`,{title:`Clear Renders`,description:`Clears collected render data. Supports filtering by component name or by commit ID threshold. When no filter is given, clears all data. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),component:n.string().optional().describe(`Clear only renders for this component (by displayName).`),beforeCommit:n.number().optional().describe(`Clear all renders from commits with an ID strictly less than this value.`)}},async({project:e,component:t,beforeCommit:n})=>{let r=A(e);return r.error?j(r.error):t?j(`Cleared ${k.clearRendersByComponent(t,r.projectId)} render(s) for component "${t}".`):n==null?(k.clearRenders(r.projectId),j(r.projectId?`Render data cleared for ${r.projectId}.`:`All render data cleared.`)):j(`Cleared renders from ${k.clearRendersByCommit(n,r.projectId)} commit file(s) before commit #${n}.`)})}function N(e){e.registerTool(`get_commits`,{title:`Get Commits`,description:`Returns a list of React commit IDs that have recorded render data for a project. Use these IDs with get_renders_by_commit to inspect individual commits.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=A(e);if(t.error)return j(t.error);let n=k.getCommits(t.projectId);return n.length===0?j(`No commits recorded yet. Make sure the browser is connected and triggering re-renders.`):j(JSON.stringify(n,null,2))})}function P(e){e.registerTool(`get_projects`,{title:`Get Projects`,description:`Returns a list of project identifiers (browser origin URLs) that have recorded render data.`,inputSchema:{}},async()=>{let e=k.getProjects();return e.length===0?j(`No projects have recorded render data yet.`):j(`Active projects:\n${e.map(e=>`- ${e}`).join(`
9
- `)}`)})}function F(e){e.registerTool(`get_render_summary`,{title:`Get Render Summary`,description:`Returns a summary of re-renders grouped by component name with counts. Use groupBy: 'commit' to get per-commit breakdowns instead of a single aggregate. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),groupBy:n.enum([`commit`]).optional().describe(`Group results by commit. When set to 'commit', returns per-commit render summaries instead of a single aggregate.`)}},async({project:e,groupBy:t})=>{let n=A(e);return n.error?j(n.error):t===`commit`?R(n.projectId):L(n.projectId)})}function I(e){let t=[];return e.props>0&&t.push(`props: ${e.props}`),e.state>0&&t.push(`state: ${e.state}`),e.hooks>0&&t.push(`hooks: ${e.hooks}`),t.length>0?` — ${t.join(`, `)}`:``}function L(e){let t=k.getSummary(e);if(Object.keys(t).length===0)return j(`No renders recorded yet.`);let n=[];for(let[e,r]of Object.entries(t)){n.push(`[${e}]`);for(let[e,{count:t,reasons:i}]of Object.entries(r))n.push(` ${e}: ${t} re-render(s)${I(i)}`)}return j(`Re-render summary:\n\n${n.join(`
10
- `)}`)}function R(e){let t=k.getSummaryByCommit(e);if(Object.keys(t).length===0)return j(`No renders with commit IDs recorded yet.`);let n=[];for(let[e,r]of Object.entries(t)){n.push(`[${e}]`);let t=Object.keys(r).map(Number).sort((e,t)=>e-t);for(let e of t){let t=r[e],i=Object.values(t).reduce((e,t)=>e+t.count,0);n.push(` Commit #${e} (${i} re-render(s)):`);for(let[e,{count:r,reasons:i}]of Object.entries(t))n.push(` ${e}: ${r}${I(i)}`)}}return j(`Re-render summary (by commit):\n\n${n.join(`
11
- `)}`)}function z(e){e.registerTool(`get_renders_by_commit`,{title:`Get Renders by Commit`,description:`Returns all re-renders for a specific React commit ID, including stack traces that show the hook chain and component tree that triggered each render. Use the stackFrames field to locate the exact source file and line. Use get_commits first to discover available commit IDs.`,inputSchema:{commitId:n.number().describe(`The React commit ID to filter by.`),component:n.string().optional().describe(`Filter by component name. Omit to get all renders.`),project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({commitId:e,component:t,project:n})=>{let r=A(n);if(r.error)return j(r.error);let i=k.getRendersByCommit(e,r.projectId);return t&&(i=i.filter(e=>e.displayName===t)),i.length===0?j(t?`No renders recorded for component "${t}" in commit ${e}.`:`No renders recorded for commit ${e}.`):j(JSON.stringify(i,null,2))})}function B(e){e.registerTool(`get_renders`,{title:`Get Renders`,description:`Returns all re-renders collected from the browser, including stack traces that show the hook chain and component tree that triggered each render. Use the stackFrames field to locate the exact source file and line. If multiple projects are active and no project is specified, the tool will ask you to disambiguate by asking the user for their dev server URL.`,inputSchema:{component:n.string().optional().describe(`Filter by component name. Omit to get all renders.`),project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({component:e,project:t})=>{let n=A(t);if(n.error)return j(n.error);let r=e?k.getRendersByComponent(e,n.projectId):k.getAllRenders(n.projectId);return r.length===0?j(e?`No renders recorded for "${e}".`:`No renders recorded yet. Make sure the browser is connected and triggering re-renders.`):j(JSON.stringify(r,null,2))})}function V(e){let t=[];if(e.include?.length){t.push(` include:`);for(let n of e.include)t.push(` - /${n}/`)}if(e.exclude?.length){t.push(` exclude:`);for(let n of e.exclude)t.push(` - /${n}/`)}if(e.trackAllPureComponents!=null&&t.push(` trackAllPureComponents: ${e.trackAllPureComponents}`),e.trackHooks!=null&&t.push(` trackHooks: ${e.trackHooks}`),e.trackExtraHooks?.length){t.push(` trackExtraHooks:`);for(let n of e.trackExtraHooks)t.push(` - ${n}`)}return e.logOnDifferentValues!=null&&t.push(` logOnDifferentValues: ${e.logOnDifferentValues}`),e.logOwnerReasons!=null&&t.push(` logOwnerReasons: ${e.logOwnerReasons}`),t}function H(e){e.registerTool(`get_tracked_components`,{title:`Get Tracked Components`,description:`Returns the why-did-you-render configuration for the connected project, including include/exclude filters and tracking options. Also shows components observed in render data. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=A(e);if(t.error)return j(t.error);let n=k.getWdyrConfig(t.projectId),r=k.getTrackedComponents(t.projectId),i=Object.keys(n).length>0,a=Object.keys(r).length>0;if(!i&&!a)return j(`No configuration or tracked components found. Make sure the browser is connected and triggering re-renders.`);let o=[],s=new Set([...Object.keys(n),...Object.keys(r)]);for(let e of s){o.push(`[${e}]`);let t=n[e];if(t){o.push(`Configuration:`);let e=V(t);e.length>0?o.push(...e):o.push(` (default options)`)}let i=r[e];if(i?.observed.length){o.push(`Observed in renders:`);for(let e of i.observed)o.push(` - ${e}`)}}return j(o.join(`
12
- `))})}let U=null;function W(e){U=e}function G(){return U}function K(e){e.registerTool(`pause_renders`,{title:`Pause Render Collection`,description:`Pauses render data collection in the browser. Connected clients will stop reporting renders until resume_renders is called. Useful when you want to ignore renders from irrelevant interactions. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=A(e);if(t.error)return j(t.error);let n=G();if(!n)return j(`WebSocket server is not running. Another MCP instance owns the WS server.`);if(t.projectId){let e=await n.fetchSockets();for(let n of e)n.data.projectId===t.projectId&&n.emit(`pause`);return j(`Paused render collection for ${t.projectId}.`)}return n.emit(`pause`),j(`Paused render collection for all projects.`)})}function q(e){e.registerTool(`resume_renders`,{title:`Resume Render Collection`,description:`Resumes render data collection that was previously paused with pause_renders. Connected clients will start reporting renders again. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:n.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=A(e);if(t.error)return j(t.error);let n=G();if(!n)return j(`WebSocket server is not running. Another MCP instance owns the WS server.`);if(t.projectId){let e=await n.fetchSockets();for(let n of e)n.data.projectId===t.projectId&&n.emit(`resume`);return j(`Resumed render collection for ${t.projectId}.`)}return n.emit(`resume`),j(`Resumed render collection for all projects.`)})}function J(e){B(e),F(e),N(e),z(e),P(e),H(e),M(e),K(e),q(e)}const Y=3e3;function X(e,t){e.on(`connection`,n=>{console.error(`[wdyr-mcp] browser connected (http://localhost:${t})`),n.data.projectId=null,n.on(`render`,(e,t,r)=>{n.data.projectId=t,k.addRender(e,t,r)}),n.on(`render-batch`,(e,t,r)=>{n.data.projectId=t;for(let n of e)k.addRender(n,t,r)}),n.on(`register`,(e,t)=>{n.data.projectId=t,k.setTrackedComponents(e,t)}),n.on(`config`,(e,t)=>{n.data.projectId=t,k.setWdyrConfig(e,t)}),n.on(`disconnect`,()=>{console.error(`[wdyr-mcp] browser disconnected`);let t=n.data.projectId;t&&([...e.sockets.sockets.values()].some(e=>e.id!==n.id&&e.data.projectId===t)||(console.error(`[wdyr-mcp] last client for ${t} disconnected, clearing render data`),k.clearRenders(t)))})})}function Z(e){let t=null,n=null,r=null,i=!1;function a(){i||(r=f.createServer(),n=new p(r,{cors:{origin:`*`},serveClient:!1,transports:[`websocket`],maxHttpBufferSize:5e7}),X(n,e),r.once(`error`,t=>{t.code===`EADDRINUSE`?(console.error(`[wdyr-mcp] Port ${e} in use, will retry every ${Y/1e3}s`),n?.close(),n=null,W(null),r=null,o()):console.error(`[wdyr-mcp] server error:`,t)}),r.listen(e,`127.0.0.1`,()=>{console.error(`[wdyr-mcp] socket.io server listening on http://localhost:${e}`),W(n),s()}))}function o(){t||i||(t=setInterval(a,Y))}function s(){t&&=(clearInterval(t),null)}return a(),{close(){i=!0,s(),n?.close(),W(null)}}}const Q=new e({name:`why-did-you-render`,version:`0.0.0`});J(Q);async function $(){let e=Z(Number(process.env.WDYR_WS_PORT)||4649),n=new t;await Q.connect(n),console.error(`[wdyr-mcp] MCP server running on stdio`);let r=!1;async function i(){r||(r=!0,console.error(`[wdyr-mcp] Shutting down…`),e.close(),await Q.close(),process.exit(0))}process.stdin.on(`end`,i),process.on(`SIGTERM`,i),process.on(`SIGINT`,i)}$().catch(e=>{console.error(`[wdyr-mcp] Fatal error:`,e),process.exit(1)});export{};
2
+ import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{io as n}from"socket.io-client";import{z as r}from"zod";import{existsSync as i,mkdirSync as a,readFileSync as o,readdirSync as s,unlinkSync as c,writeFileSync as l}from"node:fs";import{homedir as u}from"node:os";import{join as d}from"node:path";import ee from"xxhash-wasm";import f from"node:http";import{Server as te}from"socket.io";let p=null,m=4649;function h(e){m=e}function g(){return p||=n(`http://127.0.0.1:${m}`,{transports:[`websocket`],reconnection:!0,reconnectionDelay:1e3,reconnectionAttempts:3}),p}function _(e){g().emit(`relay-pause`,e)}function v(e){g().emit(`relay-resume`,e)}function y(){p&&=(p.close(),null)}const b=`@@dict`,x=`@@ref:`;let S;const C=ee().then(e=>{S=e.h64ToString});function w(){return C}function T(e){return S(JSON.stringify(e))}function E(e){return typeof e==`object`&&!!e}function D(e,t){return e?e.map(e=>{let{prevValue:n,nextValue:r}=e;if(E(n)){let e=T(n);t[e]??=n,n=`${x}${e}`}if(E(r)){let e=T(r);t[e]??=r,r=`${x}${e}`}return n===e.prevValue&&r===e.nextValue?e:{...e,prevValue:n,nextValue:r}}):!1}function ne(e,t){let{propsDifferences:n,stateDifferences:r,hookDifferences:i}=e.reason,a=D(n,t),o=D(r,t),s=D(i,t);return a===n&&o===r&&s===i?e:{...e,reason:{propsDifferences:a,stateDifferences:o,hookDifferences:s}}}function O(e,t){return e?e.map(e=>{let{prevValue:n,nextValue:r}=e;return typeof n==`string`&&n.startsWith(x)&&(n=t[n.slice(6)]??n),typeof r==`string`&&r.startsWith(x)&&(r=t[r.slice(6)]??r),n===e.prevValue&&r===e.nextValue?e:{...e,prevValue:n,nextValue:r}}):!1}function k(e,t){let{propsDifferences:n,stateDifferences:r,hookDifferences:i}=e.reason,a=O(n,t),o=O(r,t),s=O(i,t);return a===n&&o===r&&s===i?e:{...e,reason:{propsDifferences:a,stateDifferences:o,hookDifferences:s}}}function A(e){if(!i(e))return[];let t=o(e,`utf-8`).split(`
3
+ `).filter(Boolean);if(t.length===0)return[];let n,r=0,a=JSON.parse(t[0]);`@@dict`in a&&(n=a[b],r=1);let s=t.slice(r).map(e=>JSON.parse(e));return n?s.map(e=>k(e,n)):s}function j(e){return e.replaceAll(/[^a-zA-Z0-9_.-]/g,`_`)}function M(e){return{project:e.projectId,displayName:e.displayName,reason:e.reason,...e.hookName!=null&&{hookName:e.hookName},...e.commitId!=null&&{commitId:e.commitId},...e.timestamp!=null&&{timestamp:e.timestamp},...e.stackFrames!=null&&e.stackFrames.length>0&&{stackFrames:e.stackFrames}}}const N=`nocommit`,P=new class{dir;buffers=new Map;timers=new Map;dicts=new Map;bufferMeta=new Map;trackedComponents=new Map;wdyrConfigs=new Map;constructor(e){this.dir=e??d(u(),`.wdyr-mcp`,`renders`),a(this.dir,{recursive:!0})}addRender(e,t,n){let r={...e,projectId:t,timestamp:Date.now(),...n!=null&&{commitId:n}},i=this.bufferKey(t,n),a=this.buffers.get(i);a||(a=[],this.buffers.set(i,a),this.bufferMeta.set(i,{projectId:t,commitId:n})),a.push(r);let o=this.timers.get(i);o&&clearTimeout(o),this.timers.set(i,setTimeout(()=>{this.flushAsync(t,n).catch(e=>console.error(`[wdyr-mcp] flush error for ${i}:`,e))},200))}async flushAsync(e,t){await w(),this.flush(e,t)}flush(e,t){if(e!=null&&t!==void 0)this.flushBuffer(this.bufferKey(e,t));else if(e!=null)for(let t of this.bufferKeysForProject(e))this.flushBuffer(t);else for(let e of[...this.buffers.keys()])this.flushBuffer(e)}flushBuffer(e){let t=this.buffers.get(e);if(!t||t.length===0)return;let n=this.bufferMeta.get(e);if(!n)return;let r=this.dicts.get(e);r||(r={},this.dicts.set(e,r));let i=t.map(e=>ne(e,r)),a=this.commitFile(n.projectId,n.commitId),o=this.readDataLines(a),s=i.map(e=>JSON.stringify(e)),c=[...o,...s];l(a,`${(Object.keys(r).length>0?[JSON.stringify({[b]:r}),...c]:c).join(`
4
+ `)}\n`),t.length=0;let u=this.timers.get(e);u&&(clearTimeout(u),this.timers.delete(e))}readDataLines(e){return i(e)?o(e,`utf-8`).split(`
5
+ `).filter(e=>!(!e||e.startsWith(`{"@@dict"`))):[]}getAllRenders(e){return this.flush(e),e?this.projectFiles(e).flatMap(e=>A(d(this.dir,e)).map(M)):this.jsonlFiles().flatMap(e=>A(d(this.dir,e)).map(M))}getRendersByComponent(e,t){return this.getAllRenders(t).filter(t=>t.displayName===e)}clearRenders(e){if(e){for(let t of this.bufferKeysForProject(e)){this.buffers.delete(t),this.dicts.delete(t),this.bufferMeta.delete(t);let e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}for(let t of this.projectFiles(e))c(d(this.dir,t))}else{for(let[,e]of this.timers)clearTimeout(e);this.buffers.clear(),this.timers.clear(),this.dicts.clear(),this.bufferMeta.clear();for(let e of this.jsonlFiles())c(d(this.dir,e))}}clearRendersByComponent(e,t){this.flush(t);let n=t?this.projectFiles(t):this.jsonlFiles(),r=0;for(let t of n){let n=d(this.dir,t),i=A(n),a=i.length,o=i.filter(t=>t.displayName!==e);r+=a-o.length,o.length===0?(c(n),this.clearBuffersForFile(t)):o.length<a&&(this.rewriteFile(n,o),this.clearBuffersForFile(t))}return r}clearRendersByCommit(e,t){this.flush(t);let n=t?this.projectFiles(t):this.jsonlFiles(),r=0;for(let t of n){let n=this.parseFilename(t);n?.commitId!=null&&n.commitId<e&&(c(d(this.dir,t)),this.clearBuffersForFile(t),r++)}return r}getProjects(){this.flush();let e=new Set,t=new Set;for(let n of this.jsonlFiles()){let r=this.parseFilename(n);if(!r||t.has(r.projectSanitized))continue;t.add(r.projectSanitized);let i=o(d(this.dir,n),`utf-8`).split(`
6
+ `);for(let t of i){if(!t)continue;let n=JSON.parse(t);if(!(`@@dict`in n)){e.add(n.projectId);break}}}return[...e]}getCommitIds(e){this.flush(e);let t=e?this.projectFiles(e):this.jsonlFiles(),n=new Set;for(let e of t){let t=this.parseFilename(e);t?.commitId!=null&&n.add(t.commitId)}return[...n].sort((e,t)=>e-t)}getCommits(e){this.flush(e);let t=e?this.projectFiles(e):this.jsonlFiles(),n=[];for(let e of t){let t=this.parseFilename(e);if(t?.commitId==null)continue;let r=A(d(this.dir,e));r.length!==0&&n.push({commitId:t.commitId,timestamp:r.find(e=>e.timestamp!=null)?.timestamp??null,renderCount:r.length,components:[...new Set(r.map(e=>e.displayName))]})}return n.sort((e,t)=>e.commitId-t.commitId)}getRendersByCommit(e,t){if(t)return this.flush(t,e),A(this.commitFile(t,e)).map(M);this.flush();let n=`_commit_${e}.jsonl`;return this.jsonlFiles().filter(e=>e.endsWith(n)).flatMap(e=>A(d(this.dir,e)).map(M))}getSummary(e){let t=this.getAllRenders(e),n={};for(let e of t){n[e.project]??={};let t=n[e.project];t[e.displayName]??={count:0,reasons:{props:0,state:0,hooks:0}};let r=t[e.displayName];r.count++,Array.isArray(e.reason.propsDifferences)&&r.reasons.props++,Array.isArray(e.reason.stateDifferences)&&r.reasons.state++,Array.isArray(e.reason.hookDifferences)&&r.reasons.hooks++,typeof e.actualDuration==`number`&&(r.totalDuration=(r.totalDuration??0)+e.actualDuration)}return n}getSummaryByCommit(e){let t=this.getAllRenders(e),n={};for(let e of t){if(e.commitId==null)continue;n[e.project]??={},n[e.project][e.commitId]??={};let t=n[e.project][e.commitId];t[e.displayName]??={count:0,reasons:{props:0,state:0,hooks:0}};let r=t[e.displayName];r.count++,Array.isArray(e.reason.propsDifferences)&&r.reasons.props++,Array.isArray(e.reason.stateDifferences)&&r.reasons.state++,Array.isArray(e.reason.hookDifferences)&&r.reasons.hooks++,typeof e.actualDuration==`number`&&(r.totalDuration=(r.totalDuration??0)+e.actualDuration)}return n}setTrackedComponents(e,t){this.trackedComponents.set(t,e)}getTrackedComponents(e){let t={},n=e?[e]:this.getProjects();for(let e of n){let n=[...new Set(this.getAllRenders(e).map(e=>e.displayName))];t[e]={registered:this.trackedComponents.get(e)??[],observed:n}}return t}setWdyrConfig(e,t){this.wdyrConfigs.set(t,e)}getWdyrConfig(e){let t={};if(e){let n=this.wdyrConfigs.get(e);n&&(t[e]=n)}else for(let[e,n]of this.wdyrConfigs)t[e]=n;return t}rewriteFile(e,t){l(e,`${t.map(e=>JSON.stringify(e)).join(`
7
+ `)}\n`)}clearBuffersForFile(e){let t=this.parseFilename(e);if(t)for(let[e,n]of this.bufferMeta){if(j(n.projectId)!==t.projectSanitized||!(t.commitId==null?n.commitId==null:n.commitId===t.commitId))continue;this.buffers.delete(e),this.dicts.delete(e),this.bufferMeta.delete(e);let r=this.timers.get(e);r&&(clearTimeout(r),this.timers.delete(e))}}bufferKey(e,t){return`${e}\0${t??N}`}bufferKeysForProject(e){let t=`${e}\0`;return[...this.buffers.keys()].filter(e=>e.startsWith(t))}commitFile(e,t){let n=j(e),r=t==null?`_${N}`:`_commit_${t}`;return d(this.dir,`${n}${r}.jsonl`)}projectFiles(e){let t=j(e);return s(this.dir).filter(e=>e.startsWith(t)&&e.endsWith(`.jsonl`))}parseFilename(e){if(!e.endsWith(`.jsonl`))return null;let t=e.slice(0,-6),n=t.match(/^(.+)_commit_(\d+)$/);if(n)return{projectSanitized:n[1],commitId:Number(n[2])};let r=t.match(/^(.+)_nocommit$/);return r?{projectSanitized:r[1]}:{projectSanitized:t}}jsonlFiles(){return s(this.dir).filter(e=>e.endsWith(`.jsonl`))}};function F(e){if(e)return{projectId:e};let t=P.getProjects();return t.length===0?{projectId:void 0}:t.length===1?{projectId:t[0]}:{projectId:void 0,error:[`Multiple projects are recording render data. Ask the user which project they are working on (e.g. their dev server URL like http://localhost:3000).`,``,`Active projects:`,...t.map(e=>`- ${e}`)].join(`
8
+ `)}}function I(e){return{content:[{type:`text`,text:e}]}}function L(e){e.registerTool(`clear_renders`,{title:`Clear Renders`,description:`Clears collected render data. Supports filtering by component name or by commit ID threshold. When no filter is given, clears all data. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),component:r.string().optional().describe(`Clear only renders for this component (by displayName).`),beforeCommit:r.number().optional().describe(`Clear all renders from commits with an ID strictly less than this value.`)}},async({project:e,component:t,beforeCommit:n})=>{let r=F(e);return r.error?I(r.error):t?I(`Cleared ${P.clearRendersByComponent(t,r.projectId)} render(s) for component "${t}".`):n==null?(P.clearRenders(r.projectId),I(r.projectId?`Render data cleared for ${r.projectId}.`:`All render data cleared.`)):I(`Cleared renders from ${P.clearRendersByCommit(n,r.projectId)} commit file(s) before commit #${n}.`)})}function R(e){e.registerTool(`get_commits`,{title:`Get Commits`,description:`Returns a list of React commit IDs that have recorded render data for a project. Use these IDs with get_renders_by_commit to inspect individual commits.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=F(e);if(t.error)return I(t.error);let n=P.getCommits(t.projectId);return n.length===0?I(`No commits recorded yet. Make sure the browser is connected and triggering re-renders.`):I(JSON.stringify(n,null,2))})}function z(e){e.registerTool(`get_projects`,{title:`Get Projects`,description:`Returns a list of project identifiers (browser origin URLs) that have recorded render data.`,inputSchema:{}},async()=>{let e=P.getProjects();return e.length===0?I(`No projects have recorded render data yet.`):I(`Active projects:\n${e.map(e=>`- ${e}`).join(`
9
+ `)}`)})}function B(e){e.registerTool(`get_render_summary`,{title:`Get Render Summary`,description:`Returns a summary of re-renders grouped by component name with counts. Use groupBy: 'commit' to get per-commit breakdowns instead of a single aggregate. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),groupBy:r.enum([`commit`]).optional().describe(`Group results by commit. When set to 'commit', returns per-commit render summaries instead of a single aggregate.`)}},async({project:e,groupBy:t})=>{let n=F(e);return n.error?I(n.error):t===`commit`?W(n.projectId):U(n.projectId)})}function V(e){return e==null?``:` (${e.toFixed(1)}ms)`}function H(e){let t=[];return e.props>0&&t.push(`props: ${e.props}`),e.state>0&&t.push(`state: ${e.state}`),e.hooks>0&&t.push(`hooks: ${e.hooks}`),t.length>0?` — ${t.join(`, `)}`:``}function U(e){let t=P.getSummary(e);if(Object.keys(t).length===0)return I(`No renders recorded yet.`);let n=[];for(let[e,r]of Object.entries(t)){n.push(`[${e}]`);for(let[e,{count:t,reasons:i,totalDuration:a}]of Object.entries(r))n.push(` ${e}: ${t} re-render(s)${H(i)}${V(a)}`)}return I(`Re-render summary:\n\n${n.join(`
10
+ `)}`)}function W(e){let t=P.getSummaryByCommit(e);if(Object.keys(t).length===0)return I(`No renders with commit IDs recorded yet.`);let n=[];for(let[e,r]of Object.entries(t)){n.push(`[${e}]`);let t=Object.keys(r).map(Number).sort((e,t)=>e-t);for(let e of t){let t=r[e],i=Object.values(t).reduce((e,t)=>e+t.count,0);n.push(` Commit #${e} (${i} re-render(s)):`);for(let[e,{count:r,reasons:i,totalDuration:a}]of Object.entries(t))n.push(` ${e}: ${r}${H(i)}${V(a)}`)}}return I(`Re-render summary (by commit):\n\n${n.join(`
11
+ `)}`)}function G(e){e.registerTool(`get_renders_by_commit`,{title:`Get Renders by Commit`,description:`Returns all re-renders for a specific React commit ID, including stack traces that show the hook chain and component tree that triggered each render. Use the stackFrames field to locate the exact source file and line. Use get_commits first to discover available commit IDs.`,inputSchema:{commitId:r.number().describe(`The React commit ID to filter by.`),component:r.string().optional().describe(`Filter by component name. Omit to get all renders.`),project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({commitId:e,component:t,project:n})=>{let r=F(n);if(r.error)return I(r.error);let i=P.getRendersByCommit(e,r.projectId);return t&&(i=i.filter(e=>e.displayName===t)),i.length===0?I(t?`No renders recorded for component "${t}" in commit ${e}.`:`No renders recorded for commit ${e}.`):I(JSON.stringify(i,null,2))})}function K(e){e.registerTool(`get_renders`,{title:`Get Renders`,description:`Returns all re-renders collected from the browser, including stack traces that show the hook chain and component tree that triggered each render. Use the stackFrames field to locate the exact source file and line. If multiple projects are active and no project is specified, the tool will ask you to disambiguate by asking the user for their dev server URL.`,inputSchema:{component:r.string().optional().describe(`Filter by component name. Omit to get all renders.`),project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({component:e,project:t})=>{let n=F(t);if(n.error)return I(n.error);let r=e?P.getRendersByComponent(e,n.projectId):P.getAllRenders(n.projectId);return r.length===0?I(e?`No renders recorded for "${e}".`:`No renders recorded yet. Make sure the browser is connected and triggering re-renders.`):I(JSON.stringify(r,null,2))})}function q(e){let t=[];if(e.include?.length){t.push(` include:`);for(let n of e.include)t.push(` - /${n}/`)}if(e.exclude?.length){t.push(` exclude:`);for(let n of e.exclude)t.push(` - /${n}/`)}if(e.trackAllPureComponents!=null&&t.push(` trackAllPureComponents: ${e.trackAllPureComponents}`),e.trackHooks!=null&&t.push(` trackHooks: ${e.trackHooks}`),e.trackExtraHooks?.length){t.push(` trackExtraHooks:`);for(let n of e.trackExtraHooks)t.push(` - ${n}`)}return e.logOnDifferentValues!=null&&t.push(` logOnDifferentValues: ${e.logOnDifferentValues}`),e.logOwnerReasons!=null&&t.push(` logOwnerReasons: ${e.logOwnerReasons}`),t}function J(e){e.registerTool(`get_tracked_components`,{title:`Get Tracked Components`,description:`Returns the why-did-you-render configuration for the connected project, including include/exclude filters and tracking options. Also shows components observed in render data. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=F(e);if(t.error)return I(t.error);let n=P.getWdyrConfig(t.projectId),r=P.getTrackedComponents(t.projectId),i=Object.keys(n).length>0,a=Object.keys(r).length>0;if(!i&&!a)return I(`No configuration or tracked components found. Make sure the browser is connected and triggering re-renders.`);let o=[],s=new Set([...Object.keys(n),...Object.keys(r)]);for(let e of s){o.push(`[${e}]`);let t=n[e];if(t){o.push(`Configuration:`);let e=q(t);e.length>0?o.push(...e):o.push(` (default options)`)}let i=r[e];if(i?.observed.length){o.push(`Observed in renders:`);for(let e of i.observed)o.push(` - ${e}`)}}return I(o.join(`
12
+ `))})}let Y=null;function X(e){Y=e}function Z(){return Y}function re(e){e.registerTool(`pause_renders`,{title:`Pause Render Collection`,description:`Pauses render data collection in the browser. Connected clients will stop reporting renders until resume_renders is called. Useful when you want to ignore renders from irrelevant interactions. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=F(e);if(t.error)return I(t.error);let n=Z();if(!n)return _(t.projectId??void 0),I(`Paused render collection for ${t.projectId??`all projects`} (relayed via WS owner).`);if(t.projectId){let e=await n.fetchSockets();for(let n of e)n.data.projectId===t.projectId&&n.emit(`pause`);return I(`Paused render collection for ${t.projectId}.`)}return n.emit(`pause`),I(`Paused render collection for all projects.`)})}function ie(e){e.registerTool(`resume_renders`,{title:`Resume Render Collection`,description:`Resumes render data collection that was previously paused with pause_renders. Connected clients will start reporting renders again. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({project:e})=>{let t=F(e);if(t.error)return I(t.error);let n=Z();if(!n)return v(t.projectId??void 0),I(`Resumed render collection for ${t.projectId??`all projects`} (relayed via WS owner).`);if(t.projectId){let e=await n.fetchSockets();for(let n of e)n.data.projectId===t.projectId&&n.emit(`resume`);return I(`Resumed render collection for ${t.projectId}.`)}return n.emit(`resume`),I(`Resumed render collection for all projects.`)})}function ae(e){K(e),B(e),R(e),G(e),z(e),J(e),L(e),re(e),ie(e)}const Q=3e3;function oe(e,t){e.on(`connection`,n=>{console.error(`[wdyr-mcp] browser connected (http://localhost:${t})`),n.data.projectId=null,n.on(`render`,(e,t,r)=>{n.data.projectId=t,P.addRender(e,t,r)}),n.on(`render-batch`,(e,t,r)=>{n.data.projectId=t;for(let n of e)P.addRender(n,t,r)}),n.on(`register`,(e,t)=>{n.data.projectId=t,P.setTrackedComponents(e,t)}),n.on(`config`,(e,t)=>{n.data.projectId=t,P.setWdyrConfig(e,t)}),n.on(`relay-pause`,async t=>{if(t){let n=await e.fetchSockets();for(let e of n)e.data.projectId===t&&e.emit(`pause`)}else e.emit(`pause`)}),n.on(`relay-resume`,async t=>{if(t){let n=await e.fetchSockets();for(let e of n)e.data.projectId===t&&e.emit(`resume`)}else e.emit(`resume`)}),n.on(`disconnect`,()=>{console.error(`[wdyr-mcp] browser disconnected`);let t=n.data.projectId;t&&([...e.sockets.sockets.values()].some(e=>e.id!==n.id&&e.data.projectId===t)||(console.error(`[wdyr-mcp] last client for ${t} disconnected, clearing render data`),P.clearRenders(t)))})})}function se(e){let t=null,n=null,r=null,i=!1;function a(){i||(r=f.createServer(),n=new te(r,{cors:{origin:`*`},serveClient:!1,transports:[`websocket`],maxHttpBufferSize:5e7}),oe(n,e),r.once(`error`,t=>{t.code===`EADDRINUSE`?(console.error(`[wdyr-mcp] Port ${e} in use, will retry every ${Q/1e3}s`),n?.close(),n=null,X(null),r=null,o()):console.error(`[wdyr-mcp] server error:`,t)}),r.listen(e,`127.0.0.1`,()=>{console.error(`[wdyr-mcp] socket.io server listening on http://localhost:${e}`),X(n),s()}))}function o(){t||i||(t=setInterval(a,Q))}function s(){t&&=(clearInterval(t),null)}return a(),{close(){i=!0,s(),n?.close(),X(null)}}}const $=new e({name:`why-did-you-render`,version:`0.0.0`});ae($);async function ce(){let e=Number(process.env.WDYR_WS_PORT)||4649;h(e);let n=se(e),r=new t;await $.connect(r),console.error(`[wdyr-mcp] MCP server running on stdio`);let i=!1;async function a(){i||(i=!0,console.error(`[wdyr-mcp] Shutting down…`),y(),n.close(),await $.close(),process.exit(0))}process.stdin.on(`end`,a),process.on(`SIGTERM`,a),process.on(`SIGINT`,a)}ce().catch(e=>{console.error(`[wdyr-mcp] Fatal error:`,e),process.exit(1)});export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0x1f320.sh/why-did-you-render-mcp",
3
- "version": "1.1.0-dev.1",
3
+ "version": "1.1.0-dev.3",
4
4
  "type": "module",
5
5
  "description": "MCP server that collects why-did-you-render data from browser and exposes it to coding agents",
6
6
  "license": "MIT",