mcp-web-inspector 0.11.0 → 0.12.0

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 CHANGED
@@ -8,6 +8,7 @@ import { setSessionConfig } from "./toolHandler.js";
8
8
  import { readFileSync } from "node:fs";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { dirname, join } from "node:path";
11
+ import { createServer } from "node:net";
11
12
  // Get package.json version
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
14
  const PACKAGE_ROOT = join(__dirname, "..");
@@ -37,6 +38,9 @@ const { values } = parseArgs({
37
38
  type: 'boolean',
38
39
  default: false,
39
40
  },
41
+ 'cdp-port': {
42
+ type: 'string',
43
+ },
40
44
  'print-tools-json': {
41
45
  type: 'boolean',
42
46
  default: false,
@@ -48,17 +52,49 @@ const { values } = parseArgs({
48
52
  },
49
53
  strict: false,
50
54
  });
55
+ // Probe localhost:port; resolve true if free, false if in use.
56
+ function isPortFree(port) {
57
+ return new Promise(resolve => {
58
+ const srv = createServer();
59
+ srv.once('error', () => resolve(false));
60
+ srv.once('listening', () => srv.close(() => resolve(true)));
61
+ srv.listen(port, '127.0.0.1');
62
+ });
63
+ }
64
+ // First free port in [start, start+span). Throws if none.
65
+ async function findFreePort(start, span) {
66
+ for (let p = start; p < start + span; p++) {
67
+ if (await isPortFree(p))
68
+ return p;
69
+ }
70
+ throw new Error(`No free CDP port in ${start}..${start + span - 1}`);
71
+ }
72
+ // Resolve --cdp-port: 0 disables; explicit value used as-is; unset auto-picks from 9222 upward.
73
+ async function resolveCdpPort(raw) {
74
+ if (raw === undefined)
75
+ return findFreePort(9222, 100);
76
+ const n = Number.parseInt(raw, 10);
77
+ if (!Number.isInteger(n) || n < 0 || n > 65535) {
78
+ console.error(`Invalid --cdp-port value: ${raw}. Must be an integer in 0..65535 (0 disables).`);
79
+ process.exit(1);
80
+ }
81
+ return n;
82
+ }
51
83
  // Configure session settings (session saving is enabled by default)
52
84
  const baseDir = String(values['user-data-dir'] || './.mcp-web-inspector');
53
- const sessionConfig = {
54
- saveSession: !Boolean(values['no-save-session']),
55
- userDataDir: `${baseDir}/user-data`,
56
- screenshotsDir: `${baseDir}/screenshots`,
57
- headlessDefault: Boolean(values['headless']) || (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY),
58
- exposeSensitiveNetworkData: Boolean(values['expose-sensitive-network-data']),
59
- };
60
- setSessionConfig(sessionConfig);
61
85
  async function runServer() {
86
+ // Skip port resolution when only printing metadata — no browser will launch.
87
+ const printOnly = Boolean(values['print-tools-json'] || values['print-tools-md']);
88
+ const cdpPort = printOnly ? 0 : await resolveCdpPort(values['cdp-port']);
89
+ const sessionConfig = {
90
+ saveSession: !Boolean(values['no-save-session']),
91
+ userDataDir: `${baseDir}/user-data`,
92
+ screenshotsDir: `${baseDir}/screenshots`,
93
+ headlessDefault: Boolean(values['headless']) || (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY),
94
+ exposeSensitiveNetworkData: Boolean(values['expose-sensitive-network-data']),
95
+ cdpPort,
96
+ };
97
+ setSessionConfig(sessionConfig);
62
98
  // Create tool definitions with session config
63
99
  const TOOLS = createToolDefinitions(sessionConfig);
64
100
  // CLI utilities: print tools metadata (JSON/Markdown) and exit
@@ -72,6 +108,9 @@ async function runServer() {
72
108
  return;
73
109
  }
74
110
  console.error(`Starting mcp-web-inspector v${VERSION}`);
111
+ const cdpInstruction = sessionConfig.cdpPort > 0
112
+ ? `External Playwright clients can attach to this browser via Chrome DevTools Protocol at http://localhost:${sessionConfig.cdpPort} — pass that URL to chromium.connectOverCDP() to share cookies, localStorage, and the open page set with this server.`
113
+ : undefined;
75
114
  const server = new Server({
76
115
  name: "mcp-web-inspector",
77
116
  version: VERSION,
@@ -80,6 +119,7 @@ async function runServer() {
80
119
  resources: {},
81
120
  tools: {},
82
121
  },
122
+ ...(cdpInstruction ? { instructions: cdpInstruction } : {}),
83
123
  });
84
124
  // Setup request handlers
85
125
  setupRequestHandlers(server, TOOLS);
@@ -13,6 +13,7 @@ let sessionConfig = {
13
13
  screenshotsDir: './.mcp-web-inspector/screenshots',
14
14
  headlessDefault: false,
15
15
  exposeSensitiveNetworkData: false,
16
+ cdpPort: 0,
16
17
  };
17
18
  let colorSchemeOverride = null;
18
19
  /**
@@ -400,10 +401,14 @@ export async function ensureBrowser(browserSettings) {
400
401
  // IPs (e.g. Tailscale 100.64.0.0/10). This breaks environments where the API is on an
401
402
  // internal network but the app is served from a public CDN.
402
403
  // Prepare context options
404
+ const launchArgs = ['--disable-features=LocalNetworkAccessChecks'];
405
+ if (sessionConfig.cdpPort && sessionConfig.cdpPort > 0) {
406
+ launchArgs.push(`--remote-debugging-port=${sessionConfig.cdpPort}`);
407
+ }
403
408
  const contextOptions = {
404
409
  headless,
405
410
  executablePath: executablePath,
406
- args: ['--disable-features=LocalNetworkAccessChecks'],
411
+ args: launchArgs,
407
412
  };
408
413
  // If device config exists, use it; otherwise use manual viewport/userAgent
409
414
  if (deviceConfig) {
@@ -439,7 +444,10 @@ export async function ensureBrowser(browserSettings) {
439
444
  else {
440
445
  browser = await browserInstance.launch({
441
446
  headless,
442
- executablePath: executablePath
447
+ executablePath: executablePath,
448
+ args: sessionConfig.cdpPort && sessionConfig.cdpPort > 0
449
+ ? [`--remote-debugging-port=${sessionConfig.cdpPort}`]
450
+ : [],
443
451
  });
444
452
  currentBrowserType = browserType;
445
453
  // Add cleanup logic when browser is disconnected
@@ -608,10 +616,14 @@ export async function ensureBrowser(browserSettings) {
608
616
  retryViewportHeight = screenSize?.height ?? 720;
609
617
  }
610
618
  // Prepare context options
619
+ const retryLaunchArgs = ['--disable-features=LocalNetworkAccessChecks'];
620
+ if (sessionConfig.cdpPort && sessionConfig.cdpPort > 0) {
621
+ retryLaunchArgs.push(`--remote-debugging-port=${sessionConfig.cdpPort}`);
622
+ }
611
623
  const retryContextOptions = {
612
624
  headless,
613
625
  executablePath: executablePath,
614
- args: ['--disable-features=LocalNetworkAccessChecks'],
626
+ args: retryLaunchArgs,
615
627
  };
616
628
  // If device config exists, use it; otherwise use manual viewport/userAgent
617
629
  if (deviceConfig) {
@@ -644,7 +656,10 @@ export async function ensureBrowser(browserSettings) {
644
656
  else {
645
657
  browser = await browserInstance.launch({
646
658
  headless,
647
- executablePath: executablePath
659
+ executablePath: executablePath,
660
+ args: sessionConfig.cdpPort && sessionConfig.cdpPort > 0
661
+ ? [`--remote-debugging-port=${sessionConfig.cdpPort}`]
662
+ : [],
648
663
  });
649
664
  currentBrowserType = browserType;
650
665
  browser.on('disconnected', () => {
@@ -38,6 +38,7 @@ export interface SessionConfig {
38
38
  screenshotsDir: string;
39
39
  headlessDefault: boolean;
40
40
  exposeSensitiveNetworkData?: boolean;
41
+ cdpPort?: number;
41
42
  }
42
43
  export interface ToolContext {
43
44
  page?: Page;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-web-inspector",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Web Inspector MCP: Give LLMs visual superpowers to see, debug, and test any web page.",
5
5
  "license": "MIT",
6
6
  "author": "Anton",