browser-debug-mcp-bridge 1.4.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.
- package/README.md +258 -52
- package/apps/mcp-server/dist/db/events-repository.js +61 -23
- package/apps/mcp-server/dist/db/events-repository.js.map +1 -1
- package/apps/mcp-server/dist/db/migrations.js +104 -1
- package/apps/mcp-server/dist/db/migrations.js.map +1 -1
- package/apps/mcp-server/dist/db/schema.js +1 -1
- package/apps/mcp-server/dist/main.js +3 -2
- package/apps/mcp-server/dist/main.js.map +1 -1
- package/apps/mcp-server/dist/mcp/server.js +259 -51
- package/apps/mcp-server/dist/mcp/server.js.map +1 -1
- package/apps/mcp-server/dist/mcp-bridge.js +37 -3
- package/apps/mcp-server/dist/mcp-bridge.js.map +1 -1
- package/apps/mcp-server/dist/retention.js +34 -8
- package/apps/mcp-server/dist/retention.js.map +1 -1
- package/apps/mcp-server/dist/websocket/messages.js +5 -0
- package/apps/mcp-server/dist/websocket/messages.js.map +1 -1
- package/apps/mcp-server/dist/websocket/websocket-server.js +59 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js.map +1 -1
- package/package.json +1 -1
- package/scripts/mcp-start.cjs +504 -26
|
@@ -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',
|
|
@@ -225,6 +241,7 @@ const DEFAULT_EVENT_LIMIT = 50;
|
|
|
225
241
|
const MAX_LIMIT = 200;
|
|
226
242
|
const DEFAULT_SNAPSHOT_ASSET_CHUNK_BYTES = 64 * 1024;
|
|
227
243
|
const MAX_SNAPSHOT_ASSET_CHUNK_BYTES = 256 * 1024;
|
|
244
|
+
const LIVE_SESSION_DISCONNECTED_CODE = 'LIVE_SESSION_DISCONNECTED';
|
|
228
245
|
const NETWORK_DOMAIN_GROUP_SQL = `
|
|
229
246
|
CASE
|
|
230
247
|
WHEN instr(replace(replace(url, 'https://', ''), 'http://', ''), '/') > 0
|
|
@@ -236,6 +253,16 @@ const NETWORK_DOMAIN_GROUP_SQL = `
|
|
|
236
253
|
ELSE replace(replace(url, 'https://', ''), 'http://', '')
|
|
237
254
|
END
|
|
238
255
|
`;
|
|
256
|
+
class LiveSessionDisconnectedError extends Error {
|
|
257
|
+
code = LIVE_SESSION_DISCONNECTED_CODE;
|
|
258
|
+
constructor(sessionId, reason) {
|
|
259
|
+
const normalizedReason = typeof reason === 'string' && reason.trim().length > 0
|
|
260
|
+
? reason.trim()
|
|
261
|
+
: 'Extension connection is stale or unavailable';
|
|
262
|
+
super(`${LIVE_SESSION_DISCONNECTED_CODE}: Session ${sessionId} is not connected to a live extension target. ${normalizedReason}. Start a fresh session in the extension and retry with a connected sessionId from list_sessions.`);
|
|
263
|
+
this.name = 'LiveSessionDisconnectedError';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
239
266
|
function resolveLimit(value, fallback) {
|
|
240
267
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
241
268
|
return fallback;
|
|
@@ -292,6 +319,65 @@ function parseRequestedTypes(value) {
|
|
|
292
319
|
.map((entry) => mapRequestedEventType(entry));
|
|
293
320
|
return Array.from(new Set(normalized));
|
|
294
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
|
+
}
|
|
295
381
|
function resolveLastUrl(payload) {
|
|
296
382
|
const candidates = [payload.url, payload.to, payload.href, payload.location];
|
|
297
383
|
for (const candidate of candidates) {
|
|
@@ -302,12 +388,17 @@ function resolveLastUrl(payload) {
|
|
|
302
388
|
return undefined;
|
|
303
389
|
}
|
|
304
390
|
function mapEventRecord(row) {
|
|
391
|
+
const payload = readJsonPayload(row.payload_json);
|
|
305
392
|
return {
|
|
306
393
|
eventId: row.event_id,
|
|
307
394
|
sessionId: row.session_id,
|
|
308
395
|
timestamp: row.ts,
|
|
309
396
|
type: row.type,
|
|
310
|
-
|
|
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,
|
|
311
402
|
};
|
|
312
403
|
}
|
|
313
404
|
function classifyNetworkFailure(status, errorClass) {
|
|
@@ -517,13 +608,62 @@ function asStringArray(value, maxItems) {
|
|
|
517
608
|
.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
518
609
|
.slice(0, maxItems);
|
|
519
610
|
}
|
|
520
|
-
|
|
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
|
+
}
|
|
631
|
+
function isLiveSessionDisconnectedMessage(message) {
|
|
632
|
+
const normalized = message.toLowerCase();
|
|
633
|
+
return normalized.includes('no active extension connection')
|
|
634
|
+
|| normalized.includes('receiving end does not exist')
|
|
635
|
+
|| normalized.includes('could not establish connection')
|
|
636
|
+
|| normalized.includes('connection closed before capture completed')
|
|
637
|
+
|| normalized.includes('websocket manager closed')
|
|
638
|
+
|| normalized.includes('extension target is unavailable')
|
|
639
|
+
|| normalized.includes('target tab for this session is unavailable');
|
|
640
|
+
}
|
|
641
|
+
function normalizeCaptureError(sessionId, error) {
|
|
642
|
+
const fallback = error instanceof Error ? error : new Error(String(error));
|
|
643
|
+
const message = fallback.message ?? '';
|
|
644
|
+
if (isLiveSessionDisconnectedMessage(message)) {
|
|
645
|
+
return new LiveSessionDisconnectedError(sessionId, message);
|
|
646
|
+
}
|
|
647
|
+
return fallback;
|
|
648
|
+
}
|
|
649
|
+
function isLiveSessionDisconnectedError(error) {
|
|
650
|
+
return error instanceof LiveSessionDisconnectedError;
|
|
651
|
+
}
|
|
652
|
+
async function executeLiveCapture(captureClient, sessionId, command, payload, timeoutMs) {
|
|
653
|
+
try {
|
|
654
|
+
return await captureClient.execute(sessionId, command, payload, timeoutMs);
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
throw normalizeCaptureError(sessionId, error);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function ensureCaptureSuccess(result, sessionId) {
|
|
521
661
|
if (!result.ok) {
|
|
522
|
-
throw new Error(result.error ?? 'Capture command failed');
|
|
662
|
+
throw normalizeCaptureError(sessionId, new Error(result.error ?? 'Capture command failed'));
|
|
523
663
|
}
|
|
524
664
|
return result.payload ?? {};
|
|
525
665
|
}
|
|
526
|
-
export function createV1ToolHandlers(getDb) {
|
|
666
|
+
export function createV1ToolHandlers(getDb, getSessionConnectionState) {
|
|
527
667
|
return {
|
|
528
668
|
list_sessions: async (input) => {
|
|
529
669
|
const db = getDb();
|
|
@@ -577,6 +717,23 @@ export function createV1ToolHandlers(getDb) {
|
|
|
577
717
|
dpr: row.dpr ?? undefined,
|
|
578
718
|
safeMode: row.safe_mode === 1,
|
|
579
719
|
pinned: row.pinned === 1,
|
|
720
|
+
liveConnection: (() => {
|
|
721
|
+
const state = getSessionConnectionState?.(row.session_id);
|
|
722
|
+
if (!state) {
|
|
723
|
+
return {
|
|
724
|
+
connected: false,
|
|
725
|
+
lastHeartbeatAt: undefined,
|
|
726
|
+
disconnectReason: row.ended_at ? 'manual_stop' : undefined,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
connected: state.connected,
|
|
731
|
+
connectedAt: state.connectedAt,
|
|
732
|
+
lastHeartbeatAt: state.lastHeartbeatAt,
|
|
733
|
+
disconnectedAt: state.disconnectedAt,
|
|
734
|
+
disconnectReason: state.disconnectReason,
|
|
735
|
+
};
|
|
736
|
+
})(),
|
|
580
737
|
}));
|
|
581
738
|
return {
|
|
582
739
|
...createBaseResponse(),
|
|
@@ -656,14 +813,18 @@ export function createV1ToolHandlers(getDb) {
|
|
|
656
813
|
get_recent_events: async (input) => {
|
|
657
814
|
const db = getDb();
|
|
658
815
|
const sessionId = getSessionId(input);
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
816
|
+
const origin = normalizeRequestedOrigin(input.url);
|
|
817
|
+
ensureSessionOrOriginFilter(sessionId, origin);
|
|
662
818
|
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
663
819
|
const offset = resolveOffset(input.offset);
|
|
664
820
|
const requestedTypes = parseRequestedTypes(input.types ?? input.eventTypes);
|
|
665
|
-
const params = [
|
|
666
|
-
const where = [
|
|
821
|
+
const params = [];
|
|
822
|
+
const where = [];
|
|
823
|
+
if (sessionId) {
|
|
824
|
+
where.push('session_id = ?');
|
|
825
|
+
params.push(sessionId);
|
|
826
|
+
}
|
|
827
|
+
appendEventOriginFilter(where, params, origin);
|
|
667
828
|
if (requestedTypes.length > 0) {
|
|
668
829
|
const placeholders = requestedTypes.map(() => '?').join(', ');
|
|
669
830
|
where.push(`type IN (${placeholders})`);
|
|
@@ -671,7 +832,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
671
832
|
}
|
|
672
833
|
const rows = db
|
|
673
834
|
.prepare(`
|
|
674
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
835
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
675
836
|
FROM events
|
|
676
837
|
WHERE ${where.join(' AND ')}
|
|
677
838
|
ORDER BY ts DESC
|
|
@@ -695,20 +856,26 @@ export function createV1ToolHandlers(getDb) {
|
|
|
695
856
|
get_navigation_history: async (input) => {
|
|
696
857
|
const db = getDb();
|
|
697
858
|
const sessionId = getSessionId(input);
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
859
|
+
const origin = normalizeRequestedOrigin(input.url);
|
|
860
|
+
ensureSessionOrOriginFilter(sessionId, origin);
|
|
701
861
|
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
702
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);
|
|
703
870
|
const rows = db
|
|
704
871
|
.prepare(`
|
|
705
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
872
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
706
873
|
FROM events
|
|
707
|
-
WHERE
|
|
874
|
+
WHERE ${where.join(' AND ')}
|
|
708
875
|
ORDER BY ts DESC
|
|
709
876
|
LIMIT ? OFFSET ?
|
|
710
877
|
`)
|
|
711
|
-
.all(
|
|
878
|
+
.all(...params, limit + 1, offset);
|
|
712
879
|
const truncated = rows.length > limit;
|
|
713
880
|
return {
|
|
714
881
|
...createBaseResponse(sessionId),
|
|
@@ -726,23 +893,27 @@ export function createV1ToolHandlers(getDb) {
|
|
|
726
893
|
get_console_events: async (input) => {
|
|
727
894
|
const db = getDb();
|
|
728
895
|
const sessionId = getSessionId(input);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
896
|
+
const origin = normalizeRequestedOrigin(input.url);
|
|
897
|
+
ensureSessionOrOriginFilter(sessionId, origin);
|
|
732
898
|
const level = typeof input.level === 'string' ? input.level : undefined;
|
|
733
899
|
const limit = resolveLimit(input.limit, DEFAULT_EVENT_LIMIT);
|
|
734
900
|
const offset = resolveOffset(input.offset);
|
|
735
|
-
const params = [
|
|
736
|
-
|
|
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);
|
|
737
908
|
if (level) {
|
|
738
|
-
|
|
909
|
+
where.push("json_extract(payload_json, '$.level') = ?");
|
|
739
910
|
params.push(level);
|
|
740
911
|
}
|
|
741
912
|
const rows = db
|
|
742
913
|
.prepare(`
|
|
743
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
914
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
744
915
|
FROM events
|
|
745
|
-
WHERE
|
|
916
|
+
WHERE ${where.join(' AND ')}
|
|
746
917
|
ORDER BY ts DESC
|
|
747
918
|
LIMIT ? OFFSET ?
|
|
748
919
|
`)
|
|
@@ -814,6 +985,8 @@ export function createV1ToolHandlers(getDb) {
|
|
|
814
985
|
get_network_failures: async (input) => {
|
|
815
986
|
const db = getDb();
|
|
816
987
|
const sessionId = typeof input.sessionId === 'string' ? input.sessionId : undefined;
|
|
988
|
+
const origin = normalizeRequestedOrigin(input.url);
|
|
989
|
+
ensureSessionOrOriginFilter(sessionId, origin);
|
|
817
990
|
const groupBy = typeof input.groupBy === 'string' ? input.groupBy : undefined;
|
|
818
991
|
const errorType = typeof input.errorType === 'string' ? input.errorType : undefined;
|
|
819
992
|
const limit = resolveLimit(input.limit, DEFAULT_LIST_LIMIT);
|
|
@@ -825,6 +998,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
825
998
|
where.push('session_id = ?');
|
|
826
999
|
params.push(sessionId);
|
|
827
1000
|
}
|
|
1001
|
+
appendNetworkOriginFilter(where, params, origin);
|
|
828
1002
|
where.push(errorFilter);
|
|
829
1003
|
if (errorFilter === 'error_class = ?' && errorType) {
|
|
830
1004
|
params.push(errorType);
|
|
@@ -872,7 +1046,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
872
1046
|
}
|
|
873
1047
|
const rows = db
|
|
874
1048
|
.prepare(`
|
|
875
|
-
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
|
|
876
1050
|
FROM network
|
|
877
1051
|
${whereClause}
|
|
878
1052
|
ORDER BY ts_start DESC
|
|
@@ -897,6 +1071,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
897
1071
|
durationMs: row.duration_ms ?? undefined,
|
|
898
1072
|
method: row.method,
|
|
899
1073
|
url: row.url,
|
|
1074
|
+
origin: row.origin ?? undefined,
|
|
900
1075
|
status: row.status ?? undefined,
|
|
901
1076
|
initiator: row.initiator ?? undefined,
|
|
902
1077
|
errorType: classifyNetworkFailure(row.status, row.error_class),
|
|
@@ -917,7 +1092,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
917
1092
|
const offset = resolveOffset(input.offset);
|
|
918
1093
|
const rows = db
|
|
919
1094
|
.prepare(`
|
|
920
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
1095
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
921
1096
|
FROM events
|
|
922
1097
|
WHERE session_id = ?
|
|
923
1098
|
AND type IN ('ui', 'element_ref')
|
|
@@ -951,7 +1126,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
951
1126
|
const windowMs = lookbackSeconds * 1000;
|
|
952
1127
|
const latestErrorEvent = db
|
|
953
1128
|
.prepare(`
|
|
954
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
1129
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
955
1130
|
FROM events
|
|
956
1131
|
WHERE session_id = ?
|
|
957
1132
|
AND (type = 'error' OR (type = 'console' AND json_extract(payload_json, '$.level') = 'error'))
|
|
@@ -961,7 +1136,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
961
1136
|
.get(sessionId);
|
|
962
1137
|
const latestNetworkFailure = db
|
|
963
1138
|
.prepare(`
|
|
964
|
-
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
|
|
965
1140
|
FROM network
|
|
966
1141
|
WHERE session_id = ?
|
|
967
1142
|
AND (error_class IS NOT NULL OR COALESCE(status, 0) >= 400)
|
|
@@ -989,7 +1164,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
989
1164
|
const windowEnd = anchorTs + 1_000;
|
|
990
1165
|
const eventRows = db
|
|
991
1166
|
.prepare(`
|
|
992
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
1167
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
993
1168
|
FROM events
|
|
994
1169
|
WHERE session_id = ?
|
|
995
1170
|
AND ts BETWEEN ? AND ?
|
|
@@ -998,7 +1173,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
998
1173
|
.all(sessionId, windowStart, windowEnd);
|
|
999
1174
|
const networkRows = db
|
|
1000
1175
|
.prepare(`
|
|
1001
|
-
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
|
|
1002
1177
|
FROM network
|
|
1003
1178
|
WHERE session_id = ?
|
|
1004
1179
|
AND ts_start BETWEEN ? AND ?
|
|
@@ -1081,7 +1256,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
1081
1256
|
}
|
|
1082
1257
|
const anchorEvent = db
|
|
1083
1258
|
.prepare(`
|
|
1084
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
1259
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
1085
1260
|
FROM events
|
|
1086
1261
|
WHERE session_id = ? AND event_id = ?
|
|
1087
1262
|
LIMIT 1
|
|
@@ -1096,7 +1271,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
1096
1271
|
const windowEnd = anchorEvent.ts + windowMs;
|
|
1097
1272
|
const nearbyEvents = db
|
|
1098
1273
|
.prepare(`
|
|
1099
|
-
SELECT event_id, session_id, ts, type, payload_json
|
|
1274
|
+
SELECT event_id, session_id, ts, type, payload_json, tab_id, origin
|
|
1100
1275
|
FROM events
|
|
1101
1276
|
WHERE session_id = ?
|
|
1102
1277
|
AND event_id != ?
|
|
@@ -1105,7 +1280,7 @@ export function createV1ToolHandlers(getDb) {
|
|
|
1105
1280
|
.all(sessionId, eventId, windowStart, windowEnd);
|
|
1106
1281
|
const nearbyNetworkFailures = db
|
|
1107
1282
|
.prepare(`
|
|
1108
|
-
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
|
|
1109
1284
|
FROM network
|
|
1110
1285
|
WHERE session_id = ?
|
|
1111
1286
|
AND ts_start BETWEEN ? AND ?
|
|
@@ -1373,14 +1548,14 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1373
1548
|
}
|
|
1374
1549
|
const maxDepth = resolveCaptureDepth(input.maxDepth, 3);
|
|
1375
1550
|
const maxBytes = resolveCaptureBytes(input.maxBytes, 50_000);
|
|
1376
|
-
const capture = await captureClient
|
|
1551
|
+
const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_DOM_SUBTREE', { selector, maxDepth, maxBytes }, 4_000);
|
|
1377
1552
|
return {
|
|
1378
1553
|
...createBaseResponse(sessionId),
|
|
1379
1554
|
limitsApplied: {
|
|
1380
1555
|
maxResults: maxBytes,
|
|
1381
1556
|
truncated: capture.truncated ?? false,
|
|
1382
1557
|
},
|
|
1383
|
-
...ensureCaptureSuccess(capture),
|
|
1558
|
+
...ensureCaptureSuccess(capture, sessionId),
|
|
1384
1559
|
};
|
|
1385
1560
|
},
|
|
1386
1561
|
get_dom_document: async (input) => {
|
|
@@ -1392,21 +1567,22 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1392
1567
|
const maxBytes = resolveCaptureBytes(input.maxBytes, 200_000);
|
|
1393
1568
|
const maxDepth = resolveCaptureDepth(input.maxDepth, 4);
|
|
1394
1569
|
try {
|
|
1395
|
-
const capture = await captureClient
|
|
1570
|
+
const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_DOM_DOCUMENT', { mode, maxBytes, maxDepth }, 4_000);
|
|
1396
1571
|
return {
|
|
1397
1572
|
...createBaseResponse(sessionId),
|
|
1398
1573
|
limitsApplied: {
|
|
1399
1574
|
maxResults: maxBytes,
|
|
1400
1575
|
truncated: capture.truncated ?? false,
|
|
1401
1576
|
},
|
|
1402
|
-
...ensureCaptureSuccess(capture),
|
|
1577
|
+
...ensureCaptureSuccess(capture, sessionId),
|
|
1403
1578
|
};
|
|
1404
1579
|
}
|
|
1405
1580
|
catch (error) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1581
|
+
const normalized = normalizeCaptureError(sessionId, error);
|
|
1582
|
+
if (mode !== 'html' || isLiveSessionDisconnectedError(normalized)) {
|
|
1583
|
+
throw normalized;
|
|
1408
1584
|
}
|
|
1409
|
-
const fallback = await captureClient
|
|
1585
|
+
const fallback = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_DOM_DOCUMENT', { mode: 'outline', maxBytes, maxDepth }, 4_000);
|
|
1410
1586
|
return {
|
|
1411
1587
|
...createBaseResponse(sessionId),
|
|
1412
1588
|
limitsApplied: {
|
|
@@ -1414,7 +1590,7 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1414
1590
|
truncated: true,
|
|
1415
1591
|
},
|
|
1416
1592
|
fallbackReason: 'timeout',
|
|
1417
|
-
...ensureCaptureSuccess(fallback),
|
|
1593
|
+
...ensureCaptureSuccess(fallback, sessionId),
|
|
1418
1594
|
};
|
|
1419
1595
|
}
|
|
1420
1596
|
},
|
|
@@ -1428,14 +1604,14 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1428
1604
|
throw new Error('selector is required');
|
|
1429
1605
|
}
|
|
1430
1606
|
const properties = asStringArray(input.properties, 64);
|
|
1431
|
-
const capture = await captureClient
|
|
1607
|
+
const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_COMPUTED_STYLES', { selector, properties }, 3_000);
|
|
1432
1608
|
return {
|
|
1433
1609
|
...createBaseResponse(sessionId),
|
|
1434
1610
|
limitsApplied: {
|
|
1435
1611
|
maxResults: properties.length || 8,
|
|
1436
1612
|
truncated: capture.truncated ?? false,
|
|
1437
1613
|
},
|
|
1438
|
-
...ensureCaptureSuccess(capture),
|
|
1614
|
+
...ensureCaptureSuccess(capture, sessionId),
|
|
1439
1615
|
};
|
|
1440
1616
|
},
|
|
1441
1617
|
get_layout_metrics: async (input) => {
|
|
@@ -1444,14 +1620,14 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1444
1620
|
throw new Error('sessionId is required');
|
|
1445
1621
|
}
|
|
1446
1622
|
const selector = typeof input.selector === 'string' ? input.selector : undefined;
|
|
1447
|
-
const capture = await captureClient
|
|
1623
|
+
const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_LAYOUT_METRICS', { selector }, 3_000);
|
|
1448
1624
|
return {
|
|
1449
1625
|
...createBaseResponse(sessionId),
|
|
1450
1626
|
limitsApplied: {
|
|
1451
1627
|
maxResults: 1,
|
|
1452
1628
|
truncated: capture.truncated ?? false,
|
|
1453
1629
|
},
|
|
1454
|
-
...ensureCaptureSuccess(capture),
|
|
1630
|
+
...ensureCaptureSuccess(capture, sessionId),
|
|
1455
1631
|
};
|
|
1456
1632
|
},
|
|
1457
1633
|
capture_ui_snapshot: async (input) => {
|
|
@@ -1473,7 +1649,7 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1473
1649
|
const maxDepth = resolveCaptureDepth(input.maxDepth, 3);
|
|
1474
1650
|
const maxBytes = resolveCaptureBytes(input.maxBytes, 50_000);
|
|
1475
1651
|
const maxAncestors = resolveCaptureAncestors(input.maxAncestors, 4);
|
|
1476
|
-
const capture = await captureClient
|
|
1652
|
+
const capture = await executeLiveCapture(captureClient, sessionId, 'CAPTURE_UI_SNAPSHOT', {
|
|
1477
1653
|
selector,
|
|
1478
1654
|
trigger,
|
|
1479
1655
|
mode,
|
|
@@ -1490,7 +1666,39 @@ export function createV2ToolHandlers(captureClient) {
|
|
|
1490
1666
|
maxResults: maxBytes,
|
|
1491
1667
|
truncated: capture.truncated ?? false,
|
|
1492
1668
|
},
|
|
1493
|
-
...ensureCaptureSuccess(capture),
|
|
1669
|
+
...ensureCaptureSuccess(capture, sessionId),
|
|
1670
|
+
};
|
|
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),
|
|
1494
1702
|
};
|
|
1495
1703
|
},
|
|
1496
1704
|
};
|
|
@@ -1542,7 +1750,7 @@ export function createMCPServer(overrides = {}, options = {}) {
|
|
|
1542
1750
|
const logger = options.logger ?? createDefaultMcpLogger();
|
|
1543
1751
|
const v2Handlers = options.captureClient ? createV2ToolHandlers(options.captureClient) : {};
|
|
1544
1752
|
const tools = createToolRegistry({
|
|
1545
|
-
...createV1ToolHandlers(() => getConnection().db),
|
|
1753
|
+
...createV1ToolHandlers(() => getConnection().db, options.getSessionConnectionState),
|
|
1546
1754
|
...v2Handlers,
|
|
1547
1755
|
...overrides,
|
|
1548
1756
|
});
|