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 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 PORT_RANGE = 5; // ports 9876-9880
21
+ const DISCOVERY_PORT = 9875;
21
22
  /**
22
- * Kill any stale abu-browser-bridge processes listening on our port range.
23
- * This ensures the Extension always connects to the newest bridge.
24
- * Cross-platform: uses lsof on macOS/Linux, netstat+taskkill on Windows.
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(basePort) {
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
- // Windows: use netstat to find PIDs on each port
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
- for (let p = basePort; p < basePort + PORT_RANGE; p++) {
36
- // Match lines like " TCP 127.0.0.1:9876 0.0.0.0:0 LISTENING 12345"
37
- const regex = new RegExp(`127\\.0\\.0\\.1:${p}\\s+.*LISTENING\\s+(\\d+)`, 'g');
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
- // macOS/Linux: single lsof call with multiple -i flags
57
- const portFlags = Array.from({ length: PORT_RANGE }, (_, i) => `-ti:${basePort + i}`);
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 (!output)
60
- return;
61
- pids = new Set(output.split('\n').map(l => parseInt(l.trim(), 10)).filter(pid => pid && pid !== myPid));
62
- for (const pid of pids) {
63
- try {
64
- process.kill(pid, 'SIGTERM');
65
- console.error(`[abu-bridge] Killed stale process ${pid}`);
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
- // Command not available or no processes found — that's fine
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 processes so the Extension connects to us
90
- killStaleBridges(wsPort);
91
- // 1. Start WebSocket server (for Chrome Extension)
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
- const actualPort = await startWSServer(wsPort);
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 (for Abu)
123
+ // 2. Create MCP server
104
124
  const mcpServer = new McpServer({
105
125
  name: 'abu-browser-bridge',
106
- version: '0.1.0',
126
+ version: '0.3.0',
107
127
  });
108
128
  // 3. Register browser tools
109
129
  registerTools(mcpServer);
@@ -7,9 +7,9 @@
7
7
  */
8
8
  import type { BridgeResponse } from './types.js';
9
9
  /**
10
- * Start the WebSocket server. If the default port is occupied,
11
- * try a few alternative ports. The Extension discovers the port
12
- * via the HTTP discovery endpoint.
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. If the default port is occupied,
99
- * try a few alternative ports. The Extension discovers the port
100
- * via the HTTP discovery endpoint.
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
- const MAX_RETRIES = 5;
106
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
107
- const tryPort = port + attempt;
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abu-browser-bridge",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MCP Server that bridges Abu AI assistant with Chrome Extension for browser automation",
5
5
  "type": "module",
6
6
  "license": "MIT",