abu-browser-bridge 0.2.0 → 0.4.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/dist/index.d.ts +5 -3
- package/dist/index.js +63 -43
- package/dist/wsServer.d.ts +3 -3
- package/dist/wsServer.js +6 -27
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* This process acts as a bridge between Abu (via MCP stdio transport)
|
|
6
6
|
* and the Chrome Extension (via WebSocket).
|
|
7
7
|
*
|
|
8
|
+
* Startup strategy:
|
|
9
|
+
* 1. Check if an old bridge is running via the discovery endpoint (port 9875)
|
|
10
|
+
* 2. If found, kill it by PID so we can take over the ports
|
|
11
|
+
* 3. Start discovery (9875) + WS (9876) on fixed ports — no fallback
|
|
12
|
+
*
|
|
8
13
|
* Usage:
|
|
9
14
|
* npx abu-browser-bridge [--port 9876]
|
|
10
|
-
*
|
|
11
|
-
* Abu connects to this as an MCP server (stdio).
|
|
12
|
-
* Chrome Extension connects via WebSocket (ws://127.0.0.1:9876).
|
|
13
15
|
*/
|
|
14
16
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -5,36 +5,63 @@
|
|
|
5
5
|
* This process acts as a bridge between Abu (via MCP stdio transport)
|
|
6
6
|
* and the Chrome Extension (via WebSocket).
|
|
7
7
|
*
|
|
8
|
+
* Startup strategy:
|
|
9
|
+
* 1. Check if an old bridge is running via the discovery endpoint (port 9875)
|
|
10
|
+
* 2. If found, kill it by PID so we can take over the ports
|
|
11
|
+
* 3. Start discovery (9875) + WS (9876) on fixed ports — no fallback
|
|
12
|
+
*
|
|
8
13
|
* Usage:
|
|
9
14
|
* npx abu-browser-bridge [--port 9876]
|
|
10
|
-
*
|
|
11
|
-
* Abu connects to this as an MCP server (stdio).
|
|
12
|
-
* Chrome Extension connects via WebSocket (ws://127.0.0.1:9876).
|
|
13
15
|
*/
|
|
14
16
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
17
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
|
-
import { execSync } from 'child_process';
|
|
17
18
|
import { startWSServer, stopWSServer } from './wsServer.js';
|
|
18
19
|
import { registerTools } from './tools.js';
|
|
19
20
|
const DEFAULT_WS_PORT = 9876;
|
|
20
|
-
const
|
|
21
|
+
const DISCOVERY_PORT = 9875;
|
|
21
22
|
/**
|
|
22
|
-
* Kill any stale abu-browser-bridge
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Kill any stale abu-browser-bridge by querying the discovery endpoint.
|
|
24
|
+
* If an old bridge is running, its /status returns { pid }, and we kill it.
|
|
25
|
+
* Also kills any process on the WS port range as a fallback.
|
|
25
26
|
*/
|
|
26
|
-
function killStaleBridges(
|
|
27
|
+
async function killStaleBridges(wsPort) {
|
|
27
28
|
const myPid = process.pid;
|
|
29
|
+
// Method 1: Query discovery endpoint for PID (most reliable)
|
|
30
|
+
try {
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
33
|
+
const res = await fetch(`http://127.0.0.1:${DISCOVERY_PORT}/status`, {
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
const data = await res.json();
|
|
39
|
+
if (data.pid && data.pid !== myPid) {
|
|
40
|
+
try {
|
|
41
|
+
process.kill(data.pid, 'SIGTERM');
|
|
42
|
+
console.error(`[abu-bridge] Killed old bridge (pid: ${data.pid}) via discovery`);
|
|
43
|
+
// Wait a bit for ports to be released
|
|
44
|
+
await new Promise(r => setTimeout(r, 500));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Already dead
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Discovery not available — old bridge might not have it
|
|
54
|
+
}
|
|
55
|
+
// Method 2: Kill any process on our ports (fallback for old versions without discovery)
|
|
56
|
+
const portsToCheck = [DISCOVERY_PORT, wsPort];
|
|
28
57
|
const isWindows = process.platform === 'win32';
|
|
29
58
|
try {
|
|
30
|
-
let pids;
|
|
31
59
|
if (isWindows) {
|
|
32
|
-
|
|
33
|
-
pids = new Set();
|
|
60
|
+
const { execSync } = await import('child_process');
|
|
34
61
|
const output = execSync('netstat -ano -p TCP', { encoding: 'utf-8', timeout: 5000 });
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const regex = new RegExp(`127\\.0\\.0\\.1:${
|
|
62
|
+
const pids = new Set();
|
|
63
|
+
for (const port of portsToCheck) {
|
|
64
|
+
const regex = new RegExp(`127\\.0\\.0\\.1:${port}\\s+.*LISTENING\\s+(\\d+)`, 'g');
|
|
38
65
|
let match;
|
|
39
66
|
while ((match = regex.exec(output)) !== null) {
|
|
40
67
|
const pid = parseInt(match[1], 10);
|
|
@@ -45,37 +72,34 @@ function killStaleBridges(basePort) {
|
|
|
45
72
|
for (const pid of pids) {
|
|
46
73
|
try {
|
|
47
74
|
execSync(`taskkill /PID ${pid} /F`, { timeout: 3000 });
|
|
48
|
-
console.error(`[abu-bridge] Killed stale process ${pid}`);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Process may have already exited
|
|
75
|
+
console.error(`[abu-bridge] Killed stale process ${pid} (port scan)`);
|
|
52
76
|
}
|
|
77
|
+
catch { /* already dead */ }
|
|
53
78
|
}
|
|
54
79
|
}
|
|
55
80
|
else {
|
|
56
|
-
|
|
57
|
-
const portFlags =
|
|
81
|
+
const { execSync } = await import('child_process');
|
|
82
|
+
const portFlags = portsToCheck.map(p => `-ti:${p}`);
|
|
58
83
|
const output = execSync(`lsof ${portFlags.join(' ')}`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
catch {
|
|
68
|
-
// Process may have already exited
|
|
84
|
+
if (output) {
|
|
85
|
+
const pids = new Set(output.split('\n').map(l => parseInt(l.trim(), 10)).filter(pid => pid && pid !== myPid));
|
|
86
|
+
for (const pid of pids) {
|
|
87
|
+
try {
|
|
88
|
+
process.kill(pid, 'SIGTERM');
|
|
89
|
+
console.error(`[abu-bridge] Killed stale process ${pid} (port scan)`);
|
|
90
|
+
}
|
|
91
|
+
catch { /* already dead */ }
|
|
69
92
|
}
|
|
70
93
|
}
|
|
71
94
|
}
|
|
72
95
|
}
|
|
73
96
|
catch {
|
|
74
|
-
//
|
|
97
|
+
// No processes found or command failed — that's fine
|
|
75
98
|
}
|
|
99
|
+
// Wait for ports to be released
|
|
100
|
+
await new Promise(r => setTimeout(r, 300));
|
|
76
101
|
}
|
|
77
102
|
async function main() {
|
|
78
|
-
// Parse CLI args
|
|
79
103
|
const args = process.argv.slice(2);
|
|
80
104
|
let wsPort = DEFAULT_WS_PORT;
|
|
81
105
|
const portIndex = args.indexOf('--port');
|
|
@@ -86,24 +110,20 @@ async function main() {
|
|
|
86
110
|
process.exit(1);
|
|
87
111
|
}
|
|
88
112
|
}
|
|
89
|
-
// 0. Kill any stale bridge
|
|
90
|
-
killStaleBridges(wsPort);
|
|
91
|
-
// 1. Start WebSocket server (
|
|
92
|
-
// startWSServer already tries ports 9876-9880 with fallback if a port is still in TIME_WAIT
|
|
113
|
+
// 0. Kill any stale bridge so we can take over the fixed ports
|
|
114
|
+
await killStaleBridges(wsPort);
|
|
115
|
+
// 1. Start WebSocket + Discovery server on fixed ports (no fallback)
|
|
93
116
|
try {
|
|
94
|
-
|
|
95
|
-
if (actualPort !== wsPort) {
|
|
96
|
-
console.error(`[abu-bridge] Note: using port ${actualPort} instead of ${wsPort}`);
|
|
97
|
-
}
|
|
117
|
+
await startWSServer(wsPort);
|
|
98
118
|
}
|
|
99
119
|
catch (err) {
|
|
100
120
|
console.error(`Failed to start WS server:`, err);
|
|
101
121
|
process.exit(1);
|
|
102
122
|
}
|
|
103
|
-
// 2. Create MCP server
|
|
123
|
+
// 2. Create MCP server
|
|
104
124
|
const mcpServer = new McpServer({
|
|
105
125
|
name: 'abu-browser-bridge',
|
|
106
|
-
version: '0.
|
|
126
|
+
version: '0.3.0',
|
|
107
127
|
});
|
|
108
128
|
// 3. Register browser tools
|
|
109
129
|
registerTools(mcpServer);
|
package/dist/wsServer.d.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { BridgeResponse } from './types.js';
|
|
9
9
|
/**
|
|
10
|
-
* Start the WebSocket server
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Start the WebSocket server on a fixed port.
|
|
11
|
+
* The caller (index.ts) is responsible for killing stale bridges first.
|
|
12
|
+
* No port fallback — if the port is taken, it's a fatal error.
|
|
13
13
|
*/
|
|
14
14
|
export declare function startWSServer(port?: number): Promise<number>;
|
|
15
15
|
/**
|
package/dist/wsServer.js
CHANGED
|
@@ -95,36 +95,15 @@ function startDiscoveryServer() {
|
|
|
95
95
|
}
|
|
96
96
|
// --- WebSocket Server ---
|
|
97
97
|
/**
|
|
98
|
-
* Start the WebSocket server
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* Start the WebSocket server on a fixed port.
|
|
99
|
+
* The caller (index.ts) is responsible for killing stale bridges first.
|
|
100
|
+
* No port fallback — if the port is taken, it's a fatal error.
|
|
101
101
|
*/
|
|
102
102
|
export async function startWSServer(port = DEFAULT_WS_PORT) {
|
|
103
|
-
// Start discovery endpoint first (non-blocking if port taken)
|
|
104
103
|
await startDiscoveryServer();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const inUse = await isPortInUse(tryPort, '127.0.0.1');
|
|
109
|
-
if (inUse) {
|
|
110
|
-
console.error(`[abu-bridge] Port ${tryPort} is in use, trying ${tryPort + 1}...`);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
await listenOnPort(tryPort);
|
|
115
|
-
activePort = tryPort;
|
|
116
|
-
return tryPort;
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
120
|
-
if (msg.includes('EADDRINUSE') && attempt < MAX_RETRIES - 1) {
|
|
121
|
-
console.error(`[abu-bridge] Port ${tryPort} race condition, trying next...`);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
throw new Error(`All ports ${port}-${port + MAX_RETRIES - 1} are in use`);
|
|
104
|
+
await listenOnPort(port);
|
|
105
|
+
activePort = port;
|
|
106
|
+
return port;
|
|
128
107
|
}
|
|
129
108
|
function listenOnPort(port) {
|
|
130
109
|
return new Promise((resolve, reject) => {
|