@0x1f320.sh/why-did-you-render-mcp 2.0.1-dev.3 → 2.0.2-dev.1
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/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,6 @@ import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{Stdio
|
|
|
11
11
|
`)}`)}function pe(e){let t=ue(j.getAllRenders(e));if(Object.keys(t).length===0)return F(`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}${U(i)}${H(a)}`)}}return F(`Re-render summary (by commit):\n\n${n.join(`
|
|
12
12
|
`)}`)}function me(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=P(n);if(r.error)return F(r.error);let i=j.getRendersByCommit(e,r.projectId);return t&&(i=i.filter(e=>e.displayName===t)),i.length===0?F(t?`No renders recorded for component "${t}" in commit ${e}.`:`No renders recorded for commit ${e}.`):F(JSON.stringify(i,null,2))})}function he(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=P(t);if(n.error)return F(n.error);let r=e?j.getRendersByComponent(e,n.projectId):j.getAllRenders(n.projectId);return r.length===0?F(e?`No renders recorded for "${e}".`:`No renders recorded yet. Make sure the browser is connected and triggering re-renders.`):F(JSON.stringify(r,null,2))})}function ge(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 _e(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=P(e);if(t.error)return F(t.error);let n=N.getWdyrConfig(t.projectId),r=t.projectId?[t.projectId]:j.getProjects(),i={};for(let e of r){let t=[...new Set(j.getAllRenders(e).map(e=>e.displayName))];i[e]={registered:N.getTrackedComponents(e),observed:t}}let a=Object.keys(n).length>0,o=Object.keys(i).length>0;if(!a&&!o)return F(`No configuration or tracked components found. Make sure the browser is connected and triggering re-renders.`);let s=[],c=new Set([...Object.keys(n),...Object.keys(i)]);for(let e of c){s.push(`[${e}]`);let t=n[e];if(t){s.push(`Configuration:`);let e=ge(t);e.length>0?s.push(...e):s.push(` (default options)`)}let r=i[e];if(r?.observed.length){s.push(`Observed in renders:`);for(let e of r.observed)s.push(` - ${e}`)}}return F(s.join(`
|
|
13
13
|
`))})}function ve(e){e.registerTool(`list_snapshots`,{title:`List Snapshots`,description:`Lists all saved render snapshots with their timestamps.`,inputSchema:{}},async()=>{let e=M.list();return e.length===0?F(`No snapshots saved yet.`):F(`Saved snapshots:\n\n${e.map(e=>`- ${e.name} (${new Date(e.timestamp).toISOString()})`).join(`
|
|
14
|
-
`)}`)})}let W=null;function G(e){W=e}function K(){return W}const q=new Set;function ye(){return q.has(null)}function J(e){return q.has(null)||q.has(e)}function Y(e){q.add(e)}function X(e){e===null?q.clear():q.delete(e)}function be(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=P(e);if(t.error)return F(t.error);Y(t.projectId??null);let n=K();if(!n)return ne(t.projectId??void 0),F(`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 F(`Paused render collection for ${t.projectId}.`)}return n.emit(`pause`),F(`Paused render collection for all projects.`)})}function xe(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=P(e);if(t.error)return F(t.error);X(t.projectId??null);let n=K();if(!n)return re(t.projectId??void 0),F(`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 F(`Resumed render collection for ${t.projectId}.`)}return n.emit(`resume`),F(`Resumed render collection for all projects.`)})}function Z(e){e.registerTool(`save_snapshot`,{title:`Save Snapshot`,description:`Saves the current render summary as a named snapshot for later comparison. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{name:r.string().describe(`A name for this snapshot (used to reference it later).`),project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({name:e,project:t})=>{let n=P(t);return n.error?F(n.error):(M.save(e,A(j.getAllRenders(n.projectId))),F(`Snapshot "${e}" saved.`))})}function Se(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 Ce(e){e.registerTool(`wait_for_renders`,{title:`Wait for Renders`,description:`Waits for new renders to arrive after a code change. Call this after modifying code to wait for HMR to complete and measure the resulting re-renders. Returns a summary of new renders received during the wait period.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),timeout:r.number().optional().describe(`Maximum time to wait in milliseconds (default 10000, max 30000).`)}},async({project:e,timeout:t})=>{let n=P(e);if(n.error)return F(n.error);let r=Math.min(t??1e4,3e4),i=Date.now();return F(await new Promise(e=>{let t=!1;function a(){let o=Date.now()-i;if(n.projectId){let e=N.getLastHmrTimestamp(n.projectId);e!=null&&e>=i&&(t=!0)}let s=j.getRendersSince(i,n.projectId);if(s.length>0){let n=we(s),r=((Date.now()-i)/1e3).toFixed(1),a=t?` after HMR update`:``;e(`Received ${s.length} new render(s)${a} (waited ${r}s):\n\n${n}`);return}if(o>=r){let n=(r/1e3).toFixed(1);e(t?`Timed out after ${n}s — HMR was detected but no new renders were received.`:[`Timed out after ${n}s — no new renders received.`,`No HMR signal was detected. Verify that:`,`- The dev server is running with HMR enabled`,`- The browser tab is open and connected`].join(`
|
|
14
|
+
`)}`)})}let W=null;function G(e){W=e}function K(){return W}const q=new Set;function ye(){return q.has(null)}function J(e){return q.has(null)||q.has(e)}function Y(e){q.add(e)}function X(e){e===null?q.clear():(q.delete(e),q.delete(null))}function be(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=P(e);if(t.error)return F(t.error);Y(t.projectId??null);let n=K();if(!n)return ne(t.projectId??void 0),F(`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 F(`Paused render collection for ${t.projectId}.`)}return n.emit(`pause`),F(`Paused render collection for all projects.`)})}function xe(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=P(e);if(t.error)return F(t.error);X(t.projectId??null);let n=K();if(!n)return re(t.projectId??void 0),F(`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 F(`Resumed render collection for ${t.projectId}.`)}return n.emit(`resume`),F(`Resumed render collection for all projects.`)})}function Z(e){e.registerTool(`save_snapshot`,{title:`Save Snapshot`,description:`Saves the current render summary as a named snapshot for later comparison. If multiple projects are active and no project is specified, the tool will ask you to disambiguate.`,inputSchema:{name:r.string().describe(`A name for this snapshot (used to reference it later).`),project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`)}},async({name:e,project:t})=>{let n=P(t);return n.error?F(n.error):(M.save(e,A(j.getAllRenders(n.projectId))),F(`Snapshot "${e}" saved.`))})}function Se(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 Ce(e){e.registerTool(`wait_for_renders`,{title:`Wait for Renders`,description:`Waits for new renders to arrive after a code change. Call this after modifying code to wait for HMR to complete and measure the resulting re-renders. Returns a summary of new renders received during the wait period.`,inputSchema:{project:r.string().optional().describe(`Project identifier (the browser's origin URL, e.g. http://localhost:3000). Omit to auto-detect.`),timeout:r.number().optional().describe(`Maximum time to wait in milliseconds (default 10000, max 30000).`)}},async({project:e,timeout:t})=>{let n=P(e);if(n.error)return F(n.error);let r=Math.min(t??1e4,3e4),i=Date.now();return F(await new Promise(e=>{let t=!1;function a(){let o=Date.now()-i;if(n.projectId){let e=N.getLastHmrTimestamp(n.projectId);e!=null&&e>=i&&(t=!0)}let s=j.getRendersSince(i,n.projectId);if(s.length>0){let n=we(s),r=((Date.now()-i)/1e3).toFixed(1),a=t?` after HMR update`:``;e(`Received ${s.length} new render(s)${a} (waited ${r}s):\n\n${n}`);return}if(o>=r){let n=(r/1e3).toFixed(1);e(t?`Timed out after ${n}s — HMR was detected but no new renders were received.`:[`Timed out after ${n}s — no new renders received.`,`No HMR signal was detected. Verify that:`,`- The dev server is running with HMR enabled`,`- The browser tab is open and connected`].join(`
|
|
15
15
|
`));return}setTimeout(a,500)}a()}))})}function we(e){let t={};for(let n of e){t[n.displayName]??={count:0,reasons:{props:0,state:0,hooks:0}};let e=t[n.displayName];e.count++,Array.isArray(n.reason.propsDifferences)&&e.reasons.props++,Array.isArray(n.reason.stateDifferences)&&e.reasons.state++,Array.isArray(n.reason.hookDifferences)&&e.reasons.hooks++}let n=[];for(let[e,{count:r,reasons:i}]of Object.entries(t))n.push(` ${e}: ${r} re-render(s)${Se(i)}`);return n.join(`
|
|
16
|
-
`)}function Te(e){he(e),V(e),z(e),me(e),B(e),_e(e),de(e),be(e),xe(e),Z(e),ve(e),I(e),R(e),Ce(e)}const Q=3e3;function Ee(e,t){e.on(`connection`,n=>{console.error(`[wdyr-mcp] browser connected (http://localhost:${t})`),n.data.projectId=null,ye()&&n.emit(`pause`),n.on(`render`,(e,t,r)=>{n.data.projectId=t,!J(t)&&j.addRender(e,t,r)}),n.on(`render-batch`,(e,t,r)=>{if(n.data.projectId=t,!J(t))for(let n of e)j.addRender(n,t,r)}),n.on(`register`,(e,t)=>{n.data.projectId=t,N.setTrackedComponents(e,t),J(t)&&n.emit(`pause`)}),n.on(`config`,(e,t)=>{n.data.projectId=t,N.setWdyrConfig(e,t)}),n.on(`relay-pause`,async t=>{if(Y(t??null),t){let n=await e.fetchSockets();for(let e of n)e.data.projectId===t&&e.emit(`pause`)}else e.emit(`pause`)}),n.on(`hmr`,e=>{n.data.projectId=e,N.recordHmr(e)}),n.on(`relay-resume`,async t=>{if(X(t??null),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`),j.clearRenders(t)))})})}function De(e){let t=null,n=null,r=null,i=!1;function a(){i||(r=p.createServer(),n=new ee(r,{cors:{origin:`*`},serveClient:!1,transports:[`websocket`],maxHttpBufferSize:5e7}),Ee(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,G(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}`),G(n),s()}))}function o(){t||i||(t=setInterval(a,Q))}function s(){t&&=(clearInterval(t),null)}return a(),{close(){i=!0,s(),n?.close(),G(null)}}}const $=new e({name:`why-did-you-render`,version:`0.0.0`});Te($);async function Oe(){let e=Number(process.env.WDYR_WS_PORT)||4649;te(e);let n=De(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…`),ie(),n.close(),await $.close(),process.exit(0))}process.stdin.on(`end`,a),process.on(`SIGTERM`,a),process.on(`SIGINT`,a)}Oe().catch(e=>{console.error(`[wdyr-mcp] Fatal error:`,e),process.exit(1)});export{};
|
|
16
|
+
`)}function Te(e){he(e),V(e),z(e),me(e),B(e),_e(e),de(e),be(e),xe(e),Z(e),ve(e),I(e),R(e),Ce(e)}const Q=3e3;function Ee(e,t){e.on(`connection`,n=>{console.error(`[wdyr-mcp] browser connected (http://localhost:${t})`),n.data.projectId=null,ye()&&n.emit(`pause`),n.on(`render`,(e,t,r)=>{n.data.projectId=t,!J(t)&&j.addRender(e,t,r)}),n.on(`render-batch`,(e,t,r)=>{if(n.data.projectId=t,!J(t))for(let n of e)j.addRender(n,t,r)}),n.on(`register`,(e,t)=>{n.data.projectId=t,N.setTrackedComponents(e,t),J(t)&&n.emit(`pause`)}),n.on(`config`,(e,t)=>{n.data.projectId=t,N.setWdyrConfig(e,t),J(t)&&n.emit(`pause`)}),n.on(`relay-pause`,async t=>{if(Y(t??null),t){let n=await e.fetchSockets();for(let e of n)e.data.projectId===t&&e.emit(`pause`)}else e.emit(`pause`)}),n.on(`hmr`,e=>{n.data.projectId=e,N.recordHmr(e)}),n.on(`relay-resume`,async t=>{if(X(t??null),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`),j.clearRenders(t)))})})}function De(e){let t=null,n=null,r=null,i=!1;function a(){i||(r=p.createServer(),n=new ee(r,{cors:{origin:`*`},serveClient:!1,transports:[`websocket`],maxHttpBufferSize:5e7}),Ee(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,G(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}`),G(n),s()}))}function o(){t||i||(t=setInterval(a,Q))}function s(){t&&=(clearInterval(t),null)}return a(),{close(){i=!0,s(),n?.close(),G(null)}}}const $=new e({name:`why-did-you-render`,version:`0.0.0`});Te($);async function Oe(){let e=Number(process.env.WDYR_WS_PORT)||4649;te(e);let n=De(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…`),ie(),n.close(),await $.close(),process.exit(0))}process.stdin.on(`end`,a),process.on(`SIGTERM`,a),process.on(`SIGINT`,a)}Oe().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": "2.0.
|
|
3
|
+
"version": "2.0.2-dev.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server that receives why-did-you-render reports from the browser and exposes them as tools for AI coding agents to diagnose React performance issues",
|
|
6
6
|
"license": "MIT",
|