what-devtools-mcp 0.6.3 → 0.6.5

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/client.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export interface DevToolsMCPConnection {
2
+ disconnect(): void;
3
+ readonly isConnected: boolean;
4
+ readonly eventCount: number;
5
+ }
6
+
7
+ export interface ConnectDevToolsMCPOptions {
8
+ port?: number;
9
+ token?: string;
10
+ /** Hostname used by the browser client for token discovery and the bridge WebSocket. Defaults to 127.0.0.1. */
11
+ host?: string;
12
+ }
13
+
14
+ export function connectDevToolsMCP(options?: ConnectDevToolsMCPOptions): DevToolsMCPConnection;
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ // CLI entrypoint package. Import browser/client helpers from `what-devtools-mcp/client`
2
+ // and Vite integration from `what-devtools-mcp/vite-plugin`.
3
+ export {};
package/package.json CHANGED
@@ -1,18 +1,30 @@
1
1
  {
2
2
  "name": "what-devtools-mcp",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "MCP server bridging AI agents to live What Framework app state via WebSocket",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "what-devtools-mcp": "src/index.js"
8
8
  },
9
9
  "exports": {
10
- ".": "./src/index.js",
11
- "./client": "./src/client.js",
12
- "./vite-plugin": "./src/vite-plugin.js"
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "import": "./src/index.js"
13
+ },
14
+ "./client": {
15
+ "types": "./client.d.ts",
16
+ "import": "./src/client.js"
17
+ },
18
+ "./vite-plugin": {
19
+ "types": "./vite-plugin.d.ts",
20
+ "import": "./src/vite-plugin.js"
21
+ }
13
22
  },
14
23
  "files": [
15
- "src"
24
+ "src",
25
+ "index.d.ts",
26
+ "client.d.ts",
27
+ "vite-plugin.d.ts"
16
28
  ],
17
29
  "dependencies": {
18
30
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -20,7 +32,7 @@
20
32
  "zod": "^3.25.0"
21
33
  },
22
34
  "peerDependencies": {
23
- "what-devtools": "^0.6.3"
35
+ "what-devtools": "^0.6.5"
24
36
  },
25
37
  "keywords": [
26
38
  "what-framework",
@@ -29,5 +41,6 @@
29
41
  "model-context-protocol",
30
42
  "ai-debugging"
31
43
  ],
32
- "license": "MIT"
44
+ "license": "MIT",
45
+ "types": "index.d.ts"
33
46
  }
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 {
package/src/client.js CHANGED
@@ -21,7 +21,7 @@ function logGrouped(badge, badgeStyle, title, data) {
21
21
  console.groupEnd();
22
22
  }
23
23
 
24
- export function connectDevToolsMCP({ port = 9229, token = '' } = {}) {
24
+ export function connectDevToolsMCP({ port = 9229, token = '', host = '127.0.0.1' } = {}) {
25
25
  // Never connect in production
26
26
  if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
27
27
  return { disconnect() {}, isConnected: false, eventCount: 0 };
@@ -61,7 +61,7 @@ export function connectDevToolsMCP({ port = 9229, token = '' } = {}) {
61
61
 
62
62
  const discoveryPort = port + 1;
63
63
  try {
64
- const res = await fetch(`http://localhost:${discoveryPort}/__what_mcp_token`);
64
+ const res = await fetch(`http://${host}:${discoveryPort}/__what_mcp_token`);
65
65
  if (res.ok) {
66
66
  const data = await res.json();
67
67
  discoveredToken = data.token;
@@ -75,13 +75,13 @@ export function connectDevToolsMCP({ port = 9229, token = '' } = {}) {
75
75
  return false;
76
76
  }
77
77
 
78
- log('MCP', BADGE, `Connecting to bridge on ws://localhost:${port}`);
78
+ log('MCP', BADGE, `Connecting to bridge on ws://${host}:${port}`);
79
79
 
80
80
  function connect() {
81
81
  reconnectAttempts++;
82
82
  try {
83
83
  const tokenParam = discoveredToken ? `?token=${encodeURIComponent(discoveredToken)}` : '';
84
- ws = new WebSocket(`ws://localhost:${discoveredPort}${tokenParam}`);
84
+ ws = new WebSocket(`ws://${host}:${discoveredPort}${tokenParam}`);
85
85
  } catch {
86
86
  if (reconnectAttempts <= 1) {
87
87
  log('MCP', BADGE_WARN, 'Bridge not available — retrying silently in background');
@@ -32,7 +32,7 @@ function resolveToken(explicitToken) {
32
32
  return '';
33
33
  }
34
34
 
35
- export default function whatDevToolsMCP({ port = 9229, token = '' } = {}) {
35
+ export default function whatDevToolsMCP({ port = 9229, token = '', host = '127.0.0.1' } = {}) {
36
36
  return {
37
37
  name: 'what-devtools-mcp',
38
38
  apply: 'serve',
@@ -47,7 +47,7 @@ Promise.all([
47
47
  import('what-devtools-mcp/client'),
48
48
  ]).then(([core, devtools, mcp]) => {
49
49
  devtools.installDevTools(core);
50
- mcp.connectDevToolsMCP({ port: ${port}, token: ${JSON.stringify(tokenValue)} });
50
+ mcp.connectDevToolsMCP({ port: ${port}, token: ${JSON.stringify(tokenValue)}, host: ${JSON.stringify(host)} });
51
51
  }).catch((error) => {
52
52
  console.warn(
53
53
  '[what-devtools-mcp] DevTools injection failed. Install what-core, what-devtools, and what-devtools-mcp, then verify Vite aliases/package exports.',
@@ -0,0 +1,8 @@
1
+ export interface WhatDevToolsMCPOptions {
2
+ port?: number;
3
+ token?: string;
4
+ /** Hostname injected into connectDevToolsMCP(). Defaults to 127.0.0.1. */
5
+ host?: string;
6
+ }
7
+
8
+ export default function whatDevToolsMCP(options?: WhatDevToolsMCPOptions): any;