aipex-mcp-bridge 1.0.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.
Files changed (3) hide show
  1. package/README.md +119 -0
  2. package/dist/bridge.js +238 -0
  3. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # aipex-mcp-bridge
2
+
3
+ MCP bridge that connects AI agents to the [AIPex](https://aipex.ai) browser extension via WebSocket.
4
+
5
+ Works with **any** MCP client that supports stdio transport — Cursor, Claude Desktop, Claude Code, VS Code Copilot, Windsurf, Zed, and more.
6
+
7
+ ## How it works
8
+
9
+ ```
10
+ AI Agent (MCP client) ──stdio──▶ aipex-mcp-bridge ──WebSocket──▶ AIPex Chrome Extension
11
+ ```
12
+
13
+ The bridge starts a WebSocket server on `localhost:9223` (configurable) and communicates with your AI agent over stdio using the MCP protocol. The AIPex extension connects to the WebSocket server to expose browser control tools.
14
+
15
+ ## Quick start
16
+
17
+ ### 1. Configure your AI agent
18
+
19
+ Add the following to your agent's MCP configuration:
20
+
21
+ **Cursor** (`.cursor/mcp.json`):
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "aipex-browser": {
27
+ "command": "npx",
28
+ "args": ["-y", "aipex-mcp-bridge"]
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ **Claude Desktop** (`claude_desktop_config.json`):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "aipex-browser": {
40
+ "command": "npx",
41
+ "args": ["-y", "aipex-mcp-bridge"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Claude Code**:
48
+
49
+ ```bash
50
+ claude mcp add aipex-browser -- npx -y aipex-mcp-bridge
51
+ ```
52
+
53
+ **VS Code Copilot** (`.vscode/mcp.json`):
54
+
55
+ ```json
56
+ {
57
+ "servers": {
58
+ "aipex-browser": {
59
+ "command": "npx",
60
+ "args": ["-y", "aipex-mcp-bridge"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ **Windsurf** (`mcp_config.json`):
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "aipex-browser": {
72
+ "command": "npx",
73
+ "args": ["-y", "aipex-mcp-bridge"]
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### 2. Connect AIPex extension
80
+
81
+ 1. Open Chrome → AIPex extension → Options page
82
+ 2. Set WebSocket URL to `ws://localhost:9223`
83
+ 3. Click **Connect**
84
+
85
+ Your AI agent can now control the browser through AIPex.
86
+
87
+ ## Options
88
+
89
+ ```
90
+ npx aipex-mcp-bridge [--port <port>]
91
+ ```
92
+
93
+ | Option | Default | Description |
94
+ | ----------------- | ------- | ---------------------------------- |
95
+ | `--port <port>` | `9223` | WebSocket port for AIPex extension |
96
+ | `--help`, `-h` | | Show help message |
97
+ | `--version`, `-v` | | Show version |
98
+
99
+ ### Custom port example
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "aipex-browser": {
105
+ "command": "npx",
106
+ "args": ["-y", "aipex-mcp-bridge", "--port", "8080"]
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Requirements
113
+
114
+ - Node.js >= 18
115
+ - AIPex Chrome extension installed
116
+
117
+ ## License
118
+
119
+ MIT
package/dist/bridge.js ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bridge.ts
4
+ import { createServer } from "http";
5
+ import { createInterface } from "readline";
6
+ import { WebSocket, WebSocketServer } from "ws";
7
+ var cliArgs = process.argv.slice(2);
8
+ if (cliArgs.includes("--help") || cliArgs.includes("-h")) {
9
+ process.stderr.write(`
10
+ AIPex MCP Bridge \u2014 connect AI agents to AIPex browser extension
11
+
12
+ Usage:
13
+ npx aipex-mcp-bridge [--port <port>]
14
+
15
+ Options:
16
+ --port <port> WebSocket port for AIPex extension (default: 9223)
17
+ --help, -h Show this help message
18
+ --version, -v Show version
19
+
20
+ After starting, open AIPex extension Options and connect to:
21
+ ws://localhost:<port>
22
+ `);
23
+ process.exit(0);
24
+ }
25
+ if (cliArgs.includes("--version") || cliArgs.includes("-v")) {
26
+ process.stderr.write("aipex-mcp-bridge 1.0.0\n");
27
+ process.exit(0);
28
+ }
29
+ var portIdx = cliArgs.indexOf("--port");
30
+ var WS_PORT = portIdx !== -1 ? parseInt(cliArgs[portIdx + 1], 10) : 9223;
31
+ if (isNaN(WS_PORT) || WS_PORT < 1 || WS_PORT > 65535) {
32
+ process.stderr.write(`Invalid port number. Must be between 1 and 65535.
33
+ `);
34
+ process.exit(1);
35
+ }
36
+ function log(msg) {
37
+ process.stderr.write(`[aipex-bridge] ${msg}
38
+ `);
39
+ }
40
+ var aipexSocket = null;
41
+ var aipexReady = false;
42
+ var cachedTools = [];
43
+ var nextAipexId = 1;
44
+ var aipexPending = /* @__PURE__ */ new Map();
45
+ function respond(id, result) {
46
+ const msg = { jsonrpc: "2.0", id, result };
47
+ process.stdout.write(JSON.stringify(msg) + "\n");
48
+ }
49
+ function respondError(id, code, message) {
50
+ const msg = { jsonrpc: "2.0", id, error: { code, message } };
51
+ process.stdout.write(JSON.stringify(msg) + "\n");
52
+ }
53
+ function sendToAipex(method, params = {}) {
54
+ if (!aipexSocket || aipexSocket.readyState !== WebSocket.OPEN) {
55
+ return Promise.reject(new Error("AIPex extension not connected"));
56
+ }
57
+ const id = nextAipexId++;
58
+ const msg = { jsonrpc: "2.0", id, method, params };
59
+ aipexSocket.send(JSON.stringify(msg));
60
+ return new Promise((resolve, reject) => {
61
+ aipexPending.set(id, { resolve, reject });
62
+ });
63
+ }
64
+ function handleAipexMessage(raw) {
65
+ let msg;
66
+ try {
67
+ msg = JSON.parse(raw);
68
+ } catch {
69
+ log(`Failed to parse AIPex message: ${raw.slice(0, 100)}`);
70
+ return;
71
+ }
72
+ if ("result" in msg || "error" in msg) {
73
+ const res = msg;
74
+ const p = aipexPending.get(res.id);
75
+ if (p) {
76
+ aipexPending.delete(res.id);
77
+ if (res.error) {
78
+ p.reject(new Error(res.error.message));
79
+ } else {
80
+ p.resolve(res.result);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ async function doAipexHandshake(socket) {
86
+ log("Starting MCP handshake with AIPex...");
87
+ const initResult = await sendToAipex("initialize", {
88
+ protocolVersion: "2024-11-05",
89
+ capabilities: {},
90
+ clientInfo: { name: "aipex-mcp-bridge", version: "1.0.0" }
91
+ });
92
+ const serverInfo = initResult?.serverInfo;
93
+ log(`AIPex server: ${serverInfo?.name ?? "?"} v${serverInfo?.version ?? "?"}`);
94
+ socket.send(
95
+ JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
96
+ );
97
+ const toolsResult = await sendToAipex("tools/list");
98
+ cachedTools = toolsResult?.tools ?? [];
99
+ aipexReady = true;
100
+ log(`Handshake complete. ${cachedTools.length} tools available.`);
101
+ }
102
+ async function handleAgentRequest(req) {
103
+ const { id, method, params } = req;
104
+ if (method === "initialize") {
105
+ respond(id, {
106
+ protocolVersion: "2024-11-05",
107
+ capabilities: { tools: {} },
108
+ serverInfo: { name: "aipex-mcp-bridge", version: "1.0.0" }
109
+ });
110
+ return;
111
+ }
112
+ if (method === "notifications/initialized") {
113
+ return;
114
+ }
115
+ if (method === "tools/list") {
116
+ if (aipexReady && cachedTools.length > 0) {
117
+ respond(id, { tools: cachedTools });
118
+ } else {
119
+ respond(id, {
120
+ tools: [
121
+ {
122
+ name: "check_aipex_connection",
123
+ description: [
124
+ "AIPex extension is not connected. To enable browser control:",
125
+ `1. Open Chrome \u2192 AIPex extension \u2192 Options page`,
126
+ `2. Set WebSocket URL to: ws://localhost:${WS_PORT}`,
127
+ `3. Click Connect`,
128
+ `Then reload this MCP server.`
129
+ ].join("\n"),
130
+ inputSchema: { type: "object", properties: {} }
131
+ }
132
+ ]
133
+ });
134
+ }
135
+ return;
136
+ }
137
+ if (method === "tools/call") {
138
+ if (!aipexReady || !aipexSocket || aipexSocket.readyState !== WebSocket.OPEN) {
139
+ respondError(
140
+ id,
141
+ -32e3,
142
+ `AIPex extension not connected. Open AIPex Options and connect to ws://localhost:${WS_PORT}`
143
+ );
144
+ return;
145
+ }
146
+ try {
147
+ const result = await sendToAipex(
148
+ "tools/call",
149
+ params
150
+ );
151
+ respond(id, result);
152
+ } catch (e) {
153
+ respondError(id, -32e3, e instanceof Error ? e.message : String(e));
154
+ }
155
+ return;
156
+ }
157
+ if (method === "ping") {
158
+ if (aipexReady && aipexSocket?.readyState === WebSocket.OPEN) {
159
+ try {
160
+ const result = await sendToAipex("ping");
161
+ respond(id, result);
162
+ } catch {
163
+ respond(id, {});
164
+ }
165
+ } else {
166
+ respond(id, {});
167
+ }
168
+ return;
169
+ }
170
+ respondError(id, -32601, `Method not found: ${method}`);
171
+ }
172
+ var stdinRl = createInterface({ input: process.stdin });
173
+ stdinRl.on("line", (line) => {
174
+ const trimmed = line.trim();
175
+ if (!trimmed) return;
176
+ let req;
177
+ try {
178
+ req = JSON.parse(trimmed);
179
+ } catch {
180
+ log(`Failed to parse stdin: ${trimmed.slice(0, 100)}`);
181
+ return;
182
+ }
183
+ handleAgentRequest(req).catch((e) => {
184
+ log(`Error handling request: ${e instanceof Error ? e.message : String(e)}`);
185
+ if (req.id != null) {
186
+ respondError(req.id, -32603, "Internal error");
187
+ }
188
+ });
189
+ });
190
+ stdinRl.on("close", () => {
191
+ log("stdin closed, shutting down");
192
+ process.exit(0);
193
+ });
194
+ var httpServer = createServer();
195
+ var wss = new WebSocketServer({ server: httpServer });
196
+ wss.on("connection", (socket, req) => {
197
+ const addr = req.socket.remoteAddress ?? "unknown";
198
+ if (aipexSocket && aipexSocket.readyState === WebSocket.OPEN) {
199
+ log(`New connection from ${addr}, closing previous`);
200
+ aipexSocket.close();
201
+ }
202
+ aipexSocket = socket;
203
+ aipexReady = false;
204
+ cachedTools = [];
205
+ log(`AIPex extension connected from ${addr}`);
206
+ socket.on("message", (data) => {
207
+ handleAipexMessage(data.toString());
208
+ });
209
+ socket.on("close", () => {
210
+ log("AIPex extension disconnected");
211
+ if (aipexSocket === socket) {
212
+ aipexSocket = null;
213
+ aipexReady = false;
214
+ cachedTools = [];
215
+ }
216
+ });
217
+ socket.on("error", (err) => {
218
+ log(`Socket error: ${err.message}`);
219
+ });
220
+ doAipexHandshake(socket).catch((err) => {
221
+ log(`Handshake failed: ${err.message}`);
222
+ });
223
+ });
224
+ wss.on("error", (err) => {
225
+ log(`WebSocket server error: ${err.message}`);
226
+ });
227
+ httpServer.listen(WS_PORT, () => {
228
+ log(`AIPex MCP Bridge started`);
229
+ log(`WebSocket server listening on ws://localhost:${WS_PORT}`);
230
+ log(`Waiting for AIPex extension to connect...`);
231
+ log(`Open AIPex Options \u2192 set URL to ws://localhost:${WS_PORT} \u2192 Connect`);
232
+ });
233
+ process.on("SIGINT", () => {
234
+ log("Shutting down...");
235
+ wss.close();
236
+ httpServer.close();
237
+ process.exit(0);
238
+ });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "aipex-mcp-bridge",
3
+ "version": "1.0.0",
4
+ "description": "MCP bridge that connects AI agents (Cursor, Claude, VS Code Copilot, etc.) to the AIPex browser extension via WebSocket",
5
+ "type": "module",
6
+ "bin": {
7
+ "aipex-mcp-bridge": "./dist/bridge.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsx src/bridge.ts"
16
+ },
17
+ "dependencies": {
18
+ "ws": "^8.18.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/ws": "^8.5.13",
22
+ "@types/node": "^22.0.0",
23
+ "tsup": "^8.0.0",
24
+ "tsx": "^4.21.0",
25
+ "typescript": "^5.3.0"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "aipex",
31
+ "browser",
32
+ "cursor",
33
+ "claude",
34
+ "copilot",
35
+ "websocket",
36
+ "bridge"
37
+ ],
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/AIPexStudio/aipex-whole.git",
42
+ "directory": "aipex/mcp-bridge"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ }
47
+ }