@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.
- package/lib/mcp/transport.js +18 -5
- package/lib/program.js +29 -1
- package/package.json +1 -1
package/lib/mcp/transport.js
CHANGED
|
@@ -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,
|
|
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');
|