what-devtools-mcp 0.6.2 → 0.6.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 +2 -2
- package/src/bridge.js +74 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-devtools-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "MCP server bridging AI agents to live What Framework app state via WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"zod": "^3.25.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"what-devtools": "^0.6.
|
|
23
|
+
"what-devtools": "^0.6.3"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"what-framework",
|
package/src/bridge.js
CHANGED
|
@@ -7,12 +7,34 @@
|
|
|
7
7
|
import { WebSocketServer } from 'ws';
|
|
8
8
|
import { randomBytes } from 'crypto';
|
|
9
9
|
import { createServer } from 'http';
|
|
10
|
-
import { writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
|
|
13
13
|
const MAX_EVENT_LOG = 1000;
|
|
14
14
|
const MAX_ERROR_LOG = 100;
|
|
15
15
|
|
|
16
|
+
function isLoopbackHostname(hostname) {
|
|
17
|
+
const normalized = hostname.toLowerCase();
|
|
18
|
+
return normalized === 'localhost'
|
|
19
|
+
|| normalized.endsWith('.localhost')
|
|
20
|
+
|| normalized === '127.0.0.1'
|
|
21
|
+
|| normalized.startsWith('127.')
|
|
22
|
+
|| normalized === '[::1]'
|
|
23
|
+
|| normalized === '::1';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isAllowedDiscoveryOrigin(origin) {
|
|
27
|
+
if (!origin) return true;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(origin);
|
|
31
|
+
return (url.protocol === 'http:' || url.protocol === 'https:')
|
|
32
|
+
&& isLoopbackHostname(url.hostname);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
export function createBridge({ port = 9229, host = '127.0.0.1' } = {}) {
|
|
17
39
|
let latestSnapshot = null;
|
|
18
40
|
const eventLog = [];
|
|
@@ -44,21 +66,43 @@ export function createBridge({ port = 9229, host = '127.0.0.1' } = {}) {
|
|
|
44
66
|
}});
|
|
45
67
|
|
|
46
68
|
// --- Token Discovery ---
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
// 2. File: writes token to node_modules/.cache/what-devtools-mcp/token
|
|
53
|
-
// The Vite plugin reads this at transform time.
|
|
54
|
-
|
|
69
|
+
// Primary mechanism: write a process-local cache file that the Vite plugin
|
|
70
|
+
// reads at transform time. HTTP token discovery exposes bearer credentials
|
|
71
|
+
// to any loopback page, so it is disabled unless explicitly opted in.
|
|
72
|
+
const httpTokenDiscovery = /^(1|true|yes)$/i.test(process.env.WHAT_MCP_HTTP_TOKEN_DISCOVERY || '');
|
|
55
73
|
const discoveryPort = port + 1;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
let httpServer = null;
|
|
75
|
+
|
|
76
|
+
if (httpTokenDiscovery) {
|
|
77
|
+
httpServer = createServer((req, res) => {
|
|
78
|
+
const origin = req.headers.origin;
|
|
79
|
+
const allowedOrigin = isAllowedDiscoveryOrigin(origin);
|
|
80
|
+
|
|
81
|
+
res.setHeader('Vary', 'Origin');
|
|
82
|
+
if (origin && allowedOrigin) {
|
|
83
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
84
|
+
}
|
|
59
85
|
res.setHeader('Access-Control-Allow-Methods', 'GET');
|
|
60
86
|
res.setHeader('Cache-Control', 'no-store');
|
|
61
87
|
|
|
88
|
+
if (!allowedOrigin) {
|
|
89
|
+
res.writeHead(403);
|
|
90
|
+
res.end('Forbidden');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (req.method === 'OPTIONS') {
|
|
95
|
+
res.writeHead(204);
|
|
96
|
+
res.end();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (req.method !== 'GET') {
|
|
101
|
+
res.writeHead(405);
|
|
102
|
+
res.end('Method not allowed');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
if (req.url === '/__what_mcp_token') {
|
|
63
107
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
64
108
|
res.end(JSON.stringify({ token: authToken, wsPort: port }));
|
|
@@ -68,25 +112,32 @@ export function createBridge({ port = 9229, host = '127.0.0.1' } = {}) {
|
|
|
68
112
|
}
|
|
69
113
|
});
|
|
70
114
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
115
|
+
httpServer.listen(discoveryPort, host, () => {
|
|
116
|
+
console.error(`[what-devtools-mcp] HTTP token discovery enabled on http://${host}:${discoveryPort}/__what_mcp_token`);
|
|
117
|
+
});
|
|
118
|
+
httpServer.on('error', () => {
|
|
119
|
+
// Discovery port unavailable — not critical, file-based fallback still works
|
|
120
|
+
console.error(`[what-devtools-mcp] Token discovery port ${discoveryPort} unavailable (non-fatal)`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
78
123
|
|
|
79
124
|
// Write token to a well-known file path for Vite plugin to read
|
|
80
125
|
try {
|
|
81
126
|
const cacheDir = join(process.cwd(), 'node_modules', '.cache', 'what-devtools-mcp');
|
|
82
127
|
mkdirSync(cacheDir, { recursive: true });
|
|
83
|
-
|
|
128
|
+
const tokenFile = join(cacheDir, 'token');
|
|
129
|
+
writeFileSync(tokenFile, JSON.stringify({ token: authToken, port, discoveryPort: httpTokenDiscovery ? discoveryPort : null }), { mode: 0o600 });
|
|
130
|
+
chmodSync(tokenFile, 0o600);
|
|
84
131
|
} catch {
|
|
85
|
-
// Non-fatal — HTTP discovery
|
|
132
|
+
// Non-fatal — explicit tokens or HTTP discovery can still be used
|
|
86
133
|
}
|
|
87
134
|
|
|
88
135
|
console.error(`[what-devtools-mcp] Bridge listening on ws://${host}:${port}`);
|
|
89
|
-
|
|
136
|
+
if (process.env.WHAT_MCP_LOG_TOKEN === '1') {
|
|
137
|
+
console.error(`[what-devtools-mcp] Auth token: ${authToken}`);
|
|
138
|
+
} else {
|
|
139
|
+
console.error('[what-devtools-mcp] Auth token generated; raw token logging disabled.');
|
|
140
|
+
}
|
|
90
141
|
|
|
91
142
|
wss.on('connection', (ws) => {
|
|
92
143
|
browserSocket = ws;
|
|
@@ -215,7 +266,7 @@ export function createBridge({ port = 9229, host = '127.0.0.1' } = {}) {
|
|
|
215
266
|
pendingCommands.delete(id);
|
|
216
267
|
}
|
|
217
268
|
wss.close();
|
|
218
|
-
httpServer
|
|
269
|
+
httpServer?.close();
|
|
219
270
|
}
|
|
220
271
|
|
|
221
272
|
return {
|