what-devtools-mcp 0.11.2 → 0.11.4
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/package.json +7 -2
- package/src/vite-plugin.js +69 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-devtools-mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "MCP server bridging AI agents to live What Framework app state via WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,5 +39,10 @@
|
|
|
39
39
|
"bugs": {
|
|
40
40
|
"url": "https://github.com/CelsianJs/what-framework/issues"
|
|
41
41
|
},
|
|
42
|
-
"homepage": "https://whatfw.com"
|
|
42
|
+
"homepage": "https://whatfw.com",
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"what-devtools": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
43
48
|
}
|
package/src/vite-plugin.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { readFileSync } from 'fs';
|
|
13
|
+
import { createRequire } from 'module';
|
|
13
14
|
import { join } from 'path';
|
|
14
15
|
|
|
15
16
|
function resolveToken(explicitToken) {
|
|
@@ -50,10 +51,63 @@ const BROWSER_BOOTSTRAP_URL = '/@id/__x00__' + VIRTUAL_BOOTSTRAP_ID;
|
|
|
50
51
|
export const DISCOVERY_PATH = '/__what_mcp_discovery';
|
|
51
52
|
|
|
52
53
|
export default function whatDevToolsMCP({ port = 9229, token = '' } = {}) {
|
|
54
|
+
// Defense-in-depth: `apply: 'serve'` is the primary guard — Vite excludes the
|
|
55
|
+
// whole plugin from `vite build`, so none of these hooks run for a production
|
|
56
|
+
// bundle. But `apply` can be defeated (a meta-framework that flattens plugin
|
|
57
|
+
// arrays and re-invokes hooks, a consumer that spreads this plugin into
|
|
58
|
+
// another plugin's returned list, or a future Vite change), and getting it
|
|
59
|
+
// wrong once ships devtools + a dev-server `<script src>` into production
|
|
60
|
+
// (observed on a real deploy: the prod page requested
|
|
61
|
+
// `virtual:what-devtools-mcp/bootstrap` and, with a dev server live on the
|
|
62
|
+
// machine, followed it to localhost). So we ALSO gate every injecting hook on
|
|
63
|
+
// the resolved Vite command: if we ever run under `command === 'build'`, we
|
|
64
|
+
// resolve/load/inject NOTHING. `command` stays undefined when configResolved
|
|
65
|
+
// isn't called (unit tests, manual `plugin.load(...)`) — treated as serve.
|
|
66
|
+
let command;
|
|
67
|
+
let projectRoot;
|
|
68
|
+
let devtoolsResolvable; // memoized: is the `what-devtools` peer installed?
|
|
69
|
+
const isBuild = () => command === 'build';
|
|
70
|
+
|
|
71
|
+
// The injected bootstrap imports `what-devtools` (an OPTIONAL peer dependency).
|
|
72
|
+
// If the consumer hasn't installed it, Vite's dev transform cannot resolve that
|
|
73
|
+
// bare import and the whole dev server crashes with a transform error. Detect
|
|
74
|
+
// availability up front and degrade gracefully: inject nothing, log once. Never
|
|
75
|
+
// crash `vite dev` just because the debugging peer is absent.
|
|
76
|
+
const devtoolsAvailable = () => {
|
|
77
|
+
if (devtoolsResolvable === undefined) {
|
|
78
|
+
try {
|
|
79
|
+
// Resolve from the consumer project root, where the peer would live.
|
|
80
|
+
createRequire(join(projectRoot || process.cwd(), 'noop.js')).resolve('what-devtools');
|
|
81
|
+
devtoolsResolvable = true;
|
|
82
|
+
} catch {
|
|
83
|
+
devtoolsResolvable = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return devtoolsResolvable;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Injecting hooks are inactive during a production build OR when the devtools
|
|
90
|
+
// peer is missing — in both cases we resolve/load/inject NOTHING.
|
|
91
|
+
const inactive = () => isBuild() || !devtoolsAvailable();
|
|
92
|
+
|
|
53
93
|
return {
|
|
54
94
|
name: 'what-devtools-mcp',
|
|
55
95
|
apply: 'serve',
|
|
56
96
|
|
|
97
|
+
// Capture the resolved command so the injecting hooks below can hard-refuse
|
|
98
|
+
// to run during a production build even if `apply: 'serve'` was bypassed.
|
|
99
|
+
configResolved(config) {
|
|
100
|
+
command = config.command;
|
|
101
|
+
projectRoot = config.root;
|
|
102
|
+
// One clear, non-fatal notice when the peer is absent during dev.
|
|
103
|
+
if (!isBuild() && !devtoolsAvailable()) {
|
|
104
|
+
console.info(
|
|
105
|
+
'[what-devtools-mcp] `what-devtools` is not installed — skipping DevTools/MCP dev injection. ' +
|
|
106
|
+
'Run `npm i -D what-devtools` to enable live debugging.',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
57
111
|
// Node-side bridge probe, exposed same-origin to the browser client.
|
|
58
112
|
// Responds { bridge: false } when no bridge is running (quietly), or
|
|
59
113
|
// { bridge: true, token, wsPort } when it is — one round-trip discovery.
|
|
@@ -83,6 +137,7 @@ export default function whatDevToolsMCP({ port = 9229, token = '' } = {}) {
|
|
|
83
137
|
|
|
84
138
|
// Resolve the virtual module so Vite knows we own it.
|
|
85
139
|
resolveId(id) {
|
|
140
|
+
if (inactive()) return null; // never own the virtual module in a build or when the peer is missing
|
|
86
141
|
if (id === VIRTUAL_BOOTSTRAP_ID || id === RESOLVED_BOOTSTRAP_ID) {
|
|
87
142
|
return RESOLVED_BOOTSTRAP_ID;
|
|
88
143
|
}
|
|
@@ -94,18 +149,30 @@ export default function whatDevToolsMCP({ port = 9229, token = '' } = {}) {
|
|
|
94
149
|
// properly rewritten to dev-server URLs — unlike inline <script type=module>
|
|
95
150
|
// tags injected via transformIndexHtml, which Vite does not transform.
|
|
96
151
|
load(id) {
|
|
152
|
+
if (inactive()) return null; // never emit devtools source into a build or when the peer is missing
|
|
97
153
|
if (id !== RESOLVED_BOOTSTRAP_ID) return null;
|
|
98
154
|
const tokenValue = resolveToken(token);
|
|
155
|
+
// The side effects are gated behind `import.meta.env.DEV`. In a dev server
|
|
156
|
+
// that's statically `true`, so devtools install and connect normally. If
|
|
157
|
+
// this module ever ends up in a production bundle (e.g. a consumer imports
|
|
158
|
+
// the virtual id directly, or a bundler pulls it in despite the guards
|
|
159
|
+
// above), `import.meta.env.DEV` is statically `false`, the whole block is
|
|
160
|
+
// dead-code-eliminated, and the now-unused imports tree-shake away — so a
|
|
161
|
+
// prod bundle carries zero devtools/MCP code and can never open a
|
|
162
|
+
// connection to a local dev server.
|
|
99
163
|
return [
|
|
100
164
|
`import * as core from 'what-core';`,
|
|
101
165
|
`import { installDevTools } from 'what-devtools';`,
|
|
102
166
|
`import { connectDevToolsMCP } from 'what-devtools-mcp/client';`,
|
|
103
|
-
`
|
|
104
|
-
`
|
|
167
|
+
`if (import.meta.env && import.meta.env.DEV) {`,
|
|
168
|
+
` installDevTools(core);`,
|
|
169
|
+
` connectDevToolsMCP({ port: ${port}, token: ${JSON.stringify(tokenValue)}, discoveryUrl: ${JSON.stringify(DISCOVERY_PATH)} });`,
|
|
170
|
+
`}`,
|
|
105
171
|
].join('\n');
|
|
106
172
|
},
|
|
107
173
|
|
|
108
174
|
transformIndexHtml() {
|
|
175
|
+
if (inactive()) return; // never inject the bootstrap <script> into a build or when the peer is missing
|
|
109
176
|
// Inject a <script src> that points at the virtual module. The browser
|
|
110
177
|
// fetches `/@id/__x00__virtual:what-devtools-mcp/bootstrap`, Vite serves
|
|
111
178
|
// the transformed bootstrap (bare specifiers resolved), and everything
|