browser-debug-mcp-bridge 1.1.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/LICENSE +15 -0
- package/README.md +101 -0
- package/apps/mcp-server/dist/db/connection.js +42 -0
- package/apps/mcp-server/dist/db/connection.js.map +1 -0
- package/apps/mcp-server/dist/db/error-fingerprints.js +21 -0
- package/apps/mcp-server/dist/db/error-fingerprints.js.map +1 -0
- package/apps/mcp-server/dist/db/events-repository.js +109 -0
- package/apps/mcp-server/dist/db/events-repository.js.map +1 -0
- package/apps/mcp-server/dist/db/index.js +4 -0
- package/apps/mcp-server/dist/db/index.js.map +1 -0
- package/apps/mcp-server/dist/db/migrations.js +101 -0
- package/apps/mcp-server/dist/db/migrations.js.map +1 -0
- package/apps/mcp-server/dist/db/schema.js +157 -0
- package/apps/mcp-server/dist/db/schema.js.map +1 -0
- package/apps/mcp-server/dist/main.js +384 -0
- package/apps/mcp-server/dist/main.js.map +1 -0
- package/apps/mcp-server/dist/mcp/server.js +1619 -0
- package/apps/mcp-server/dist/mcp/server.js.map +1 -0
- package/apps/mcp-server/dist/mcp-bridge.js +55 -0
- package/apps/mcp-server/dist/mcp-bridge.js.map +1 -0
- package/apps/mcp-server/dist/retention.js +841 -0
- package/apps/mcp-server/dist/retention.js.map +1 -0
- package/apps/mcp-server/dist/websocket/index.js +3 -0
- package/apps/mcp-server/dist/websocket/index.js.map +1 -0
- package/apps/mcp-server/dist/websocket/messages.js +150 -0
- package/apps/mcp-server/dist/websocket/messages.js.map +1 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js +302 -0
- package/apps/mcp-server/dist/websocket/websocket-server.js.map +1 -0
- package/apps/mcp-server/package.json +28 -0
- package/package.json +88 -0
- package/scripts/mcp-start.cjs +229 -0
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "browser-debug-mcp-bridge",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Chrome Extension + Node.js MCP server for browser debugging",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"scripts/mcp-start.cjs",
|
|
11
|
+
"apps/mcp-server/dist/**/*",
|
|
12
|
+
"apps/mcp-server/package.json",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"browser-debug-mcp-bridge": "scripts/mcp-start.cjs"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "nx run-many -t lint --projects mcp-server,chrome-extension,shared,redaction,selectors,mcp-contracts",
|
|
21
|
+
"test": "nx run-many -t test",
|
|
22
|
+
"lint": "nx run-many -t lint",
|
|
23
|
+
"build": "nx run-many -t build",
|
|
24
|
+
"verify": "pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm docs:ci && pnpm mcp:check-stdio-guard",
|
|
25
|
+
"serve": "nx serve mcp-server",
|
|
26
|
+
"mcp:start": "node scripts/mcp-start.cjs",
|
|
27
|
+
"build:mcp-runtime": "pnpm -C apps/mcp-server build",
|
|
28
|
+
"prepack": "pnpm run build:mcp-runtime",
|
|
29
|
+
"docs:dev": "nx serve docs",
|
|
30
|
+
"docs:build": "nx build docs",
|
|
31
|
+
"docs:lint": "nx lint docs",
|
|
32
|
+
"docs:ci": "nx run docs:ci",
|
|
33
|
+
"gha:list": "act -l",
|
|
34
|
+
"gha:dry-run": "act -n",
|
|
35
|
+
"gha:ci": "act push -W .github/workflows/ci.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
36
|
+
"gha:docs": "act push -W .github/workflows/docs-pages.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
37
|
+
"gha:nightly": "act schedule -W .github/workflows/nightly-health.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
38
|
+
"gha:release": "act workflow_dispatch -W .github/workflows/release.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
39
|
+
"gha:release-please": "act workflow_dispatch -W .github/workflows/release-please.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
40
|
+
"gha:dependency-update": "act workflow_dispatch -W .github/workflows/dependency-update.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest",
|
|
41
|
+
"release:tag": "node tools/release/create-tag.mjs",
|
|
42
|
+
"release:tag:dry": "node tools/release/create-tag.mjs --dry-run --yes",
|
|
43
|
+
"release:tag:yes": "node tools/release/create-tag.mjs --yes",
|
|
44
|
+
"mcp:print-config": "node tools/mcp/print-client-config.mjs",
|
|
45
|
+
"mcp:check-stdio-guard": "node tools/mcp/check-stdio-guard.mjs",
|
|
46
|
+
"hooks:install": "git config core.hooksPath .githooks && git config commit.template .gitmessage.txt"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"mcp",
|
|
50
|
+
"chrome-extension",
|
|
51
|
+
"debugging"
|
|
52
|
+
],
|
|
53
|
+
"author": "",
|
|
54
|
+
"license": "ISC",
|
|
55
|
+
"packageManager": "pnpm@10.28.2",
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@nx/vite": "^22.4.5",
|
|
61
|
+
"@types/chrome": "^0.1.36",
|
|
62
|
+
"@types/node": "^20.19.33",
|
|
63
|
+
"@types/react": "^18.3.24",
|
|
64
|
+
"@types/react-dom": "^18.3.7",
|
|
65
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
66
|
+
"@docusaurus/core": "^3.9.2",
|
|
67
|
+
"@docusaurus/preset-classic": "^3.9.2",
|
|
68
|
+
"@easyops-cn/docusaurus-search-local": "^0.53.0",
|
|
69
|
+
"@mdx-js/react": "^3.1.1",
|
|
70
|
+
"jsdom": "^28.0.0",
|
|
71
|
+
"markdownlint-cli2": "^0.18.1",
|
|
72
|
+
"nx": "^20.0.0",
|
|
73
|
+
"react": "^18.3.1",
|
|
74
|
+
"react-dom": "^18.3.1",
|
|
75
|
+
"typescript": "^5.3.0",
|
|
76
|
+
"vite": "^7.3.1",
|
|
77
|
+
"vitest": "^1.6.1"
|
|
78
|
+
},
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
81
|
+
"better-sqlite3": "^12.6.2",
|
|
82
|
+
"fastify": "^4.28.0",
|
|
83
|
+
"jszip": "^3.10.1",
|
|
84
|
+
"tsx": "^4.20.5",
|
|
85
|
+
"ws": "^8.19.0",
|
|
86
|
+
"zod": "^4.3.6"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
const { existsSync } = require('node:fs');
|
|
4
|
+
const { dirname, join, resolve } = require('node:path');
|
|
5
|
+
const { createRequire } = require('node:module');
|
|
6
|
+
const net = require('node:net');
|
|
7
|
+
|
|
8
|
+
const repoRoot = resolve(__dirname, '..');
|
|
9
|
+
const packageJson = join(repoRoot, 'package.json');
|
|
10
|
+
const mcpBridgeDistEntry = join(repoRoot, 'apps', 'mcp-server', 'dist', 'mcp-bridge.js');
|
|
11
|
+
const mainServerDistEntry = join(repoRoot, 'apps', 'mcp-server', 'dist', 'main.js');
|
|
12
|
+
const mcpBridgeEntry = join(repoRoot, 'apps', 'mcp-server', 'src', 'mcp-bridge.ts');
|
|
13
|
+
const mainServerEntry = join(repoRoot, 'apps', 'mcp-server', 'src', 'main.ts');
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const useTsx = args.includes('--mode=tsx');
|
|
16
|
+
const useDist = args.includes('--mode=dist');
|
|
17
|
+
const dryRun = args.includes('--dry-run');
|
|
18
|
+
const standalone = args.includes('--standalone');
|
|
19
|
+
const localRequire = createRequire(join(repoRoot, 'package.json'));
|
|
20
|
+
const supportsColor = Boolean(process.stderr.isTTY) && !process.env.NO_COLOR;
|
|
21
|
+
const greenBackground = '\x1b[42m\x1b[30m';
|
|
22
|
+
const ansiReset = '\x1b[0m';
|
|
23
|
+
|
|
24
|
+
function resolveRuntimePath(specifier) {
|
|
25
|
+
try {
|
|
26
|
+
return localRequire.resolve(specifier);
|
|
27
|
+
} catch {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveFromPackage(packageName, relativePath) {
|
|
33
|
+
const packageJsonPath = resolveRuntimePath(`${packageName}/package.json`);
|
|
34
|
+
if (!packageJsonPath) return '';
|
|
35
|
+
const candidate = join(dirname(packageJsonPath), relativePath);
|
|
36
|
+
return existsSync(candidate) ? candidate : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveBinFallback(name) {
|
|
40
|
+
const cmdSuffix = process.platform === 'win32' ? '.cmd' : '';
|
|
41
|
+
const candidate = join(repoRoot, 'node_modules', '.bin', `${name}${cmdSuffix}`);
|
|
42
|
+
return existsSync(candidate) ? candidate : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const nxBin =
|
|
46
|
+
resolveRuntimePath('nx/bin/nx.js') ||
|
|
47
|
+
resolveFromPackage('nx', 'bin/nx.js') ||
|
|
48
|
+
resolveBinFallback('nx');
|
|
49
|
+
|
|
50
|
+
const tsxCli =
|
|
51
|
+
resolveRuntimePath('tsx/dist/cli.mjs') ||
|
|
52
|
+
resolveFromPackage('tsx', 'dist/cli.mjs') ||
|
|
53
|
+
resolveBinFallback('tsx');
|
|
54
|
+
|
|
55
|
+
function isPortInUse(port, host = '127.0.0.1') {
|
|
56
|
+
return new Promise((resolvePort) => {
|
|
57
|
+
const server = net.createServer();
|
|
58
|
+
server.once('error', (error) => {
|
|
59
|
+
if (error && error.code === 'EADDRINUSE') {
|
|
60
|
+
resolvePort(true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
resolvePort(false);
|
|
64
|
+
});
|
|
65
|
+
server.once('listening', () => {
|
|
66
|
+
server.close(() => resolvePort(false));
|
|
67
|
+
});
|
|
68
|
+
server.listen(port, host);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!existsSync(packageJson)) {
|
|
73
|
+
process.stderr.write(`[mcp-start] Invalid repository root: ${repoRoot}\n`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function spawnRuntime(runtime) {
|
|
78
|
+
const nxTarget = standalone ? 'mcp-server:serve' : 'mcp-server:serve-mcp';
|
|
79
|
+
const entryScript =
|
|
80
|
+
runtime === 'dist'
|
|
81
|
+
? standalone
|
|
82
|
+
? mainServerDistEntry
|
|
83
|
+
: mcpBridgeDistEntry
|
|
84
|
+
: standalone
|
|
85
|
+
? mainServerEntry
|
|
86
|
+
: mcpBridgeEntry;
|
|
87
|
+
|
|
88
|
+
if (dryRun) {
|
|
89
|
+
process.stderr.write(`[mcp-start] Dry run mode. Selected runtime: ${runtime}\n`);
|
|
90
|
+
if (runtime === 'nx') {
|
|
91
|
+
process.stderr.write(`[mcp-start] Command: node ${nxBin} run ${nxTarget}\n`);
|
|
92
|
+
} else if (runtime === 'dist') {
|
|
93
|
+
process.stderr.write(`[mcp-start] Command: node ${entryScript}\n`);
|
|
94
|
+
} else {
|
|
95
|
+
process.stderr.write(`[mcp-start] Command: node ${tsxCli} ${entryScript}\n`);
|
|
96
|
+
}
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const startedMessage = standalone
|
|
101
|
+
? `[mcp-start] Started Browser Debug MCP Bridge (runtime: ${runtime}, mode: standalone). Keep this terminal open.`
|
|
102
|
+
: `[mcp-start] Started Browser Debug MCP Bridge (runtime: ${runtime}, mode: mcp-stdio).`;
|
|
103
|
+
process.stderr.write(`${supportsColor ? `${greenBackground}${startedMessage}${ansiReset}` : startedMessage}\n`);
|
|
104
|
+
if (!standalone && process.stdin.isTTY) {
|
|
105
|
+
process.stderr.write(
|
|
106
|
+
'[mcp-start] Running from interactive terminal without MCP host. ' +
|
|
107
|
+
'Use --standalone for manual keep-alive testing.\n',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const child =
|
|
112
|
+
runtime === 'nx'
|
|
113
|
+
? spawn(
|
|
114
|
+
nxBin.endsWith('.cmd') ? nxBin : process.execPath,
|
|
115
|
+
nxBin.endsWith('.cmd') ? ['run', nxTarget] : [nxBin, 'run', nxTarget],
|
|
116
|
+
{
|
|
117
|
+
cwd: repoRoot,
|
|
118
|
+
env: { ...process.env },
|
|
119
|
+
stdio: 'inherit',
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
: runtime === 'dist'
|
|
123
|
+
? spawn(process.execPath, [entryScript], {
|
|
124
|
+
cwd: repoRoot,
|
|
125
|
+
env: { ...process.env },
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
})
|
|
128
|
+
: spawn(
|
|
129
|
+
tsxCli.endsWith('.cmd') ? tsxCli : process.execPath,
|
|
130
|
+
tsxCli.endsWith('.cmd') ? [entryScript] : [tsxCli, entryScript],
|
|
131
|
+
{
|
|
132
|
+
cwd: repoRoot,
|
|
133
|
+
env: { ...process.env },
|
|
134
|
+
stdio: 'inherit',
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
child.on('exit', (code, signal) => {
|
|
139
|
+
if (signal) {
|
|
140
|
+
process.kill(process.pid, signal);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!standalone && process.stdin.isTTY && (code ?? 0) === 0) {
|
|
144
|
+
process.stderr.write(
|
|
145
|
+
'[mcp-start] MCP stdio process exited (no MCP host attached). ' +
|
|
146
|
+
'Use --standalone to keep the local server running in a terminal.\n',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
process.exit(code ?? 0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
child.on('error', (error) => {
|
|
153
|
+
process.stderr.write(
|
|
154
|
+
`[mcp-start] Failed to launch MCP bridge with ${runtime}. ` +
|
|
155
|
+
`Ensure dependencies are installed. ${error.message}\n`,
|
|
156
|
+
);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function main() {
|
|
162
|
+
const port = Number(process.env.PORT || '8065');
|
|
163
|
+
if (Number.isFinite(port)) {
|
|
164
|
+
const inUse = await isPortInUse(port);
|
|
165
|
+
if (inUse) {
|
|
166
|
+
process.stderr.write(
|
|
167
|
+
`[mcp-start] Port ${port} is already in use. Another Browser Debug MCP Bridge instance is likely running.\n`,
|
|
168
|
+
);
|
|
169
|
+
process.stderr.write(
|
|
170
|
+
'[mcp-start] Stop the existing process, or run this instance on a different port.\n',
|
|
171
|
+
);
|
|
172
|
+
if (process.platform === 'win32') {
|
|
173
|
+
process.stderr.write(
|
|
174
|
+
'[mcp-start] Windows help: netstat -ano | findstr :8065\n',
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
'[mcp-start] Example with different port: PORT=8070 browser-debug-mcp-bridge --standalone\n',
|
|
179
|
+
);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (useDist) {
|
|
185
|
+
if (!existsSync(mcpBridgeDistEntry) || !existsSync(mainServerDistEntry)) {
|
|
186
|
+
process.stderr.write(
|
|
187
|
+
'[mcp-start] Missing dist runtime. Build mcp-server first (pnpm nx build mcp-server).\n',
|
|
188
|
+
);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
spawnRuntime('dist');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (useTsx) {
|
|
196
|
+
if (!existsSync(tsxCli)) {
|
|
197
|
+
process.stderr.write('[mcp-start] Missing tsx runtime. Run npm install/pnpm install first.\n');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
spawnRuntime('tsx');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (existsSync(mcpBridgeDistEntry) && existsSync(mainServerDistEntry)) {
|
|
205
|
+
spawnRuntime('dist');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (existsSync(nxBin)) {
|
|
210
|
+
spawnRuntime('nx');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!existsSync(tsxCli)) {
|
|
215
|
+
process.stderr.write(
|
|
216
|
+
'[mcp-start] Missing both nx and tsx runtimes. ' +
|
|
217
|
+
'Install dependencies first (npm install or pnpm install).\n',
|
|
218
|
+
);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
process.stderr.write('[mcp-start] nx runtime not found, using tsx fallback runtime.\n');
|
|
223
|
+
spawnRuntime('tsx');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
main().catch((error) => {
|
|
227
|
+
process.stderr.write(`[mcp-start] Startup failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
});
|