browsertrack 0.2.1 → 0.2.3
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/AGENTS.md +9 -6
- package/README.md +2 -0
- package/dist/{chunk-464D4U2U.js → chunk-5NR5K3ER.js} +299 -44
- package/dist/chunk-5NR5K3ER.js.map +1 -0
- package/dist/{chunk-3HOXPTM2.js → chunk-G5CIZSQM.js} +808 -53
- package/dist/chunk-G5CIZSQM.js.map +1 -0
- package/dist/{chunk-6VA7GBAO.js → chunk-ONPW7AYL.js} +144 -12
- package/dist/chunk-ONPW7AYL.js.map +1 -0
- package/dist/{chunk-INXDWPJW.js → chunk-PG4JJDCV.js} +353 -119
- package/dist/chunk-PG4JJDCV.js.map +1 -0
- package/dist/cli/index.js +1058 -546
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +449 -128
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -2
- package/dist/client.iife.js +121 -15
- package/dist/core/index.d.ts +32 -2
- package/dist/core/index.js +11 -1
- package/dist/daemon/index.d.ts +2 -2
- package/dist/daemon/index.js +6 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -7
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +7 -4
- package/dist/{server-DiVmTrIR.d.ts → server-BRG-RQQP.d.ts} +10 -1
- package/docs/cli.md +4 -1
- package/docs/getting-started.md +60 -6
- package/docs/mcp-reference.md +42 -0
- package/package.json +1 -1
- package/packages/cli/src/index.ts +247 -151
- package/packages/client/src/interceptors/navigation.ts +38 -26
- package/packages/client/src/interceptors/network.ts +22 -17
- package/packages/client/src/notes/inspector.ts +289 -65
- package/packages/client/src/source/resolver.ts +9 -3
- package/packages/client/src/transport/websocket.ts +23 -18
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/safety.ts +86 -0
- package/packages/core/src/selector.ts +110 -15
- package/packages/daemon/src/server/daemon.ts +7 -1
- package/packages/daemon/src/server/http.ts +125 -5
- package/packages/daemon/src/server/ws.ts +33 -29
- package/packages/daemon/src/storage/db.ts +57 -35
- package/packages/mcp/src/handlers.ts +121 -45
- package/packages/mcp/src/server.ts +221 -3
- package/test/core/safety.test.ts +106 -0
- package/test/core/selector.test.ts +133 -0
- package/test/daemon/storage.test.ts +36 -0
- package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
- package/test/mcp/auto-start.test.ts +87 -0
- package/dist/chunk-3HOXPTM2.js.map +0 -1
- package/dist/chunk-464D4U2U.js.map +0 -1
- package/dist/chunk-6VA7GBAO.js.map +0 -1
- package/dist/chunk-7OCOQGDN.js +0 -635
- package/dist/chunk-7OCOQGDN.js.map +0 -1
- package/dist/chunk-INXDWPJW.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -99,10 +99,9 @@ When an error or unexpected behavior occurs in the frontend:
|
|
|
99
99
|
|
|
100
100
|
If BrowserTrack is not yet active in your project:
|
|
101
101
|
|
|
102
|
-
### 1.
|
|
103
|
-
|
|
104
|
-
browsertrack start
|
|
105
|
-
```
|
|
102
|
+
### 1. Zero-Config Daemon Auto-Boot (Singleton Daemon)
|
|
103
|
+
You do **not** need to manually run `browsertrack start`! When your IDE connects to BrowserTrack via MCP (`browsertrack mcp`), the background HTTP/WebSocket server is launched **automatically** on `http://127.0.0.1:7331` as a **singleton daemon**. Multiple windows, workspaces, or sub-agent sessions seamlessly share this single daemon without spawning duplicates or conflicting on ports.
|
|
104
|
+
*(You can still manually run `browsertrack start` or `browsertrack stop` if you want a standalone terminal process).*
|
|
106
105
|
|
|
107
106
|
### 2. Connect the Browser Client
|
|
108
107
|
- **Via HTML script tag:**
|
|
@@ -116,17 +115,21 @@ browsertrack start
|
|
|
116
115
|
|
|
117
116
|
### 3. MCP Configuration
|
|
118
117
|
Ensure BrowserTrack MCP is configured in your editor:
|
|
118
|
+
|
|
119
119
|
```json
|
|
120
120
|
{
|
|
121
121
|
"mcpServers": {
|
|
122
122
|
"browsertrack": {
|
|
123
|
-
"command": "
|
|
124
|
-
"args": ["mcp"]
|
|
123
|
+
"command": "npx",
|
|
124
|
+
"args": ["-y", "browsertrack@latest", "mcp"]
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
> **Note on PATH errors (`executable file not found in $PATH`):**
|
|
131
|
+
> If your IDE cannot find `npx` or `browsertrack` in `$PATH` (common in GUI apps on macOS using NVM or Homebrew), provide the absolute path to `npx` (e.g. `~/.nvm/versions/node/v20.x.x/bin/npx`) along with `PATH` in the `env` object.
|
|
132
|
+
|
|
130
133
|
---
|
|
131
134
|
|
|
132
135
|
## 💡 Best Practices for AI Agents
|
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ browsertrack start
|
|
|
17
17
|
|
|
18
18
|
This starts the local diagnostics server at `http://127.0.0.1:7331` (WebSocket at `ws://127.0.0.1:7331`).
|
|
19
19
|
|
|
20
|
+
> **⚡ Zero-Config Auto-Start**: If you configure BrowserTrack via MCP in your AI editor (Cursor, Antigravity, Claude Code), you do **not** need to manually run `browsertrack start`! The MCP server automatically launches and manages a **single background daemon (singleton)** on port `7331`. Multiple windows or sessions share this single daemon without duplicate processes or port conflicts.
|
|
21
|
+
|
|
20
22
|
### 2. Connect Your Web Project
|
|
21
23
|
|
|
22
24
|
#### Option A: Zero-install script tag (Plain HTML / Vite / Next.js)
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
SessionManager,
|
|
6
6
|
StorageDB,
|
|
7
7
|
VerificationEngine,
|
|
8
|
+
createDaemon,
|
|
8
9
|
getDaemonConfig
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-G5CIZSQM.js";
|
|
10
11
|
|
|
11
12
|
// packages/mcp/src/tools.ts
|
|
12
13
|
var TOOLS = [
|
|
@@ -273,6 +274,108 @@ var TOOLS = [
|
|
|
273
274
|
];
|
|
274
275
|
|
|
275
276
|
// packages/mcp/src/handlers.ts
|
|
277
|
+
async function sendSessionCommand(ctx, sessionId, command, timeoutMs = 5e3) {
|
|
278
|
+
if (ctx.daemonInitPromise) {
|
|
279
|
+
try {
|
|
280
|
+
await Promise.race([ctx.daemonInitPromise, new Promise((r) => setTimeout(r, 2e3))]);
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {
|
|
285
|
+
const targetSession = sessionId ? ctx.db.getSession(sessionId) : ctx.sessionManager.getAnyActiveSession();
|
|
286
|
+
if (targetSession) {
|
|
287
|
+
const res = await ctx.sessionManager.sendCommand(targetSession.id, command, timeoutMs);
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
throw new Error(res.error || res.reason || `Command ${command.type} failed`);
|
|
290
|
+
}
|
|
291
|
+
return res;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (ctx.daemonUrl) {
|
|
295
|
+
try {
|
|
296
|
+
const resp = await fetch(`${ctx.daemonUrl}/api/command`, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { "Content-Type": "application/json" },
|
|
299
|
+
body: JSON.stringify({ sessionId, command, timeoutMs }),
|
|
300
|
+
signal: AbortSignal.timeout(timeoutMs + 2e3)
|
|
301
|
+
});
|
|
302
|
+
if (resp.ok) {
|
|
303
|
+
const res = await resp.json();
|
|
304
|
+
if (!res.ok) {
|
|
305
|
+
throw new Error(res.error || res.reason || `Command ${command.type} failed`);
|
|
306
|
+
}
|
|
307
|
+
return res;
|
|
308
|
+
}
|
|
309
|
+
const errJson = await resp.json().catch(() => ({}));
|
|
310
|
+
if (errJson.error) {
|
|
311
|
+
throw new Error(errJson.error);
|
|
312
|
+
}
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (err.message && !err.message.includes("fetch failed") && !err.message.includes("ECONNREFUSED")) {
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
throw new Error(
|
|
320
|
+
'No active browser session connected. Please ensure the BrowserTrack daemon is running ("browsertrack start") and your application tab is open in the browser.'
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
async function runVerifyIncident(ctx, incidentId, options) {
|
|
324
|
+
if (ctx.verificationEngine && ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {
|
|
325
|
+
return await ctx.verificationEngine.verifyIncident(incidentId, options);
|
|
326
|
+
}
|
|
327
|
+
if (ctx.daemonUrl) {
|
|
328
|
+
try {
|
|
329
|
+
const timeoutMs = (options?.observationWindowMs || 3e3) + 7e3;
|
|
330
|
+
const resp = await fetch(`${ctx.daemonUrl}/api/verify/incident`, {
|
|
331
|
+
method: "POST",
|
|
332
|
+
headers: { "Content-Type": "application/json" },
|
|
333
|
+
body: JSON.stringify({ incidentId, options }),
|
|
334
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
335
|
+
});
|
|
336
|
+
if (resp.ok) {
|
|
337
|
+
const data = await resp.json();
|
|
338
|
+
if (data.ok) return data.result;
|
|
339
|
+
throw new Error(data.error);
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
if (err.message && !err.message.includes("fetch failed") && !err.message.includes("ECONNREFUSED")) {
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
throw new Error(
|
|
348
|
+
'Verification failed: No active browser session connected. Please ensure the BrowserTrack daemon is running ("browsertrack start") and your application tab is open in the browser.'
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
async function runVerifyNote(ctx, noteId, options) {
|
|
352
|
+
if (ctx.noteVerificationEngine && ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {
|
|
353
|
+
return await ctx.noteVerificationEngine.verifyNote(noteId, options);
|
|
354
|
+
}
|
|
355
|
+
if (ctx.daemonUrl) {
|
|
356
|
+
try {
|
|
357
|
+
const timeoutMs = (options?.observationWindowMs || 3e3) + 7e3;
|
|
358
|
+
const resp = await fetch(`${ctx.daemonUrl}/api/verify/note`, {
|
|
359
|
+
method: "POST",
|
|
360
|
+
headers: { "Content-Type": "application/json" },
|
|
361
|
+
body: JSON.stringify({ noteId, options }),
|
|
362
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
363
|
+
});
|
|
364
|
+
if (resp.ok) {
|
|
365
|
+
const data = await resp.json();
|
|
366
|
+
if (data.ok) return data.result;
|
|
367
|
+
throw new Error(data.error);
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
if (err.message && !err.message.includes("fetch failed") && !err.message.includes("ECONNREFUSED")) {
|
|
371
|
+
throw err;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw new Error(
|
|
376
|
+
'Verification failed: No active browser session connected. Please ensure the BrowserTrack daemon is running ("browsertrack start") and your application tab is open in the browser.'
|
|
377
|
+
);
|
|
378
|
+
}
|
|
276
379
|
async function handleToolCall(name, args, ctx) {
|
|
277
380
|
const { db, sessionManager, verificationEngine, noteVerificationEngine } = ctx;
|
|
278
381
|
switch (name) {
|
|
@@ -422,38 +525,18 @@ async function handleToolCall(name, args, ctx) {
|
|
|
422
525
|
};
|
|
423
526
|
}
|
|
424
527
|
case "get_page_state": {
|
|
425
|
-
|
|
426
|
-
throw new Error("Live browser connection not available: Daemon session manager not attached.");
|
|
427
|
-
}
|
|
428
|
-
let session = args.sessionId ? db.getSession(args.sessionId) : sessionManager.getAnyActiveSession();
|
|
429
|
-
if (!session) {
|
|
430
|
-
throw new Error("No active browser session connected.");
|
|
431
|
-
}
|
|
432
|
-
const cmdRes = await sessionManager.sendCommand(session.id, {
|
|
528
|
+
const cmdRes = await sendSessionCommand(ctx, args.sessionId, {
|
|
433
529
|
id: `cmd_mcp_${Date.now()}`,
|
|
434
530
|
type: "get_page_state"
|
|
435
531
|
});
|
|
436
|
-
if (!cmdRes.ok) {
|
|
437
|
-
throw new Error(cmdRes.error || "Failed to retrieve page state from browser.");
|
|
438
|
-
}
|
|
439
532
|
return cmdRes.result;
|
|
440
533
|
}
|
|
441
534
|
case "capture_element": {
|
|
442
|
-
|
|
443
|
-
throw new Error("Live browser connection not available: Daemon session manager not attached.");
|
|
444
|
-
}
|
|
445
|
-
let session = args.sessionId ? db.getSession(args.sessionId) : sessionManager.getAnyActiveSession();
|
|
446
|
-
if (!session) {
|
|
447
|
-
throw new Error("No active browser session connected.");
|
|
448
|
-
}
|
|
449
|
-
const cmdRes = await sessionManager.sendCommand(session.id, {
|
|
535
|
+
const cmdRes = await sendSessionCommand(ctx, args.sessionId, {
|
|
450
536
|
id: `cmd_mcp_${Date.now()}`,
|
|
451
537
|
type: "capture_element",
|
|
452
538
|
params: { selector: args.selector }
|
|
453
539
|
});
|
|
454
|
-
if (!cmdRes.ok) {
|
|
455
|
-
throw new Error(cmdRes.error || cmdRes.reason || "Failed to capture element screenshot.");
|
|
456
|
-
}
|
|
457
540
|
return {
|
|
458
541
|
ok: true,
|
|
459
542
|
format: cmdRes.result?.format || "webp",
|
|
@@ -463,10 +546,7 @@ async function handleToolCall(name, args, ctx) {
|
|
|
463
546
|
};
|
|
464
547
|
}
|
|
465
548
|
case "verify_incident": {
|
|
466
|
-
|
|
467
|
-
throw new Error("Verification engine not available: Daemon session manager not attached.");
|
|
468
|
-
}
|
|
469
|
-
const res = await verificationEngine.verifyIncident(args.incidentId, {
|
|
549
|
+
const res = await runVerifyIncident(ctx, args.incidentId, {
|
|
470
550
|
route: args.route,
|
|
471
551
|
targetSelector: args.targetSelector,
|
|
472
552
|
expect: args.expect,
|
|
@@ -617,10 +697,7 @@ async function handleToolCall(name, args, ctx) {
|
|
|
617
697
|
};
|
|
618
698
|
}
|
|
619
699
|
case "verify_note": {
|
|
620
|
-
|
|
621
|
-
throw new Error("Note verification engine not available: Daemon session manager not attached.");
|
|
622
|
-
}
|
|
623
|
-
const res = await noteVerificationEngine.verifyNote(args.noteId, {
|
|
700
|
+
const res = await runVerifyNote(ctx, args.noteId, {
|
|
624
701
|
observationWindowMs: args.observationWindowMs
|
|
625
702
|
});
|
|
626
703
|
return res;
|
|
@@ -633,24 +710,17 @@ async function handleToolCall(name, args, ctx) {
|
|
|
633
710
|
return v;
|
|
634
711
|
}
|
|
635
712
|
case "capture_note_context": {
|
|
636
|
-
|
|
637
|
-
throw new Error("Live browser connection not available: Daemon session manager not attached.");
|
|
638
|
-
}
|
|
639
|
-
let session = args.sessionId ? db.getSession(args.sessionId) : sessionManager.getAnyActiveSession();
|
|
640
|
-
if (!session) {
|
|
641
|
-
throw new Error("No active browser session connected.");
|
|
642
|
-
}
|
|
643
|
-
const queryCmd = await sessionManager.sendCommand(session.id, {
|
|
713
|
+
const queryCmd = await sendSessionCommand(ctx, args.sessionId, {
|
|
644
714
|
id: `cmd_ctx_${Date.now()}`,
|
|
645
715
|
type: "query_element",
|
|
646
716
|
params: { selector: args.selector }
|
|
647
717
|
});
|
|
648
|
-
const overflowCmd = await
|
|
718
|
+
const overflowCmd = await sendSessionCommand(ctx, args.sessionId, {
|
|
649
719
|
id: `cmd_ovf_${Date.now()}`,
|
|
650
720
|
type: "check_overflow",
|
|
651
721
|
params: { selector: args.selector }
|
|
652
722
|
});
|
|
653
|
-
const styleCmd = await
|
|
723
|
+
const styleCmd = await sendSessionCommand(ctx, args.sessionId, {
|
|
654
724
|
id: `cmd_sty_${Date.now()}`,
|
|
655
725
|
type: "get_element_style",
|
|
656
726
|
params: { selector: args.selector }
|
|
@@ -668,11 +738,83 @@ async function handleToolCall(name, args, ctx) {
|
|
|
668
738
|
}
|
|
669
739
|
|
|
670
740
|
// packages/mcp/src/server.ts
|
|
741
|
+
import fs from "fs";
|
|
742
|
+
import path from "path";
|
|
743
|
+
import { fileURLToPath } from "url";
|
|
744
|
+
import { spawn } from "child_process";
|
|
671
745
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
672
746
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
673
747
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
748
|
+
async function isDaemonRunning(host, port) {
|
|
749
|
+
try {
|
|
750
|
+
const res = await fetch(`http://${host}:${port}/health`, {
|
|
751
|
+
signal: AbortSignal.timeout(600)
|
|
752
|
+
});
|
|
753
|
+
if (res.ok) {
|
|
754
|
+
const data = await res.json().catch(() => ({}));
|
|
755
|
+
return data?.name === "browsertrack";
|
|
756
|
+
}
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
function resolveCliPath() {
|
|
762
|
+
if (process.argv[1]) {
|
|
763
|
+
const candidate = process.argv[1];
|
|
764
|
+
if (candidate.endsWith("cli/index.js") || candidate.endsWith("browsertrack") || candidate.endsWith("bin/browsertrack.js") || candidate.endsWith("dist/cli/index.js")) {
|
|
765
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
770
|
+
const paths = [
|
|
771
|
+
path.resolve(currentDir, "../cli/index.js"),
|
|
772
|
+
path.resolve(currentDir, "../../cli/index.js"),
|
|
773
|
+
path.resolve(currentDir, "../../dist/cli/index.js")
|
|
774
|
+
];
|
|
775
|
+
for (const p of paths) {
|
|
776
|
+
if (fs.existsSync(p)) return p;
|
|
777
|
+
}
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
function acquireBootLock(lockFile) {
|
|
783
|
+
try {
|
|
784
|
+
fs.mkdirSync(path.dirname(lockFile), { recursive: true });
|
|
785
|
+
const fd = fs.openSync(lockFile, "wx");
|
|
786
|
+
fs.writeSync(fd, String(process.pid));
|
|
787
|
+
fs.closeSync(fd);
|
|
788
|
+
return true;
|
|
789
|
+
} catch {
|
|
790
|
+
try {
|
|
791
|
+
const stats = fs.statSync(lockFile);
|
|
792
|
+
if (Date.now() - stats.mtimeMs > 5e3) {
|
|
793
|
+
fs.unlinkSync(lockFile);
|
|
794
|
+
const fd = fs.openSync(lockFile, "wx");
|
|
795
|
+
fs.writeSync(fd, String(process.pid));
|
|
796
|
+
fs.closeSync(fd);
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function releaseBootLock(lockFile) {
|
|
805
|
+
try {
|
|
806
|
+
if (fs.existsSync(lockFile)) {
|
|
807
|
+
fs.unlinkSync(lockFile);
|
|
808
|
+
}
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
}
|
|
674
812
|
function createMcpServer(options = {}) {
|
|
675
|
-
const config = getDaemonConfig({
|
|
813
|
+
const config = getDaemonConfig({
|
|
814
|
+
dbPath: options.dbPath,
|
|
815
|
+
port: options.port,
|
|
816
|
+
host: options.host
|
|
817
|
+
});
|
|
676
818
|
const db = options.context?.db || new StorageDB(config.dbPath);
|
|
677
819
|
const screenshotStore = new ScreenshotStore(config.screenshotsDir);
|
|
678
820
|
const sessionManager = options.context?.sessionManager || new SessionManager(db);
|
|
@@ -687,6 +829,103 @@ function createMcpServer(options = {}) {
|
|
|
687
829
|
daemonUrl: `http://${config.host}:${config.port}`,
|
|
688
830
|
...options.context
|
|
689
831
|
};
|
|
832
|
+
let embeddedDaemon = null;
|
|
833
|
+
async function ensureDaemon() {
|
|
834
|
+
if (options.autoStartDaemon === false || options.context?.sessionManager) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (await isDaemonRunning(config.host, config.port)) {
|
|
838
|
+
console.error(`[BrowserTrack MCP] Connected to active singleton daemon at http://${config.host}:${config.port}`);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const lockFile = path.join(config.dataDir, "daemon_boot.lock");
|
|
842
|
+
const hasLock = acquireBootLock(lockFile);
|
|
843
|
+
if (!hasLock) {
|
|
844
|
+
for (let i = 0; i < 30; i++) {
|
|
845
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
846
|
+
if (await isDaemonRunning(config.host, config.port)) {
|
|
847
|
+
console.error(`[BrowserTrack MCP] Connected to shared singleton daemon at http://${config.host}:${config.port}`);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
if (await isDaemonRunning(config.host, config.port)) {
|
|
854
|
+
console.error(`[BrowserTrack MCP] Connected to shared singleton daemon at http://${config.host}:${config.port}`);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (options.detached !== false) {
|
|
858
|
+
const cliPath = resolveCliPath();
|
|
859
|
+
if (cliPath && fs.existsSync(cliPath)) {
|
|
860
|
+
try {
|
|
861
|
+
const nodePaths = [
|
|
862
|
+
"/usr/local/bin",
|
|
863
|
+
"/opt/homebrew/bin",
|
|
864
|
+
process.env.HOME ? `${process.env.HOME}/.nvm/versions/node/v20.19.5/bin` : "",
|
|
865
|
+
process.env.HOME ? `${process.env.HOME}/.nvm/versions/node/v23.1.0/bin` : ""
|
|
866
|
+
].filter(Boolean);
|
|
867
|
+
const extendedPath = `${process.env.PATH || ""}:${nodePaths.join(":")}`;
|
|
868
|
+
const child = spawn(
|
|
869
|
+
process.execPath,
|
|
870
|
+
[cliPath, "start", "--port", String(config.port), "--host", config.host],
|
|
871
|
+
{
|
|
872
|
+
detached: true,
|
|
873
|
+
stdio: "ignore",
|
|
874
|
+
env: { ...process.env, PATH: extendedPath, BROWSERTRACK_DAEMON_DETACHED: "1" }
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
child.unref();
|
|
878
|
+
for (let i = 0; i < 30; i++) {
|
|
879
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
880
|
+
if (await isDaemonRunning(config.host, config.port)) {
|
|
881
|
+
console.error(
|
|
882
|
+
`[BrowserTrack MCP] Started singleton background daemon on http://${config.host}:${config.port}`
|
|
883
|
+
);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
} catch (spawnErr) {
|
|
888
|
+
console.error(
|
|
889
|
+
`[BrowserTrack MCP] Detached daemon spawn failed (${spawnErr?.message}), falling back to in-process daemon.`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
embeddedDaemon = createDaemon({
|
|
895
|
+
host: config.host,
|
|
896
|
+
port: config.port,
|
|
897
|
+
dbPath: config.dbPath,
|
|
898
|
+
screenshotsDir: config.screenshotsDir,
|
|
899
|
+
verbose: false
|
|
900
|
+
});
|
|
901
|
+
await embeddedDaemon.start();
|
|
902
|
+
console.error(`[BrowserTrack MCP] Started singleton daemon on http://${config.host}:${config.port}`);
|
|
903
|
+
ctx.db = embeddedDaemon.db;
|
|
904
|
+
ctx.sessionManager = embeddedDaemon.sessionManager;
|
|
905
|
+
ctx.verificationEngine = embeddedDaemon.verificationEngine;
|
|
906
|
+
ctx.noteVerificationEngine = embeddedDaemon.noteVerificationEngine;
|
|
907
|
+
const cleanup = () => {
|
|
908
|
+
if (embeddedDaemon) {
|
|
909
|
+
try {
|
|
910
|
+
embeddedDaemon.stop();
|
|
911
|
+
} catch {
|
|
912
|
+
}
|
|
913
|
+
embeddedDaemon = null;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
process.on("exit", cleanup);
|
|
917
|
+
process.on("SIGINT", cleanup);
|
|
918
|
+
process.on("SIGTERM", cleanup);
|
|
919
|
+
} catch (err) {
|
|
920
|
+
console.error(
|
|
921
|
+
`[BrowserTrack MCP] Note: Daemon startup skipped (${err?.message}). Running MCP in standalone database mode.`
|
|
922
|
+
);
|
|
923
|
+
} finally {
|
|
924
|
+
if (hasLock) {
|
|
925
|
+
releaseBootLock(lockFile);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
690
929
|
const server = new Server(
|
|
691
930
|
{
|
|
692
931
|
name: "browsertrack-mcp",
|
|
@@ -704,7 +943,10 @@ function createMcpServer(options = {}) {
|
|
|
704
943
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
705
944
|
const { name, arguments: args } = request.params;
|
|
706
945
|
try {
|
|
707
|
-
const
|
|
946
|
+
const timeoutPromise = new Promise(
|
|
947
|
+
(_, reject) => setTimeout(() => reject(new Error(`Tool execution timed out after 20s: ${name}`)), 2e4)
|
|
948
|
+
);
|
|
949
|
+
const result = await Promise.race([handleToolCall(name, args || {}, ctx), timeoutPromise]);
|
|
708
950
|
return {
|
|
709
951
|
content: [
|
|
710
952
|
{
|
|
@@ -727,9 +969,21 @@ function createMcpServer(options = {}) {
|
|
|
727
969
|
});
|
|
728
970
|
return {
|
|
729
971
|
server,
|
|
972
|
+
ctx,
|
|
973
|
+
ensureDaemon,
|
|
974
|
+
async stopDaemon() {
|
|
975
|
+
if (embeddedDaemon) {
|
|
976
|
+
await embeddedDaemon.stop();
|
|
977
|
+
embeddedDaemon = null;
|
|
978
|
+
}
|
|
979
|
+
},
|
|
730
980
|
async startStdio() {
|
|
731
981
|
const transport = new StdioServerTransport();
|
|
732
982
|
await server.connect(transport);
|
|
983
|
+
const daemonInitPromise = ensureDaemon().catch((err) => {
|
|
984
|
+
console.error(`[BrowserTrack MCP] Daemon background init note: ${err?.message || err}`);
|
|
985
|
+
});
|
|
986
|
+
ctx.daemonInitPromise = daemonInitPromise;
|
|
733
987
|
}
|
|
734
988
|
};
|
|
735
989
|
}
|
|
@@ -737,6 +991,7 @@ function createMcpServer(options = {}) {
|
|
|
737
991
|
export {
|
|
738
992
|
TOOLS,
|
|
739
993
|
handleToolCall,
|
|
994
|
+
isDaemonRunning,
|
|
740
995
|
createMcpServer
|
|
741
996
|
};
|
|
742
|
-
//# sourceMappingURL=chunk-
|
|
997
|
+
//# sourceMappingURL=chunk-5NR5K3ER.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/mcp/src/tools.ts","../packages/mcp/src/handlers.ts","../packages/mcp/src/server.ts"],"sourcesContent":["import type { Tool } from '@modelcontextprotocol/sdk/types.js';\n\nexport const TOOLS: Tool[] = [\n {\n name: 'list_projects',\n description: 'List all registered projects tracked by BrowserTrack/BrowserDiag',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'list_sessions',\n description: 'List active and recent browser sessions connected to the local development daemon',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: { type: 'string', description: 'Filter sessions by project ID or name' },\n activeOnly: { type: 'boolean', description: 'Show only currently active WebSocket sessions (default: true)' },\n },\n },\n },\n {\n name: 'list_incidents',\n description: 'List recorded browser runtime errors, unhandled rejections, and console errors grouped into incidents',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: { type: 'string', description: 'Filter by project ID or name' },\n status: {\n type: 'string',\n enum: ['OPEN', 'FIX_ATTEMPTED', 'VERIFYING', 'VERIFIED', 'FAILED', 'INCONCLUSIVE'],\n description: 'Filter by incident status',\n },\n severity: {\n type: 'string',\n enum: ['error', 'warn', 'fatal'],\n description: 'Filter by severity',\n },\n limit: { type: 'number', description: 'Maximum number of incidents to return (default: 20)' },\n },\n },\n },\n {\n name: 'get_incident',\n description: 'Retrieve compact and high-signal debugging context for a specific error incident (stack trace, breadcrumbs, network failures, last interacted element, error screenshot)',\n inputSchema: {\n type: 'object',\n properties: {\n incidentId: { type: 'string', description: 'The unique ID of the incident (e.g. inc_42)' },\n },\n required: ['incidentId'],\n },\n },\n {\n name: 'get_console',\n description: 'Get recent console logs, warnings, and errors from a browser session',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string', description: 'Browser session ID (optional, defaults to active session)' },\n limit: { type: 'number', description: 'Number of console logs to retrieve (default: 30)' },\n },\n },\n },\n {\n name: 'get_network_failures',\n description: 'Get recent failed HTTP network requests (4xx, 5xx, network errors, timeouts, aborts)',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string', description: 'Browser session ID (optional)' },\n limit: { type: 'number', description: 'Maximum number of failed requests to return (default: 20)' },\n },\n },\n },\n {\n name: 'get_breadcrumbs',\n description: 'Get the sequence of recent user interactions, navigations, console logs, and network events prior to an error',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string', description: 'Browser session ID (optional)' },\n incidentId: { type: 'string', description: 'Incident ID to fetch breadcrumbs from (optional)' },\n limit: { type: 'number', description: 'Maximum number of breadcrumbs (default: 50)' },\n },\n },\n },\n {\n name: 'get_page_state',\n description: 'Query the live page state (URL, route, document title, readyState, active element) from the connected browser session',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string', description: 'Target session ID (optional, defaults to active)' },\n },\n },\n },\n {\n name: 'capture_element',\n description: 'Capture a screenshot of a specific DOM element or the entire visible page in the active browser tab',\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string', description: 'CSS selector of the element to capture (e.g. [data-testid=\"user-card\"])' },\n sessionId: { type: 'string', description: 'Browser session ID (optional)' },\n },\n },\n },\n {\n name: 'verify_incident',\n description: 'Trigger closed-loop verification of a bug fix: reloads the browser, checks if the incident reoccurs, evaluates optional verification probes, and records before/after screenshots',\n inputSchema: {\n type: 'object',\n properties: {\n incidentId: { type: 'string', description: 'The ID of the incident to verify' },\n route: { type: 'string', description: 'Optional route to navigate to for verification (defaults to incident route)' },\n targetSelector: { type: 'string', description: 'Optional element selector to inspect and capture after-fix screenshot' },\n expect: {\n type: 'array',\n description: 'Optional verification probes to evaluate after reload',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['element_exists', 'element_visible', 'text_contains', 'route_is', 'no_incident'],\n },\n selector: { type: 'string' },\n text: { type: 'string' },\n route: { type: 'string' },\n },\n required: ['type'],\n },\n },\n observationWindowMs: { type: 'number', description: 'Observation window in milliseconds (default: 2000)' },\n },\n required: ['incidentId'],\n },\n },\n {\n name: 'get_verification',\n description: 'Retrieve the latest verification result and before/after screenshot artifacts for an incident',\n inputSchema: {\n type: 'object',\n properties: {\n incidentId: { type: 'string', description: 'The ID of the incident' },\n },\n required: ['incidentId'],\n },\n },\n {\n name: 'list_notes',\n description: 'List visual development notes / screen annotations left on elements, regions, or pages during development',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: { type: 'string', description: 'Filter notes by project ID or name' },\n scenarioId: { type: 'string', description: 'Filter notes by scenario / flow ID' },\n status: {\n type: 'string',\n enum: ['OPEN', 'IN_PROGRESS', 'VERIFYING', 'RESOLVED', 'FAILED', 'INCONCLUSIVE'],\n description: 'Filter by note status (default: OPEN)',\n },\n limit: { type: 'number', description: 'Maximum number of notes to return (default: 20)' },\n },\n },\n },\n {\n name: 'list_scenarios',\n description: 'List recorded reproduction scenarios and multi-step user interaction flows',\n inputSchema: {\n type: 'object',\n properties: {\n projectId: { type: 'string', description: 'Filter scenarios by project ID or name' },\n status: {\n type: 'string',\n enum: ['OPEN', 'RESOLVED'],\n description: 'Filter by scenario status',\n },\n limit: { type: 'number', description: 'Maximum number of scenarios to return (default: 20)' },\n },\n },\n },\n {\n name: 'get_scenario',\n description: 'Retrieve full chronological step-by-step reproduction flow with selectors, route, screenshots, and action details',\n inputSchema: {\n type: 'object',\n properties: {\n scenarioId: { type: 'string', description: 'The unique ID of the scenario / flow (e.g. scen_123)' },\n },\n required: ['scenarioId'],\n },\n },\n {\n name: 'get_note',\n description: 'Retrieve full debugging context for a visual note (message, route, viewport dimensions, target element selector, DOM context, screenshot file path, project path)',\n inputSchema: {\n type: 'object',\n properties: {\n noteId: { type: 'string', description: 'The unique ID of the visual note (e.g. note_42)' },\n },\n required: ['noteId'],\n },\n },\n {\n name: 'resolve_note',\n description: 'Mark a visual note as RESOLVED once the layout or styling issue has been fixed',\n inputSchema: {\n type: 'object',\n properties: {\n noteId: { type: 'string', description: 'The ID of the visual note to resolve' },\n },\n required: ['noteId'],\n },\n },\n {\n name: 'reopen_note',\n description: 'Reopen a previously resolved visual note',\n inputSchema: {\n type: 'object',\n properties: {\n noteId: { type: 'string', description: 'The ID of the visual note to reopen' },\n },\n required: ['noteId'],\n },\n },\n {\n name: 'verify_note',\n description: 'Run closed-loop layout & visual verification for a note: checks route, element existence/visibility, evaluates viewport overflow probes, captures after-screenshot, and computes geometry diff',\n inputSchema: {\n type: 'object',\n properties: {\n noteId: { type: 'string', description: 'The ID of the visual note to verify' },\n observationWindowMs: { type: 'number', description: 'Observation window in milliseconds (default: 1000)' },\n },\n required: ['noteId'],\n },\n },\n {\n name: 'get_note_verification',\n description: 'Retrieve the latest verification result, before/after screenshot references, and layout geometry diff for a visual note',\n inputSchema: {\n type: 'object',\n properties: {\n noteId: { type: 'string', description: 'The ID of the visual note' },\n },\n required: ['noteId'],\n },\n },\n {\n name: 'capture_note_context',\n description: 'Inspect live DOM context, bounding box, overflow, and styles for a target element in the active browser tab',\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string', description: 'CSS selector of the target element' },\n sessionId: { type: 'string', description: 'Browser session ID (optional)' },\n },\n required: ['selector'],\n },\n },\n];\n","import type { StorageDB } from '../../daemon/src/storage/db.js';\nimport type { VerificationEngine } from '../../daemon/src/verification/engine.js';\nimport type { NoteVerificationEngine } from '../../daemon/src/notes/verification.js';\nimport type { SessionManager } from '../../daemon/src/session/manager.js';\n\nexport interface McpContext {\n db: StorageDB;\n sessionManager?: SessionManager;\n verificationEngine?: VerificationEngine;\n noteVerificationEngine?: NoteVerificationEngine;\n daemonUrl?: string;\n daemonInitPromise?: Promise<void>;\n}\n\nasync function sendSessionCommand(ctx: McpContext, sessionId: string | undefined, command: any, timeoutMs = 5000): Promise<any> {\n if (ctx.daemonInitPromise) {\n try {\n await Promise.race([ctx.daemonInitPromise, new Promise((r) => setTimeout(r, 2000))]);\n } catch {}\n }\n\n // 1. In-process session manager (if active sockets exist)\n if (ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {\n const targetSession = sessionId ? ctx.db.getSession(sessionId) : ctx.sessionManager.getAnyActiveSession();\n if (targetSession) {\n const res = await ctx.sessionManager.sendCommand(targetSession.id, command, timeoutMs);\n if (!res.ok) {\n throw new Error(res.error || res.reason || `Command ${command.type} failed`);\n }\n return res;\n }\n }\n\n // 2. Multi-process proxy to running daemon HTTP server\n if (ctx.daemonUrl) {\n try {\n const resp = await fetch(`${ctx.daemonUrl}/api/command`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ sessionId, command, timeoutMs }),\n signal: AbortSignal.timeout(timeoutMs + 2000),\n });\n if (resp.ok) {\n const res = await resp.json();\n if (!res.ok) {\n throw new Error(res.error || res.reason || `Command ${command.type} failed`);\n }\n return res;\n }\n const errJson = await resp.json().catch(() => ({}));\n if (errJson.error) {\n throw new Error(errJson.error);\n }\n } catch (err: any) {\n if (err.message && !err.message.includes('fetch failed') && !err.message.includes('ECONNREFUSED')) {\n throw err;\n }\n }\n }\n\n throw new Error(\n 'No active browser session connected. Please ensure the BrowserTrack daemon is running (\"browsertrack start\") and your application tab is open in the browser.'\n );\n}\n\nasync function runVerifyIncident(ctx: McpContext, incidentId: string, options: any): Promise<any> {\n if (ctx.verificationEngine && ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {\n return await ctx.verificationEngine.verifyIncident(incidentId, options);\n }\n\n if (ctx.daemonUrl) {\n try {\n const timeoutMs = (options?.observationWindowMs || 3000) + 7000;\n const resp = await fetch(`${ctx.daemonUrl}/api/verify/incident`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ incidentId, options }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (resp.ok) {\n const data = await resp.json();\n if (data.ok) return data.result;\n throw new Error(data.error);\n }\n } catch (err: any) {\n if (err.message && !err.message.includes('fetch failed') && !err.message.includes('ECONNREFUSED')) {\n throw err;\n }\n }\n }\n\n throw new Error(\n 'Verification failed: No active browser session connected. Please ensure the BrowserTrack daemon is running (\"browsertrack start\") and your application tab is open in the browser.'\n );\n}\n\nasync function runVerifyNote(ctx: McpContext, noteId: string, options: any): Promise<any> {\n if (ctx.noteVerificationEngine && ctx.sessionManager && ctx.sessionManager.getActiveCount() > 0) {\n return await ctx.noteVerificationEngine.verifyNote(noteId, options);\n }\n\n if (ctx.daemonUrl) {\n try {\n const timeoutMs = (options?.observationWindowMs || 3000) + 7000;\n const resp = await fetch(`${ctx.daemonUrl}/api/verify/note`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ noteId, options }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (resp.ok) {\n const data = await resp.json();\n if (data.ok) return data.result;\n throw new Error(data.error);\n }\n } catch (err: any) {\n if (err.message && !err.message.includes('fetch failed') && !err.message.includes('ECONNREFUSED')) {\n throw err;\n }\n }\n }\n\n throw new Error(\n 'Verification failed: No active browser session connected. Please ensure the BrowserTrack daemon is running (\"browsertrack start\") and your application tab is open in the browser.'\n );\n}\n\nexport async function handleToolCall(name: string, args: any, ctx: McpContext): Promise<any> {\n const { db, sessionManager, verificationEngine, noteVerificationEngine } = ctx;\n\n switch (name) {\n case 'list_projects': {\n const projects = db.listProjects();\n return {\n projects: projects.map((p) => ({\n id: p.id,\n name: p.name,\n origin: p.origin,\n path: p.path,\n updatedAt: p.updatedAt,\n })),\n };\n }\n\n case 'list_sessions': {\n const activeOnly = args.activeOnly !== false;\n const sessions = db.listSessions(args.projectId, activeOnly);\n return {\n count: sessions.length,\n sessions: sessions.map((s) => ({\n id: s.id,\n projectId: s.projectId,\n origin: s.origin,\n url: s.url,\n title: s.title,\n active: s.active,\n lastSeenAt: s.lastSeenAt,\n })),\n };\n }\n\n case 'list_incidents': {\n const incidents = db.listIncidents({\n projectId: args.projectId,\n status: args.status,\n severity: args.severity,\n limit: args.limit || 20,\n });\n return {\n total: incidents.length,\n incidents: incidents.map((inc) => ({\n id: inc.id,\n type: inc.type,\n severity: inc.severity,\n message: inc.message,\n source: `${inc.source.file}:${inc.source.line}`,\n route: inc.route,\n status: inc.status,\n occurrences: inc.occurrences,\n firstSeen: inc.firstSeen,\n lastSeen: inc.lastSeen,\n })),\n };\n }\n\n case 'get_incident': {\n const incident = db.getIncident(args.incidentId);\n if (!incident) {\n throw new Error(`Incident '${args.incidentId}' not found.`);\n }\n\n // Compact breadcrumbs summary (last 10 most relevant)\n const breadcrumbsTimeline = incident.breadcrumbs.slice(-15).map((b) => {\n const time = new Date(b.timestamp).toISOString().split('T')[1]?.slice(0, 8);\n return `[${time}] ${b.message}`;\n });\n\n return {\n id: incident.id,\n status: incident.status,\n type: incident.type,\n severity: incident.severity,\n message: incident.message,\n source: {\n file: incident.source.file,\n line: incident.source.line,\n column: incident.source.column,\n },\n route: incident.route,\n occurrences: incident.occurrences,\n firstSeen: incident.firstSeen,\n lastSeen: incident.lastSeen,\n stack: incident.stack,\n lastInteractedElement: incident.lastElement\n ? {\n selector: incident.lastElement.selector,\n tag: incident.lastElement.tag,\n visible: incident.lastElement.visible,\n innerText: incident.lastElement.innerText,\n outerHTML: incident.lastElement.outerHTML,\n componentSource: incident.lastElement.componentSource,\n }\n : undefined,\n recentBreadcrumbs: breadcrumbsTimeline,\n networkFailures: incident.networkFailures,\n screenshot: incident.screenshots?.error,\n };\n }\n\n case 'get_console': {\n const limit = args.limit || 30;\n const events = db.getEvents({\n sessionId: args.sessionId,\n eventType: 'console',\n limit,\n });\n\n return {\n logs: events.map((e) => ({\n level: e.payload.level,\n message: e.payload.message,\n timestamp: new Date(e.timestamp).toISOString(),\n route: e.route,\n })),\n };\n }\n\n case 'get_network_failures': {\n const limit = args.limit || 20;\n const events = db.getEvents({\n sessionId: args.sessionId,\n limit: 100,\n });\n\n const failures = events\n .filter((e) => (e.eventType === 'fetch' || e.eventType === 'xhr') && (e.payload.status >= 400 || e.payload.error))\n .slice(0, limit)\n .map((e) => ({\n url: e.payload.url,\n method: e.payload.method,\n status: e.payload.status,\n error: e.payload.error,\n durationMs: e.payload.durationMs,\n timestamp: new Date(e.timestamp).toISOString(),\n }));\n\n return { failures };\n }\n\n case 'get_breadcrumbs': {\n if (args.incidentId) {\n const incident = db.getIncident(args.incidentId);\n if (!incident) {\n throw new Error(`Incident '${args.incidentId}' not found.`);\n }\n return {\n incidentId: args.incidentId,\n breadcrumbs: incident.breadcrumbs.slice(-(args.limit || 50)),\n };\n }\n\n const events = db.getEvents({\n sessionId: args.sessionId,\n limit: args.limit || 50,\n });\n\n return {\n breadcrumbs: events.map((e) => ({\n type: e.eventType,\n message: e.payload.message || `${e.eventType} on ${e.route}`,\n timestamp: new Date(e.timestamp).toISOString(),\n route: e.route,\n })),\n };\n }\n\n case 'get_page_state': {\n const cmdRes = await sendSessionCommand(ctx, args.sessionId, {\n id: `cmd_mcp_${Date.now()}`,\n type: 'get_page_state',\n });\n return cmdRes.result;\n }\n\n case 'capture_element': {\n const cmdRes = await sendSessionCommand(ctx, args.sessionId, {\n id: `cmd_mcp_${Date.now()}`,\n type: 'capture_element',\n params: { selector: args.selector },\n });\n\n return {\n ok: true,\n format: cmdRes.result?.format || 'webp',\n width: cmdRes.result?.width,\n height: cmdRes.result?.height,\n dataUrlPreview: cmdRes.result?.dataUrl ? `${cmdRes.result.dataUrl.slice(0, 100)}...` : undefined,\n };\n }\n\n case 'verify_incident': {\n const res = await runVerifyIncident(ctx, args.incidentId, {\n route: args.route,\n targetSelector: args.targetSelector,\n expect: args.expect,\n observationWindowMs: args.observationWindowMs,\n });\n\n return res;\n }\n\n case 'get_verification': {\n const v = db.getLatestVerification(args.incidentId);\n if (!v) {\n throw new Error(`No verification records found for incident '${args.incidentId}'.`);\n }\n return v;\n }\n\n case 'list_notes': {\n const notes = db.listNotes({\n projectId: args.projectId,\n scenarioId: args.scenarioId,\n status: args.status,\n limit: args.limit || 20,\n });\n\n return {\n total: notes.length,\n notes: notes.map((n) => ({\n id: n.id,\n type: n.type,\n status: n.status,\n route: n.route,\n scenarioId: n.scenarioId,\n stepNumber: n.stepNumber,\n scenarioTitle: n.scenarioTitle,\n viewport: `${n.viewport.width} × ${n.viewport.height}`,\n target: n.target?.selector || n.type,\n message: n.message,\n screenshotAvailable: !!n.screenshots?.original,\n createdAt: n.createdAt,\n })),\n };\n }\n\n case 'list_scenarios': {\n const scenarios = db.listScenarios({\n projectId: args.projectId,\n status: args.status,\n limit: args.limit || 20,\n });\n\n return {\n total: scenarios.length,\n scenarios: scenarios.map((s) => ({\n id: s.id,\n title: s.title,\n stepsCount: s.stepsCount,\n status: s.status,\n route: s.route,\n firstStepAt: s.firstStepAt,\n lastStepAt: s.lastStepAt,\n })),\n };\n }\n\n case 'get_scenario': {\n const scenario = db.getScenario(args.scenarioId);\n if (!scenario) {\n throw new Error(`Scenario '${args.scenarioId}' not found.`);\n }\n\n return {\n id: scenario.id,\n title: scenario.title,\n stepsCount: scenario.stepsCount,\n status: scenario.status,\n steps: scenario.steps.map((step) => ({\n id: step.id,\n stepNumber: step.stepNumber,\n type: step.type,\n message: step.message,\n route: step.route,\n url: step.url,\n targetSelector: step.target?.selector,\n boundingRect: step.target?.boundingRect || step.region,\n elementContext: step.elementContext,\n screenshot: step.screenshots?.original,\n status: step.status,\n createdAt: step.createdAt,\n })),\n createdAt: scenario.createdAt,\n updatedAt: scenario.updatedAt,\n };\n }\n\n case 'get_note': {\n const note = db.getNote(args.noteId);\n if (!note) {\n throw new Error(`Visual note '${args.noteId}' not found.`);\n }\n\n const project = db.getProject(note.projectId);\n\n return {\n id: note.id,\n type: note.type,\n status: note.status,\n message: note.message,\n scenarioId: note.scenarioId,\n stepNumber: note.stepNumber,\n scenarioTitle: note.scenarioTitle,\n route: note.route,\n url: note.url,\n viewport: note.viewport,\n scroll: note.scroll,\n target: note.target,\n elementContext: note.elementContext,\n region: note.region,\n screenshot: {\n available: !!note.screenshots?.original,\n original: note.screenshots?.original,\n after: note.screenshots?.after,\n },\n project: project\n ? {\n id: project.id,\n name: project.name,\n origin: project.origin,\n path: project.path,\n }\n : undefined,\n relatedIncidentId: note.incidentId,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n resolvedAt: note.resolvedAt,\n };\n }\n\n case 'resolve_note': {\n const note = db.getNote(args.noteId);\n if (!note) {\n throw new Error(`Visual note '${args.noteId}' not found.`);\n }\n db.updateNoteStatus(args.noteId, 'RESOLVED');\n return {\n ok: true,\n noteId: args.noteId,\n status: 'RESOLVED',\n resolvedAt: new Date().toISOString(),\n };\n }\n\n case 'reopen_note': {\n const note = db.getNote(args.noteId);\n if (!note) {\n throw new Error(`Visual note '${args.noteId}' not found.`);\n }\n db.updateNoteStatus(args.noteId, 'OPEN');\n return {\n ok: true,\n noteId: args.noteId,\n status: 'OPEN',\n };\n }\n\n case 'verify_note': {\n const res = await runVerifyNote(ctx, args.noteId, {\n observationWindowMs: args.observationWindowMs,\n });\n return res;\n }\n\n case 'get_note_verification': {\n const v = db.getLatestNoteVerification(args.noteId);\n if (!v) {\n throw new Error(`No verification records found for visual note '${args.noteId}'.`);\n }\n return v;\n }\n\n case 'capture_note_context': {\n const queryCmd = await sendSessionCommand(ctx, args.sessionId, {\n id: `cmd_ctx_${Date.now()}`,\n type: 'query_element',\n params: { selector: args.selector },\n });\n\n const overflowCmd = await sendSessionCommand(ctx, args.sessionId, {\n id: `cmd_ovf_${Date.now()}`,\n type: 'check_overflow',\n params: { selector: args.selector },\n });\n\n const styleCmd = await sendSessionCommand(ctx, args.sessionId, {\n id: `cmd_sty_${Date.now()}`,\n type: 'get_element_style',\n params: { selector: args.selector },\n });\n\n return {\n selector: args.selector,\n element: queryCmd.result,\n overflow: overflowCmd.result,\n styles: styleCmd.result?.styles,\n };\n }\n\n default:\n throw new Error(`Unknown MCP tool: ${name}`);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { spawn } from 'node:child_process';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { getDaemonConfig } from '../../daemon/src/config.js';\nimport { StorageDB } from '../../daemon/src/storage/db.js';\nimport { ScreenshotStore } from '../../daemon/src/storage/screenshot-store.js';\nimport { SessionManager } from '../../daemon/src/session/manager.js';\nimport { NotesEngine } from '../../daemon/src/notes/engine.js';\nimport { NoteVerificationEngine } from '../../daemon/src/notes/verification.js';\nimport { VerificationEngine, createDaemon } from '../../daemon/src/index.js';\nimport type { McpContext } from './handlers.js';\nimport { handleToolCall } from './handlers.js';\nimport { TOOLS } from './tools.js';\n\nexport async function isDaemonRunning(host: string, port: number): Promise<boolean> {\n try {\n const res = await fetch(`http://${host}:${port}/health`, {\n signal: AbortSignal.timeout(600),\n });\n if (res.ok) {\n const data = (await res.json().catch(() => ({}))) as any;\n return data?.name === 'browsertrack';\n }\n } catch {}\n return false;\n}\n\nfunction resolveCliPath(): string | null {\n if (process.argv[1]) {\n const candidate = process.argv[1];\n if (\n candidate.endsWith('cli/index.js') ||\n candidate.endsWith('browsertrack') ||\n candidate.endsWith('bin/browsertrack.js') ||\n candidate.endsWith('dist/cli/index.js')\n ) {\n if (fs.existsSync(candidate)) return candidate;\n }\n }\n\n try {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const paths = [\n path.resolve(currentDir, '../cli/index.js'),\n path.resolve(currentDir, '../../cli/index.js'),\n path.resolve(currentDir, '../../dist/cli/index.js'),\n ];\n for (const p of paths) {\n if (fs.existsSync(p)) return p;\n }\n } catch {}\n\n return null;\n}\n\nfunction acquireBootLock(lockFile: string): boolean {\n try {\n fs.mkdirSync(path.dirname(lockFile), { recursive: true });\n const fd = fs.openSync(lockFile, 'wx');\n fs.writeSync(fd, String(process.pid));\n fs.closeSync(fd);\n return true;\n } catch {\n try {\n const stats = fs.statSync(lockFile);\n // If older than 5 seconds, assume stale lock and break it\n if (Date.now() - stats.mtimeMs > 5000) {\n fs.unlinkSync(lockFile);\n const fd = fs.openSync(lockFile, 'wx');\n fs.writeSync(fd, String(process.pid));\n fs.closeSync(fd);\n return true;\n }\n } catch {}\n return false;\n }\n}\n\nfunction releaseBootLock(lockFile: string): void {\n try {\n if (fs.existsSync(lockFile)) {\n fs.unlinkSync(lockFile);\n }\n } catch {}\n}\n\nexport interface McpServerOptions {\n dbPath?: string;\n context?: Partial<McpContext>;\n autoStartDaemon?: boolean;\n port?: number;\n host?: string;\n detached?: boolean;\n}\n\nexport function createMcpServer(options: McpServerOptions = {}) {\n const config = getDaemonConfig({\n dbPath: options.dbPath,\n port: options.port,\n host: options.host,\n });\n const db = options.context?.db || new StorageDB(config.dbPath);\n const screenshotStore = new ScreenshotStore(config.screenshotsDir);\n const sessionManager = options.context?.sessionManager || new SessionManager(db);\n const verificationEngine = options.context?.verificationEngine || new VerificationEngine(db, sessionManager, screenshotStore);\n const notesEngine = new NotesEngine(db, screenshotStore);\n const noteVerificationEngine = options.context?.noteVerificationEngine || new NoteVerificationEngine(db, sessionManager, notesEngine);\n\n const ctx: McpContext = {\n db,\n sessionManager,\n verificationEngine,\n noteVerificationEngine,\n daemonUrl: `http://${config.host}:${config.port}`,\n ...options.context,\n };\n\n let embeddedDaemon: any = null;\n\n async function ensureDaemon(): Promise<void> {\n if (options.autoStartDaemon === false || options.context?.sessionManager) {\n return;\n }\n\n // 1. Fast path: check if a singleton daemon is already running\n if (await isDaemonRunning(config.host, config.port)) {\n console.error(`[BrowserTrack MCP] Connected to active singleton daemon at http://${config.host}:${config.port}`);\n return;\n }\n\n // 2. Concurrency lock to guarantee only ONE daemon is ever started across multiple IDE sessions\n const lockFile = path.join(config.dataDir, 'daemon_boot.lock');\n const hasLock = acquireBootLock(lockFile);\n\n if (!hasLock) {\n // Another session is currently booting the daemon, wait for it\n for (let i = 0; i < 30; i++) {\n await new Promise((r) => setTimeout(r, 100));\n if (await isDaemonRunning(config.host, config.port)) {\n console.error(`[BrowserTrack MCP] Connected to shared singleton daemon at http://${config.host}:${config.port}`);\n return;\n }\n }\n }\n\n try {\n // 3. Double-check before starting\n if (await isDaemonRunning(config.host, config.port)) {\n console.error(`[BrowserTrack MCP] Connected to shared singleton daemon at http://${config.host}:${config.port}`);\n return;\n }\n\n // 4. Detached background process (singleton daemon that survives across IDE windows/sessions)\n if (options.detached !== false) {\n const cliPath = resolveCliPath();\n if (cliPath && fs.existsSync(cliPath)) {\n try {\n const nodePaths = [\n '/usr/local/bin',\n '/opt/homebrew/bin',\n process.env.HOME ? `${process.env.HOME}/.nvm/versions/node/v20.19.5/bin` : '',\n process.env.HOME ? `${process.env.HOME}/.nvm/versions/node/v23.1.0/bin` : '',\n ].filter(Boolean);\n const extendedPath = `${process.env.PATH || ''}:${nodePaths.join(':')}`;\n\n const child = spawn(\n process.execPath,\n [cliPath, 'start', '--port', String(config.port), '--host', config.host],\n {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env, PATH: extendedPath, BROWSERTRACK_DAEMON_DETACHED: '1' },\n }\n );\n child.unref();\n\n for (let i = 0; i < 30; i++) {\n await new Promise((r) => setTimeout(r, 100));\n if (await isDaemonRunning(config.host, config.port)) {\n console.error(\n `[BrowserTrack MCP] Started singleton background daemon on http://${config.host}:${config.port}`\n );\n return;\n }\n }\n } catch (spawnErr: any) {\n console.error(\n `[BrowserTrack MCP] Detached daemon spawn failed (${spawnErr?.message}), falling back to in-process daemon.`\n );\n }\n }\n }\n\n // 5. In-process fallback (used when detached is false or in test runners)\n embeddedDaemon = createDaemon({\n host: config.host,\n port: config.port,\n dbPath: config.dbPath,\n screenshotsDir: config.screenshotsDir,\n verbose: false,\n });\n await embeddedDaemon.start();\n console.error(`[BrowserTrack MCP] Started singleton daemon on http://${config.host}:${config.port}`);\n\n ctx.db = embeddedDaemon.db;\n ctx.sessionManager = embeddedDaemon.sessionManager;\n ctx.verificationEngine = embeddedDaemon.verificationEngine;\n ctx.noteVerificationEngine = embeddedDaemon.noteVerificationEngine;\n\n const cleanup = () => {\n if (embeddedDaemon) {\n try {\n embeddedDaemon.stop();\n } catch {}\n embeddedDaemon = null;\n }\n };\n\n process.on('exit', cleanup);\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err: any) {\n console.error(\n `[BrowserTrack MCP] Note: Daemon startup skipped (${err?.message}). Running MCP in standalone database mode.`\n );\n } finally {\n if (hasLock) {\n releaseBootLock(lockFile);\n }\n }\n }\n\n const server = new Server(\n {\n name: 'browsertrack-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: TOOLS };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n try {\n // 20-second safety timeout so tool execution never hangs indefinitely\n const timeoutPromise = new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`Tool execution timed out after 20s: ${name}`)), 20000)\n );\n const result = await Promise.race([handleToolCall(name, args || {}, ctx), timeoutPromise]);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (err: any) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `Error executing tool ${name}: ${err?.message || String(err)}`,\n },\n ],\n };\n }\n });\n\n return {\n server,\n ctx,\n ensureDaemon,\n async stopDaemon() {\n if (embeddedDaemon) {\n await embeddedDaemon.stop();\n embeddedDaemon = null;\n }\n },\n async startStdio() {\n // Connect stdio transport immediately so client handshakes are answered instantly\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Start daemon verification / auto-boot asynchronously in background so client handshakes never block\n const daemonInitPromise = ensureDaemon().catch((err) => {\n console.error(`[BrowserTrack MCP] Daemon background init note: ${err?.message || err}`);\n });\n ctx.daemonInitPromise = daemonInitPromise;\n },\n };\n}\n"],"mappings":"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","names":[]}
|