@ejazullah/browser-mcp 0.0.60 → 0.0.62

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.
@@ -29,7 +29,7 @@ export async function start(serverBackendFactory, options, authConfig) {
29
29
  if (options.port !== undefined) {
30
30
  const httpServer = await startHttpServer(options);
31
31
  const auth = new AuthManager(authConfig ?? defaultAuthConfig());
32
- startHttpTransport(httpServer, serverBackendFactory, auth);
32
+ startHttpTransport(httpServer, serverBackendFactory, auth, !!options.heartbeat);
33
33
  }
34
34
  else {
35
35
  await startStdioTransport(serverBackendFactory);
@@ -157,7 +157,7 @@ function findSessionIdByCdpKey(sessionCdpKeys, sessions, cdpSessionKey) {
157
157
  }
158
158
  return undefined;
159
159
  }
160
- async function handleStreamable(serverBackendFactory, req, res, sessions, sessionCdpKeys) {
160
+ async function handleStreamable(serverBackendFactory, req, res, sessions, sessionCdpKeys, runHeartbeat) {
161
161
  const cdpSessionKey = resolveClientCdpSessionKey(req);
162
162
  const sessionId = req.headers['mcp-session-id'];
163
163
  if (sessionId) {
@@ -168,6 +168,19 @@ async function handleStreamable(serverBackendFactory, req, res, sessions, sessio
168
168
  res.end('Session not found');
169
169
  return;
170
170
  }
171
+ if (cdpSessionKey) {
172
+ const matchedSessionId = findSessionIdByCdpKey(sessionCdpKeys, sessions, cdpSessionKey);
173
+ if (matchedSessionId) {
174
+ const matchedTransport = sessions.get(matchedSessionId);
175
+ testDebug(`stale mcp-session-id ${sessionId}, reusing cdp-matched session: ${matchedSessionId} (${cdpSessionKey})`);
176
+ return await matchedTransport.handleRequest(req, res);
177
+ }
178
+ }
179
+ if (sessions.size === 1) {
180
+ const [singleSessionId, singleTransport] = sessions.entries().next().value;
181
+ testDebug(`stale mcp-session-id ${sessionId}, reusing single active session: ${singleSessionId}`);
182
+ return await singleTransport.handleRequest(req, res);
183
+ }
171
184
  delete req.headers['mcp-session-id'];
172
185
  testDebug(`stale http session id: ${sessionId}, creating a new session`);
173
186
  }
@@ -209,7 +222,7 @@ async function handleStreamable(serverBackendFactory, req, res, sessions, sessio
209
222
  testDebug(`create http session: ${transport.sessionId}`);
210
223
  if (cdpSessionKey)
211
224
  sessionCdpKeys.set(sessionId, cdpSessionKey);
212
- await mcpServer.connect(serverBackendFactory, transport, true);
225
+ await mcpServer.connect(serverBackendFactory, transport, runHeartbeat);
213
226
  sessions.set(sessionId, transport);
214
227
  }
215
228
  });
@@ -226,7 +239,7 @@ async function handleStreamable(serverBackendFactory, req, res, sessions, sessio
226
239
  res.statusCode = 400;
227
240
  res.end('Invalid request');
228
241
  }
229
- function startHttpTransport(httpServer, serverBackendFactory, auth) {
242
+ function startHttpTransport(httpServer, serverBackendFactory, auth, runHeartbeat) {
230
243
  const sseSessions = new Map();
231
244
  const streamableSessions = new Map();
232
245
  const streamableSessionCdpKeys = new Map();
@@ -254,7 +267,7 @@ function startHttpTransport(httpServer, serverBackendFactory, auth) {
254
267
  if (url.pathname.startsWith('/sse'))
255
268
  await handleSSE(serverBackendFactory, req, res, url, sseSessions);
256
269
  else
257
- await handleStreamable(serverBackendFactory, req, res, streamableSessions, streamableSessionCdpKeys);
270
+ await handleStreamable(serverBackendFactory, req, res, streamableSessions, streamableSessionCdpKeys, runHeartbeat);
258
271
  });
259
272
  });
260
273
  const url = httpAddressToString(httpServer.address());
package/lib/program.js CHANGED
@@ -16,6 +16,7 @@
16
16
  import { program, Option } from 'commander';
17
17
  // @ts-ignore
