browser-debug-mcp-bridge 1.5.0 → 1.6.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.
@@ -38,9 +38,9 @@ const TOOL_SCHEMAS = {
38
38
  },
39
39
  get_recent_events: {
40
40
  type: 'object',
41
- required: ['sessionId'],
42
41
  properties: {
43
42
  sessionId: { type: 'string' },
43
+ url: { type: 'string' },
44
44
  eventTypes: { type: 'array', items: { type: 'string' } },
45
45
  limit: { type: 'number' },
46
46
  offset: { type: 'number' },
@@ -48,18 +48,18 @@ const TOOL_SCHEMAS = {
48
48
  },
49
49
  get_navigation_history: {
50
50
  type: 'object',
51
- required: ['sessionId'],
52
51
  properties: {
53
52
  sessionId: { type: 'string' },
53
+ url: { type: 'string' },
54
54
  limit: { type: 'number' },
55
55
  offset: { type: 'number' },
56
56
  },
57
57
  },
58
58
  get_console_events: {
59
59
  type: 'object',
60
- required: ['sessionId'],
61
60
  properties: {
62
61
  sessionId: { type: 'string' },
62
+ url: { type: 'string' },
63
63
  level: { type: 'string' },
64
64
  limit: { type: 'number' },
65
65
  offset: { type: 'number' },
@@ -78,6 +78,7 @@ const TOOL_SCHEMAS = {
78
78
  type: 'object',
79
79
  properties: {
80
80
  sessionId: { type: 'string' },
81
+ url: { type: 'string' },
81
82
  errorType: { type: 'string' },
82
83
  groupBy: { type: 'string' },
83
84
  limit: { type: 'number' },
@@ -143,6 +144,20 @@ const TOOL_SCHEMAS = {
143
144
  maxAncestors: { type: 'number' },
144
145
  },
145
146
  },
147
+ get_live_console_logs: {
148
+ type: 'object',
149
+ required: ['sessionId'],
150
+ properties: {
151
+ sessionId: { type: 'string' },
152
+ url: { type: 'string' },
153
+ tabId: { type: 'number' },
154
+ levels: { type: 'array', items: { type: 'string' } },
155
+ contains: { type: 'string' },
156
+ sinceTs: { type: 'number' },
157
+ includeRuntimeErrors: { type: 'boolean' },
158
+ limit: { type: 'number' },
159
+ },
160
+ },
146
161
  explain_last_failure: {
147
162
  type: 'object',
148
163
  required: ['sessionId'],
@@ -208,6 +223,7 @@ const TOOL_DESCRIPTIONS = {
208
223
  get_computed_styles: 'Read computed CSS styles for an element',
209
224
  get_layout_metrics: 'Read viewport and element layout metrics',
210
225
  capture_ui_snapshot: 'Capture redacted UI snapshot (DOM/styles/optional PNG) and persist it',
226
+ get_live_console_logs: 'Read in-memory live console logs for a connected session',
211
227
  explain_last_failure: 'Explain the latest failure timeline',
212
228
  get_event_correlation: 'Correlate related events by window',
213
229
  list_snapshots: 'List snapshot metadata by session/time/trigger',
@@ -303,6 +319,65 @@ function parseRequestedTypes(value) {
303
319
  .map((entry) => mapRequestedEventType(entry));
304
320
  return Array.from(new Set(normalized));
305
321
  }
322
+ function normalizeRequestedOrigin(value) {
323
+ if (value === undefined || value === null || value === '') {
324
+ return undefined;
325
+ }
326
+ if (typeof value !== 'string') {
327
+ throw new Error('url must be a string');
328
+ }
329
+ try {
330
+ const parsed = new URL(value);
331
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
332
+ throw new Error('url must use http:// or https://');
333
+ }
334
+ return parsed.origin;
335
+ }
336
+ catch {
337
+ throw new Error('url must be a valid absolute http(s) URL');
338
+ }
339
+ }
340
+ function ensureSessionOrOriginFilter(sessionId, origin) {
341
+ if (!sessionId && !origin) {
342
+ throw new Error('sessionId or url is required');
343
+ }
344
+ }
345
+ function resolveUrlPrefixFromOrigin(origin) {
346
+ return origin.endsWith('/') ? origin : origin + '/';
347
+ }
348
+ function appendEventOriginFilter(where, params, origin) {
349
+ if (!origin) {
350
+ return;
351
+ }
352
+ const prefix = resolveUrlPrefixFromOrigin(origin);
353
+ where.push(`
354
+ (
355
+ origin = ?
356
+ OR (
357
+ origin IS NULL AND (
358
+ json_extract(payload_json, '$.origin') = ?
359
+ OR json_extract(payload_json, '$.url') = ?
360
+ OR json_extract(payload_json, '$.url') LIKE ?
361
+ OR json_extract(payload_json, '$.to') = ?
362
+ OR json_extract(payload_json, '$.to') LIKE ?
363
+ OR json_extract(payload_json, '$.href') = ?
364
+ OR json_extract(payload_json, '$.href') LIKE ?
365
+ OR json_extract(payload_json, '$.location') = ?
366
+ OR json_extract(payload_json, '$.location') LIKE ?
367
+ )
368
+ )
369
+ )
370
+ `);
371
+ params.push(origin, origin, origin, `${prefix}%`, origin, `${prefix}%`, origin, `${prefix}%`, origin, `${prefix}%`);
372
+ }
373
+ function appendNetworkOriginFilter(where, params, origin) {
374
+ if (!origin) {
375
+ return;
376
+ }
377
+ const prefix = resolveUrlPrefixFromOrigin(origin);
378
+ where.push('(origin = ? OR (origin IS NULL AND (url = ? OR url LIKE ?)))');
379
+ params.push(origin, origin, `${prefix}%`);
380
+ }
306
381
  function resolveLastUrl(payload) {
307
382
  const candidates = [payload.url, payload.to, payload.href, payload.location];
308
383
  for (const candidate of candidates) {
@@ -313,12 +388,17 @@ function resolveLastUrl(payload) {
313
388
  return undefined;
314
389
  }
315
390
  function mapEventRecord(row) {
391
+ const payload = readJsonPayload(row.payload_json);
316
392
  return {
317
393
  eventId: row.event_id,
318
394
  sessionId: row.session_id,
319
395
  timestamp: row.ts,
320
396
  type: row.type,
321
- payload: readJsonPayload(row.payload_json),
397
+ tabId: row.tab_id ?? (typeof payload.tabId === 'number' ? payload.tabId : undefined),
398
+ origin: row.origin
399
+ ?? (typeof payload.origin === 'string' ? payload.origin : undefined)
400
+ ?? undefined,
401
+ payload,
322
402
  };
323
403
  }
324
404
  function classifyNetworkFailure(status, errorClass) {
@@ -528,6 +608,26 @@ function asStringArray(value, maxItems) {
528
608
  .filter((entry) => typeof entry === 'string' && entry.length > 0)
529
609
  .slice(0, maxItems);
530
610
  }
611
+ const LIVE_CONSOLE_LEVELS = new Set(['log', 'info', 'warn', 'error', 'debug', 'trace']);
612
+ function resolveLiveConsoleLevels(value) {
613
+ const levels = asStringArray(value, 16)
614
+ .map((entry) => entry.trim().toLowerCase())
615
+ .filter((entry) => LIVE_CONSOLE_LEVELS.has(entry));
616
+ return Array.from(new Set(levels));
617
+ }
618
+ function resolveOptionalTabId(value) {
619
+ if (value === undefined || value === null || value === '') {
620
+ return undefined;
621
+ }
622
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
623
+ throw new Error('tabId must be an integer');
624
+ }
625
+ const tabId = Math.floor(value);
626
+ if (!Number.isInteger(tabId) || tabId < 0) {
627
+ throw new Error('tabId must be an integer');
628
+ }
629
+ return tabId;
630
+ }
531
631
  function isLiveSessionDisconnectedMessage(message) {
532
632
  const normalized = message.toLowerCase();
533
633
  return normalized.includes('no active extension connection')
@@ -713,14 +813,18 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
713
813
  get_recent_events: async (input) => {
714
814
  const db = getDb();
715
815
  const sessionId = getSessionId(input);
716
- if (!sessionId) {
717
- throw new Error('sessionId is required');
718
- }
816
+ const origin = normalizeRequestedOrigin(input.url);
817
+ ensureSessionOrOriginFilter(sessionId, origin);
719
818
  const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
720
819
  const offset = resolveOffset(input.offset);
721
820
  const requestedTypes = parseRequestedTypes(input.types ?? input.eventTypes);
722
- const params = [sessionId];
723
- const where = ['session_id = ?'];
821
+ const params = [];
822
+ const where = [];
823
+ if (sessionId) {
824
+ where.push('session_id = ?');
825
+ params.push(sessionId);
826
+ }
827
+ appendEventOriginFilter(where, params, origin);
724
828
  if (requestedTypes.length > 0) {
725
829
  const placeholders = requestedTypes.map(() => '?').join(', ');
726
830
  where.push(`type IN (${placeholders})`);
@@ -728,7 +832,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
728
832
  }
729
833
  const rows = db
730
834
  .prepare(`
731
- SELECT event_id, session_id, ts, type, payload_json
835
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
732
836
  FROM events
733
837
  WHERE ${where.join(' AND ')}
734
838
  ORDER BY ts DESC
@@ -752,20 +856,26 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
752
856
  get_navigation_history: async (input) => {
753
857
  const db = getDb();
754
858
  const sessionId = getSessionId(input);
755
- if (!sessionId) {
756
- throw new Error('sessionId is required');
757
- }
859
+ const origin = normalizeRequestedOrigin(input.url);
860
+ ensureSessionOrOriginFilter(sessionId, origin);
758
861
  const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
759
862
  const offset = resolveOffset(input.offset);
863
+ const params = [];
864
+ const where = ["type = 'nav'"];
865
+ if (sessionId) {
866
+ where.push('session_id = ?');
867
+ params.push(sessionId);
868
+ }
869
+ appendEventOriginFilter(where, params, origin);
760
870
  const rows = db
761
871
  .prepare(`
762
- SELECT event_id, session_id, ts, type, payload_json
872
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
763
873
  FROM events
764
- WHERE session_id = ? AND type = 'nav'
874
+ WHERE ${where.join(' AND ')}
765
875
  ORDER BY ts DESC
766
876
  LIMIT ? OFFSET ?
767
877
  `)
768
- .all(sessionId, limit + 1, offset);
878
+ .all(...params, limit + 1, offset);
769
879
  const truncated = rows.length > limit;
770
880
  return {
771
881
  ...createBaseResponse(sessionId),
@@ -783,23 +893,27 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
783
893
  get_console_events: async (input) => {
784
894
  const db = getDb();
785
895
  const sessionId = getSessionId(input);
786
- if (!sessionId) {
787
- throw new Error('sessionId is required');
788
- }
896
+ const origin = normalizeRequestedOrigin(input.url);
897
+ ensureSessionOrOriginFilter(sessionId, origin);
789
898
  const level = typeof input.level === 'string' ? input.level : undefined;
790
899
  const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
791
900
  const offset = resolveOffset(input.offset);
792
- const params = [sessionId];
793
- let levelClause = '';
901
+ const params = [];
902
+ const where = ["type = 'console'"];
903
+ if (sessionId) {
904
+ where.push('session_id = ?');
905
+ params.push(sessionId);
906
+ }
907
+ appendEventOriginFilter(where, params, origin);
794
908
  if (level) {
795
- levelClause = "AND json_extract(payload_json, '$.level') = ?";
909
+ where.push("json_extract(payload_json, '$.level') = ?");
796
910
  params.push(level);
797
911
  }
798
912
  const rows = db
799
913
  .prepare(`
800
- SELECT event_id, session_id, ts, type, payload_json
914
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
801
915
  FROM events
802
- WHERE session_id = ? AND type = 'console' ${levelClause}
916
+ WHERE ${where.join(' AND ')}
803
917
  ORDER BY ts DESC
804
918
  LIMIT ? OFFSET ?
805
919
  `)
@@ -871,6 +985,8 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
871
985
  get_network_failures: async (input) => {
872
986
  const db = getDb();
873
987
  const sessionId = typeof input.sessionId === 'string' ? input.sessionId : undefined;
988
+ const origin = normalizeRequestedOrigin(input.url);
989
+ ensureSessionOrOriginFilter(sessionId, origin);
874
990
  const groupBy = typeof input.groupBy === 'string' ? input.groupBy : undefined;
875
991
  const errorType = typeof input.errorType === 'string' ? input.errorType : undefined;
876
992
  const limit = resolveLimit(input.limit, DEFAULT_LIST_LIMIT);
@@ -882,6 +998,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
882
998
  where.push('session_id = ?');
883
999
  params.push(sessionId);
884
1000
  }
1001
+ appendNetworkOriginFilter(where, params, origin);
885
1002
  where.push(errorFilter);
886
1003
  if (errorFilter === 'error_class = ?' && errorType) {
887
1004
  params.push(errorType);
@@ -929,7 +1046,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
929
1046
  }
930
1047
  const rows = db
931
1048
  .prepare(`
932
- SELECT request_id, session_id, ts_start, duration_ms, method, url, status, initiator, error_class
1049
+ SELECT request_id, session_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class
933
1050
  FROM network
934
1051
  ${whereClause}
935
1052
  ORDER BY ts_start DESC
@@ -954,6 +1071,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
954
1071
  durationMs: row.duration_ms ?? undefined,
955
1072
  method: row.method,
956
1073
  url: row.url,
1074
+ origin: row.origin ?? undefined,
957
1075
  status: row.status ?? undefined,
958
1076
  initiator: row.initiator ?? undefined,
959
1077
  errorType: classifyNetworkFailure(row.status, row.error_class),
@@ -974,7 +1092,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
974
1092
  const offset = resolveOffset(input.offset);
975
1093
  const rows = db
976
1094
  .prepare(`
977
- SELECT event_id, session_id, ts, type, payload_json
1095
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
978
1096
  FROM events
979
1097
  WHERE session_id = ?
980
1098
  AND type IN ('ui', 'element_ref')
@@ -1008,7 +1126,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1008
1126
  const windowMs = lookbackSeconds * 1000;
1009
1127
  const latestErrorEvent = db
1010
1128
  .prepare(`
1011
- SELECT event_id, session_id, ts, type, payload_json
1129
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
1012
1130
  FROM events
1013
1131
  WHERE session_id = ?
1014
1132
  AND (type = 'error' OR (type = 'console' AND json_extract(payload_json, '$.level') = 'error'))
@@ -1018,7 +1136,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1018
1136
  .get(sessionId);
1019
1137
  const latestNetworkFailure = db
1020
1138
  .prepare(`
1021
- SELECT request_id, session_id, ts_start, duration_ms, method, url, status, initiator, error_class
1139
+ SELECT request_id, session_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class
1022
1140
  FROM network
1023
1141
  WHERE session_id = ?
1024
1142
  AND (error_class IS NOT NULL OR COALESCE(status, 0) >= 400)
@@ -1046,7 +1164,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1046
1164
  const windowEnd = anchorTs + 1_000;
1047
1165
  const eventRows = db
1048
1166
  .prepare(`
1049
- SELECT event_id, session_id, ts, type, payload_json
1167
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
1050
1168
  FROM events
1051
1169
  WHERE session_id = ?
1052
1170
  AND ts BETWEEN ? AND ?
@@ -1055,7 +1173,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1055
1173
  .all(sessionId, windowStart, windowEnd);
1056
1174
  const networkRows = db
1057
1175
  .prepare(`
1058
- SELECT request_id, session_id, ts_start, duration_ms, method, url, status, initiator, error_class
1176
+ SELECT request_id, session_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class
1059
1177
  FROM network
1060
1178
  WHERE session_id = ?
1061
1179
  AND ts_start BETWEEN ? AND ?
@@ -1138,7 +1256,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1138
1256
  }
1139
1257
  const anchorEvent = db
1140
1258
  .prepare(`
1141
- SELECT event_id, session_id, ts, type, payload_json
1259
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
1142
1260
  FROM events
1143
1261
  WHERE session_id = ? AND event_id = ?
1144
1262
  LIMIT 1
@@ -1153,7 +1271,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1153
1271
  const windowEnd = anchorEvent.ts + windowMs;
1154
1272
  const nearbyEvents = db
1155
1273
  .prepare(`
1156
- SELECT event_id, session_id, ts, type, payload_json
1274
+ SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
1157
1275
  FROM events
1158
1276
  WHERE session_id = ?
1159
1277
  AND event_id != ?
@@ -1162,7 +1280,7 @@ export function createV1ToolHandlers(getDb, getSessionConnectionState) {
1162
1280
  .all(sessionId, eventId, windowStart, windowEnd);
1163
1281
  const nearbyNetworkFailures = db
1164
1282
  .prepare(`
1165
- SELECT request_id, session_id, ts_start, duration_ms, method, url, status, initiator, error_class
1283
+ SELECT request_id, session_id, ts_start, duration_ms, method, url, origin, status, initiator, error_class
1166
1284
  FROM network
1167
1285
  WHERE session_id = ?
1168
1286
  AND ts_start BETWEEN ? AND ?
@@ -1551,6 +1669,38 @@ export function createV2ToolHandlers(captureClient) {
1551
1669
  ...ensureCaptureSuccess(capture, sessionId),
1552
1670
  };
1553
1671
  },
1672
+ get_live_console_logs: async (input) => {
1673
+ const sessionId = getSessionId(input);
1674
+ if (!sessionId) {
1675
+ throw new Error('sessionId is required');
1676
+ }
1677
+ const origin = normalizeRequestedOrigin(input.url);
1678
+ const tabId = resolveOptionalTabId(input.tabId);
1679
+ const levels = resolveLiveConsoleLevels(input.levels);
1680
+ const contains = typeof input.contains === 'string' && input.contains.trim().length > 0
1681
+ ? input.contains.trim()
1682
+ : undefined;
1683
+ const sinceTs = resolveOptionalTimestamp(input.sinceTs);
1684
+ const includeRuntimeErrors = input.includeRuntimeErrors !== false;
1685
+ const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
1686
+ const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_GET_LIVE_CONSOLE_LOGS', {
1687
+ origin,
1688
+ tabId,
1689
+ levels,
1690
+ contains,
1691
+ sinceTs,
1692
+ includeRuntimeErrors,
1693
+ limit,
1694
+ }, 3_000);
1695
+ return {
1696
+ ...createBaseResponse(sessionId),
1697
+ limitsApplied: {
1698
+ maxResults: limit,
1699
+ truncated: capture.truncated ?? false,
1700
+ },
1701
+ ...ensureCaptureSuccess(capture, sessionId),
1702
+ };
1703
+ },
1554
1704
  };
1555
1705
  }
1556
1706
  function isRecord(value) {