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.
Files changed (2) hide show
  1. package/package.json +2 -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.2",
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.2"
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
- // Two mechanisms so the browser client can find the token automatically:
48
- //
49
- // 1. HTTP endpoint: GET http://localhost:{port+1}/__what_mcp_token returns the token
50
- // The client fetches this on startup before connecting the WebSocket.
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
- const httpServer = createServer((req, res) => {
57
- // CORS headers so the browser can fetch from any origin
58
- res.setHeader('Access-Control-Allow-Origin', '*');
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
- httpServer.listen(discoveryPort, host, () => {
72
- console.error(`[what-devtools-mcp] Token discovery on http://${host}:${discoveryPort}/__what_mcp_token`);
73
- });
74
- httpServer.on('error', () => {
75
- // Discovery port unavailable — not critical, file-based fallback still works
76
- console.error(`[what-devtools-mcp] Token discovery port ${discoveryPort} unavailable (non-fatal)`);
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
- writeFileSync(join(cacheDir, 'token'), JSON.stringify({ token: authToken, port, discoveryPort }));
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 is the primary mechanism
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
- console.error(`[what-devtools-mcp] Auth token: ${authToken}`);
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.close();
269
+ httpServer?.close();
219
270
  }
220
271
 
221
272
  return {