18
18
  import { startTraceViewerServer } from 'playwright-core/lib/server';
19
+ import debug from 'debug';
19
20
  import * as mcpTransport from './mcp/transport.js';
20
21
  import { commaSeparatedList, resolveCLIConfig, semicolonSeparatedList } from './config.js';
21
22
  import { buildAuthConfig } from './auth.js';
@@ -25,6 +26,28 @@ import { BrowserServerBackend } from './browserServerBackend.js';
25
26
  import { Context } from './context.js';
26
27
  import { contextFactory } from './browserContextFactory.js';
27
28
  import { runLoopTools } from './loopTools/main.js';
29
+ const mcpDebugNamespace = 'pw:mcp:*';
30
+ function setMcpDebugLogging(enabled) {
31
+ if (enabled === undefined)
32
+ return;
33
+ const current = process.env.DEBUG ?? '';
34
+ const namespaces = current.split(',').map(token => token.trim()).filter(Boolean);
35
+ if (enabled) {
36
+ if (!namespaces.includes(mcpDebugNamespace)) {
37
+ namespaces.push(mcpDebugNamespace);
38
+ process.env.DEBUG = namespaces.join(',');
39
+ debug.enable(process.env.DEBUG);
40
+ }
41
+ // eslint-disable-next-line no-console
42
+ console.error(`[mcp] verbose logs enabled (${mcpDebugNamespace})`);
43
+ return;
44
+ }
45
+ const filtered = namespaces.filter(ns => ns !== mcpDebugNamespace && !ns.startsWith('pw:mcp:'));
46
+ process.env.DEBUG = filtered.join(',');
47
+ debug.enable(process.env.DEBUG);
48
+ // eslint-disable-next-line no-console
49
+ console.error('[mcp] verbose logs disabled');
50
+ }
28
51
  program
29
52
  .version('Version ' + packageJSON.version)
30
53
  .name(packageJSON.name)
@@ -53,6 +76,10 @@ program
53
76
  .option('--user-agent <ua string>', 'specify user agent string')
54
77
  .option('--user-data-dir <path>', 'path to the user data directory. If not specified, a temporary directory will be created.')
55
78
  .option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"')
79
+ .option('--mcp-logs', 'Enable verbose MCP/session transport logs in CLI output')
80
+ .option('--no-mcp-logs', 'Disable verbose MCP/session transport logs in CLI output')
81
+ .option('--mcp-heartbeat', 'Enable MCP streamable transport heartbeat (may close sessions on slow/long operations)')
82
+ .option('--no-mcp-heartbeat', 'Disable MCP streamable transport heartbeat (recommended for n8n item-by-item runs)')
56
83
  .option('--mongodb-url <url>', 'MongoDB connection URL. Example: mongodb://localhost:27017')
57
84
  .option('--mongodb-db <name>', 'MongoDB database name. Default: playwright_mcp')
58
85
  .option('--mongodb-collection <name>', 'MongoDB collection name. Default: element_interactions')
@@ -67,6 +94,7 @@ program
67
94
  .addOption(new Option('--vision', 'Legacy option, use --caps=vision instead').hideHelp())
68
95
  .action(async (options) => {
69
96
  setupExitWatchdog();
97
+ setMcpDebugLogging(options.mcpLogs);
70
98
  if (options.vision) {
71
99
  // eslint-disable-next-line no-console
72
100
  console.error('The --vision option is deprecated, use --caps=vision instead');
@@ -87,7 +115,7 @@ program
87
115
  factories.push(createExtensionContextFactory(config));
88
116
  const serverBackendFactory = () => new BrowserServerBackend(config, factories);
89
117
  const authConfig = buildAuthConfig(options);
90
- await mcpTransport.start(serverBackendFactory, config.server, authConfig);
118
+ await mcpTransport.start(serverBackendFactory, { ...config.server, heartbeat: options.mcpHeartbeat }, authConfig);
91
119
  if (config.saveTrace) {
92
120
  const server = await startTraceViewerServer();
93
121
  const urlPrefix = server.urlPrefix('human-readable');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ejazullah/browser-mcp",
3
- "version": "0.0.60",
3
+ "version": "0.0.62",
4
4
  "description": "@ejazullah/browser-mcp - Enhanced Playwright Tools for MCP with CDP Support",
5
5
  "type": "module",
6
6
  "repository": {