@ejazullah/browser-mcp 0.0.59 → 0.0.60

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.
@@ -128,7 +128,37 @@ async function handleSSE(serverBackendFactory, req, res, url, sessions) {
128
128
  res.statusCode = 405;
129
129
  res.end('Method not allowed');
130
130
  }
131
- async function handleStreamable(serverBackendFactory, req, res, sessions) {
131
+ function cdpSessionKeyFromUrl(value) {
132
+ try {
133
+ const parsed = new URL(value);
134
+ const match = parsed.pathname.match(/\/devtools\/([^/]+)/i);
135
+ return match?.[1];
136
+ }
137
+ catch {
138
+ const match = value.match(/\/devtools\/([^/]+)/i);
139
+ return match?.[1];
140
+ }
141
+ }
142
+ function resolveClientCdpSessionKey(req) {
143
+ const explicitSessionId = req.headers['x-cdp-session-id'];
144
+ if (explicitSessionId)
145
+ return explicitSessionId;
146
+ const cdpUrl = req.headers['x-cdp-url']
147
+ ?? req.headers['x-cdp-endpoint']
148
+ ?? req.headers['x-browser-url'];
149
+ if (!cdpUrl)
150
+ return undefined;
151
+ return cdpSessionKeyFromUrl(cdpUrl);
152
+ }
153
+ function findSessionIdByCdpKey(sessionCdpKeys, sessions, cdpSessionKey) {
154
+ for (const [knownSessionId, knownCdpSessionKey] of sessionCdpKeys.entries()) {
155
+ if (knownCdpSessionKey === cdpSessionKey && sessions.has(knownSessionId))
156
+ return knownSessionId;
157
+ }
158
+ return undefined;
159
+ }
160
+ async function handleStreamable(serverBackendFactory, req, res, sessions, sessionCdpKeys) {
161
+ const cdpSessionKey = resolveClientCdpSessionKey(req);
132
162
  const sessionId = req.headers['mcp-session-id'];
133
163
  if (sessionId) {
134
164
  const transport = sessions.get(sessionId);
@@ -142,14 +172,43 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
142
172
  testDebug(`stale http session id: ${sessionId}, creating a new session`);
143
173
  }
144
174
  else {
145
- return await transport.handleRequest(req, res);
175
+ if (cdpSessionKey) {
176
+ const knownCdpSessionKey = sessionCdpKeys.get(sessionId);
177
+ if (knownCdpSessionKey && knownCdpSessionKey !== cdpSessionKey) {
178
+ delete req.headers['mcp-session-id'];
179
+ testDebug(`browser changed for mcp session ${sessionId} (${knownCdpSessionKey} -> ${cdpSessionKey}), creating a new session`);
180
+ }
181
+ else {
182
+ if (!knownCdpSessionKey)
183
+ sessionCdpKeys.set(sessionId, cdpSessionKey);
184
+ return await transport.handleRequest(req, res);
185
+ }
186
+ }
187
+ else {
188
+ return await transport.handleRequest(req, res);
189
+ }
146
190
  }
147
191
  }
192
+ if (!sessionId && cdpSessionKey && req.method === 'POST') {
193
+ const matchedSessionId = findSessionIdByCdpKey(sessionCdpKeys, sessions, cdpSessionKey);
194
+ if (matchedSessionId) {
195
+ const matchedTransport = sessions.get(matchedSessionId);
196
+ testDebug(`missing mcp-session-id, reusing cdp-matched session: ${matchedSessionId} (${cdpSessionKey})`);
197
+ return await matchedTransport.handleRequest(req, res);
198
+ }
199
+ }
200
+ if (!sessionId && req.method === 'POST' && sessions.size === 1) {
201
+ const [singleSessionId, singleTransport] = sessions.entries().next().value;
202
+ testDebug(`missing mcp-session-id, reusing single active session: ${singleSessionId}`);
203
+ return await singleTransport.handleRequest(req, res);
204
+ }
148
205
  if (req.method === 'POST') {
149
206
  const transport = new StreamableHTTPServerTransport({
150
207
  sessionIdGenerator: () => crypto.randomUUID(),
151
208
  onsessioninitialized: async (sessionId) => {
152
209
  testDebug(`create http session: ${transport.sessionId}`);
210
+ if (cdpSessionKey)
211
+ sessionCdpKeys.set(sessionId, cdpSessionKey);
153
212
  await mcpServer.connect(serverBackendFactory, transport, true);
154
213
  sessions.set(sessionId, transport);
155
214
  }
@@ -158,6 +217,7 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
158
217
  if (!transport.sessionId)
159
218
  return;
160
219
  sessions.delete(transport.sessionId);
220
+ sessionCdpKeys.delete(transport.sessionId);
161
221
  testDebug(`delete http session: ${transport.sessionId}`);
162
222
  };
163
223
  await transport.handleRequest(req, res);
@@ -169,6 +229,7 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
169
229
  function startHttpTransport(httpServer, serverBackendFactory, auth) {
170
230
  const sseSessions = new Map();
171
231
  const streamableSessions = new Map();
232
+ const streamableSessionCdpKeys = new Map();
172
233
  // Configure CORS with permissive settings for MCP tools
173
234
  const corsHandler = cors({
174
235
  origin: true, // Allow all origins
@@ -193,7 +254,7 @@ function startHttpTransport(httpServer, serverBackendFactory, auth) {
193
254
  if (url.pathname.startsWith('/sse'))
194
255
  await handleSSE(serverBackendFactory, req, res, url, sseSessions);
195
256
  else
196
- await handleStreamable(serverBackendFactory, req, res, streamableSessions);
257
+ await handleStreamable(serverBackendFactory, req, res, streamableSessions, streamableSessionCdpKeys);
197
258
  });
198
259
  });
199
260
  const url = httpAddressToString(httpServer.address());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ejazullah/browser-mcp",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "description": "@ejazullah/browser-mcp - Enhanced Playwright Tools for MCP with CDP Support",
5
5
  "type": "module",
6
6
  "repository": {