chrome-ai-bridge 2.4.0 → 2.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -40
- package/build/extension/README.md +10 -10
- package/build/extension/background.mjs +94 -26
- package/build/extension/manifest.json +2 -2
- package/build/extension/relay-server.ts +16 -2
- package/build/extension/ui/connect.html +1 -1
- package/build/extension/ui/connect.js +46 -10
- package/build/src/cli.js +6 -1
- package/build/src/config.js +2 -4
- package/build/src/extension/relay-server.js +20 -2
- package/build/src/fast-cdp/agent-context.js +2 -2
- package/build/src/fast-cdp/{mcp-logger.js → debug-logger.js} +11 -11
- package/build/src/fast-cdp/extension-raw.js +51 -5
- package/build/src/fast-cdp/fast-chat.js +166 -101
- package/build/src/logger.js +3 -3
- package/build/src/main.js +104 -568
- package/build/src/plugin-api.js +1 -1
- package/build/src/runtime-scope.js +1 -1
- package/build/src/tools/ai-helpers.js +72 -17
- package/build/src/tools/chatgpt-gemini-web.js +1 -1
- package/build/src/tools/chatgpt-web.js +7 -7
- package/build/src/tools/gemini-web.js +10 -22
- package/build/src/tools/optional-tools.js +8 -5
- package/package.json +17 -18
- package/scripts/cab +202 -0
- package/scripts/cli.mjs +1 -1
- package/build/src/McpResponse.js +0 -60
- package/build/src/stdio-http-proxy.js +0 -157
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* stdio-to-HTTP proxy for multi-client MCP support.
|
|
3
|
-
*
|
|
4
|
-
* When a Primary MCP server is already running, Secondary instances
|
|
5
|
-
* start in proxy mode: they bridge stdio (for Claude Code) to the
|
|
6
|
-
* Primary's Streamable HTTP endpoint.
|
|
7
|
-
*
|
|
8
|
-
* Uses MCP SDK transports to avoid custom JSON-RPC parsing.
|
|
9
|
-
*
|
|
10
|
-
* Resilience: On Primary disconnect, retries with exponential backoff
|
|
11
|
-
* (1s, 2s, 4s — max 3 attempts, ~7s total) before giving up.
|
|
12
|
-
*/
|
|
13
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
15
|
-
import { logger } from './logger.js';
|
|
16
|
-
import { IPC_CONFIG } from './config.js';
|
|
17
|
-
import { readLockInfo } from './process-lock.js';
|
|
18
|
-
/**
|
|
19
|
-
* Check if the Primary's /health endpoint is reachable.
|
|
20
|
-
* Optionally verifies instanceId matches the lock file.
|
|
21
|
-
*/
|
|
22
|
-
export async function checkPrimaryHealth(port, expectedInstanceId) {
|
|
23
|
-
try {
|
|
24
|
-
const resp = await fetch(`http://${IPC_CONFIG.host}:${port}${IPC_CONFIG.healthPath}`, { signal: AbortSignal.timeout(2000) });
|
|
25
|
-
if (!resp.ok)
|
|
26
|
-
return false;
|
|
27
|
-
if (expectedInstanceId) {
|
|
28
|
-
const body = (await resp.json());
|
|
29
|
-
if (body.instanceId && body.instanceId !== expectedInstanceId) {
|
|
30
|
-
logger(`[proxy] instanceId mismatch: expected=${expectedInstanceId.slice(0, 8)}, got=${body.instanceId.slice(0, 8)}`);
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const RETRY_DELAYS = [1000, 2000, 4000]; // exponential backoff
|
|
41
|
-
/**
|
|
42
|
-
* Attempt to reconnect to the Primary with exponential backoff.
|
|
43
|
-
* Re-reads lock file on every attempt (follow-the-leader strategy):
|
|
44
|
-
* - Port may have changed (dynamic fallback)
|
|
45
|
-
* - instanceId may have changed (Primary restart)
|
|
46
|
-
* Returns the current port for reconnection.
|
|
47
|
-
*/
|
|
48
|
-
async function waitForPrimaryRecovery(initialPort) {
|
|
49
|
-
for (let i = 0; i < RETRY_DELAYS.length; i++) {
|
|
50
|
-
const delay = RETRY_DELAYS[i];
|
|
51
|
-
logger(`[proxy] Retry ${i + 1}/${RETRY_DELAYS.length} in ${delay}ms...`);
|
|
52
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
53
|
-
// Re-read lock file each attempt (port/instanceId may have changed)
|
|
54
|
-
const lockInfo = readLockInfo();
|
|
55
|
-
if (!lockInfo) {
|
|
56
|
-
logger('[proxy] Lock file missing or unreadable, retrying...');
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const currentPort = lockInfo.port || initialPort;
|
|
60
|
-
// Follow-the-leader: use lock file's instanceId as truth
|
|
61
|
-
const expectedId = lockInfo.instanceId || undefined;
|
|
62
|
-
if (await checkPrimaryHealth(currentPort, expectedId)) {
|
|
63
|
-
logger(`[proxy] Primary recovered (port=${currentPort})`);
|
|
64
|
-
return { recovered: true, port: currentPort };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return { recovered: false, port: initialPort };
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Start in proxy mode: bridge stdio <-> Primary HTTP.
|
|
71
|
-
*
|
|
72
|
-
* This function never returns normally — the process exits
|
|
73
|
-
* when stdin closes or the Primary becomes unreachable after retries.
|
|
74
|
-
*/
|
|
75
|
-
export async function startProxyMode(port) {
|
|
76
|
-
let currentPort = port;
|
|
77
|
-
const mcpUrl = new URL(`http://${IPC_CONFIG.host}:${currentPort}${IPC_CONFIG.mcpPath}`);
|
|
78
|
-
logger(`[proxy] Entering proxy mode -> ${mcpUrl}`);
|
|
79
|
-
const stdio = new StdioServerTransport();
|
|
80
|
-
let http = new StreamableHTTPClientTransport(mcpUrl);
|
|
81
|
-
let isReconnecting = false;
|
|
82
|
-
/**
|
|
83
|
-
* Try to reconnect to the Primary after disconnect.
|
|
84
|
-
* Follows the leader: adopts new port/instanceId from lock file.
|
|
85
|
-
* On success, creates a new HTTP transport and re-wires the bridge.
|
|
86
|
-
* On failure, exits with code 1.
|
|
87
|
-
*/
|
|
88
|
-
async function handlePrimaryDisconnect() {
|
|
89
|
-
if (isReconnecting)
|
|
90
|
-
return;
|
|
91
|
-
isReconnecting = true;
|
|
92
|
-
logger('[proxy] Primary disconnected. Attempting recovery...');
|
|
93
|
-
const result = await waitForPrimaryRecovery(currentPort);
|
|
94
|
-
if (!result.recovered) {
|
|
95
|
-
logger('[proxy] Primary not recovered after retries. Exiting.');
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
// Port may have changed after Primary restart
|
|
99
|
-
currentPort = result.port;
|
|
100
|
-
const newMcpUrl = new URL(`http://${IPC_CONFIG.host}:${currentPort}${IPC_CONFIG.mcpPath}`);
|
|
101
|
-
// Create new transport and re-wire
|
|
102
|
-
try {
|
|
103
|
-
const newHttp = new StreamableHTTPClientTransport(newMcpUrl);
|
|
104
|
-
wireHttpTransport(newHttp);
|
|
105
|
-
await newHttp.start();
|
|
106
|
-
http = newHttp;
|
|
107
|
-
isReconnecting = false;
|
|
108
|
-
logger(`[proxy] Reconnected to Primary (port=${currentPort})`);
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
logger(`[proxy] Reconnection failed: ${err}`);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Wire event handlers for an HTTP transport instance.
|
|
117
|
-
*/
|
|
118
|
-
function wireHttpTransport(transport) {
|
|
119
|
-
transport.onmessage = (message) => {
|
|
120
|
-
stdio.send(message).catch((err) => {
|
|
121
|
-
logger(`[proxy] Failed to write to stdout: ${err}`);
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
transport.onclose = () => {
|
|
125
|
-
logger('[proxy] HTTP connection to Primary closed');
|
|
126
|
-
handlePrimaryDisconnect();
|
|
127
|
-
};
|
|
128
|
-
transport.onerror = (err) => {
|
|
129
|
-
logger(`[proxy] HTTP error: ${err.message}`);
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// Bridge: stdin (Claude Code) -> HTTP POST (Primary)
|
|
133
|
-
stdio.onmessage = (message) => {
|
|
134
|
-
http.send(message).catch((err) => {
|
|
135
|
-
logger(`[proxy] Failed to forward to Primary: ${err}`);
|
|
136
|
-
handlePrimaryDisconnect();
|
|
137
|
-
});
|
|
138
|
-
};
|
|
139
|
-
// Handle stdio close (Claude Code disconnected)
|
|
140
|
-
stdio.onclose = () => {
|
|
141
|
-
logger('[proxy] stdio closed');
|
|
142
|
-
http
|
|
143
|
-
.terminateSession()
|
|
144
|
-
.catch(() => { })
|
|
145
|
-
.finally(() => http.close().catch(() => { }))
|
|
146
|
-
.finally(() => process.exit(0));
|
|
147
|
-
};
|
|
148
|
-
// Wire initial HTTP transport
|
|
149
|
-
wireHttpTransport(http);
|
|
150
|
-
// Start HTTP transport first (sets up AbortController),
|
|
151
|
-
// then stdio (starts reading from stdin).
|
|
152
|
-
await http.start();
|
|
153
|
-
await stdio.start();
|
|
154
|
-
logger('[proxy] Proxy mode active');
|
|
155
|
-
// Keep process alive; exit is handled by event handlers above.
|
|
156
|
-
return new Promise(() => { });
|
|
157
|
-
}
|