mcp-page-bridge 0.1.0 → 0.1.4
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/bridge.js +1 -1
- package/dist/{chunk-7BFUNK5V.js → chunk-RRWNJUKA.js} +105 -65
- package/dist/cli.js +48 -2
- package/package.json +3 -3
package/dist/bridge.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
22
|
|
|
23
23
|
// ../protocol/src/index.ts
|
|
24
|
-
var MCP_PAGE_BRIDGE_VERSION = "0.1.
|
|
24
|
+
var MCP_PAGE_BRIDGE_VERSION = "0.1.4";
|
|
25
25
|
var WS_SUBPROTOCOL = "mcp";
|
|
26
26
|
var DEFAULT_PORT = 8787;
|
|
27
27
|
var NAMESPACE_SEP = "__";
|
|
@@ -781,17 +781,7 @@ async function createBridge(opts = {}) {
|
|
|
781
781
|
const toolRoutes = /* @__PURE__ */ new Map();
|
|
782
782
|
const promptRoutes = /* @__PURE__ */ new Map();
|
|
783
783
|
const resourceRoutes = /* @__PURE__ */ new Map();
|
|
784
|
-
const
|
|
785
|
-
{ name: "mcp-page-bridge", version: MCP_PAGE_BRIDGE_VERSION },
|
|
786
|
-
{
|
|
787
|
-
capabilities: {
|
|
788
|
-
tools: { listChanged: true },
|
|
789
|
-
prompts: { listChanged: true },
|
|
790
|
-
resources: { listChanged: true },
|
|
791
|
-
logging: {}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
);
|
|
784
|
+
const agentServers = /* @__PURE__ */ new Set();
|
|
795
785
|
function metaTools() {
|
|
796
786
|
return [
|
|
797
787
|
{
|
|
@@ -875,9 +865,11 @@ async function createBridge(opts = {}) {
|
|
|
875
865
|
}
|
|
876
866
|
function notifyChanged(kind) {
|
|
877
867
|
rebuildRoutes();
|
|
878
|
-
const send = kind === "tools" ? () =>
|
|
879
|
-
|
|
880
|
-
|
|
868
|
+
const send = kind === "tools" ? (s) => s.sendToolListChanged() : kind === "prompts" ? (s) => s.sendPromptListChanged() : (s) => s.sendResourceListChanged();
|
|
869
|
+
for (const agentServer of agentServers) {
|
|
870
|
+
void Promise.resolve().then(() => send(agentServer)).catch(() => {
|
|
871
|
+
});
|
|
872
|
+
}
|
|
881
873
|
}
|
|
882
874
|
function uniqueLabel(base) {
|
|
883
875
|
const used = new Set([...providers.values()].map((p) => p.label));
|
|
@@ -886,49 +878,65 @@ async function createBridge(opts = {}) {
|
|
|
886
878
|
while (used.has(`${base}-${i}`)) i += 1;
|
|
887
879
|
return `${base}-${i}`;
|
|
888
880
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
881
|
+
function createAgentServer() {
|
|
882
|
+
const agentServer = new Server(
|
|
883
|
+
{ name: "mcp-page-bridge", version: MCP_PAGE_BRIDGE_VERSION },
|
|
884
|
+
{
|
|
885
|
+
capabilities: {
|
|
886
|
+
tools: { listChanged: true },
|
|
887
|
+
prompts: { listChanged: true },
|
|
888
|
+
resources: { listChanged: true },
|
|
889
|
+
logging: {}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
);
|
|
893
|
+
agentServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: exposedTools() }));
|
|
894
|
+
agentServer.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
895
|
+
const name = req.params.name;
|
|
896
|
+
if (name === META_LIST_CLIENTS) {
|
|
897
|
+
return { content: [{ type: "text", text: JSON.stringify(providerSummary(), null, 2) }] };
|
|
898
|
+
}
|
|
899
|
+
const route = toolRoutes.get(name);
|
|
900
|
+
const provider = route && providers.get(route.providerId);
|
|
901
|
+
if (!route || !provider) {
|
|
902
|
+
return { content: [{ type: "text", text: `Unknown or disconnected tool: ${name}` }], isError: true };
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const result = await provider.client.callTool({
|
|
906
|
+
name: route.originalName,
|
|
907
|
+
arguments: req.params.arguments ?? {}
|
|
908
|
+
});
|
|
909
|
+
return result;
|
|
910
|
+
} catch (error) {
|
|
911
|
+
return {
|
|
912
|
+
content: [{ type: "text", text: `Tool call failed: ${error.message}` }],
|
|
913
|
+
isError: true
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
agentServer.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: exposedPrompts() }));
|
|
918
|
+
agentServer.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
919
|
+
const route = promptRoutes.get(req.params.name);
|
|
920
|
+
const provider = route && providers.get(route.providerId);
|
|
921
|
+
if (!route || !provider) throw new Error(`Unknown or disconnected prompt: ${req.params.name}`);
|
|
922
|
+
return provider.client.getPrompt({
|
|
902
923
|
name: route.originalName,
|
|
903
|
-
arguments: req.params.arguments
|
|
924
|
+
arguments: req.params.arguments
|
|
904
925
|
});
|
|
905
|
-
return result;
|
|
906
|
-
} catch (error) {
|
|
907
|
-
return {
|
|
908
|
-
content: [{ type: "text", text: `Tool call failed: ${error.message}` }],
|
|
909
|
-
isError: true
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: exposedPrompts() }));
|
|
914
|
-
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
915
|
-
const route = promptRoutes.get(req.params.name);
|
|
916
|
-
const provider = route && providers.get(route.providerId);
|
|
917
|
-
if (!route || !provider) throw new Error(`Unknown or disconnected prompt: ${req.params.name}`);
|
|
918
|
-
return provider.client.getPrompt({
|
|
919
|
-
name: route.originalName,
|
|
920
|
-
arguments: req.params.arguments
|
|
921
926
|
});
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
927
|
+
agentServer.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
928
|
+
resources: exposedResources()
|
|
929
|
+
}));
|
|
930
|
+
agentServer.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
931
|
+
const providerId = resourceRoutes.get(req.params.uri);
|
|
932
|
+
const provider = providerId ? providers.get(providerId) : void 0;
|
|
933
|
+
if (!provider) throw new Error(`Unknown or disconnected resource: ${req.params.uri}`);
|
|
934
|
+
return provider.client.readResource({ uri: req.params.uri });
|
|
935
|
+
});
|
|
936
|
+
agentServers.add(agentServer);
|
|
937
|
+
return agentServer;
|
|
938
|
+
}
|
|
939
|
+
const server = createAgentServer();
|
|
932
940
|
function providerSummary() {
|
|
933
941
|
return [...providers.values()].map((p) => ({
|
|
934
942
|
label: p.label,
|
|
@@ -1053,6 +1061,33 @@ async function createBridge(opts = {}) {
|
|
|
1053
1061
|
const address = httpServer.address();
|
|
1054
1062
|
const port = typeof address === "object" && address ? address.port : opts.port ?? DEFAULT_PORT;
|
|
1055
1063
|
wss.on("connection", async (ws, req) => {
|
|
1064
|
+
const path = (() => {
|
|
1065
|
+
try {
|
|
1066
|
+
return new URL(req.url ?? "/", "ws://localhost").pathname;
|
|
1067
|
+
} catch {
|
|
1068
|
+
return "/";
|
|
1069
|
+
}
|
|
1070
|
+
})();
|
|
1071
|
+
if (path === "/agent") {
|
|
1072
|
+
const agentServer = createAgentServer();
|
|
1073
|
+
const transport2 = new WebSocketServerTransport(ws);
|
|
1074
|
+
const cleanupAgent = () => {
|
|
1075
|
+
if (!agentServers.delete(agentServer)) return;
|
|
1076
|
+
void agentServer.close().catch(() => {
|
|
1077
|
+
});
|
|
1078
|
+
};
|
|
1079
|
+
ws.on("close", cleanupAgent);
|
|
1080
|
+
try {
|
|
1081
|
+
await agentServer.connect(transport2);
|
|
1082
|
+
} catch {
|
|
1083
|
+
cleanupAgent();
|
|
1084
|
+
try {
|
|
1085
|
+
ws.close();
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1056
1091
|
const transport = new WebSocketServerTransport(ws);
|
|
1057
1092
|
const client = new Client({ name: "mcp-page-bridge", version: MCP_PAGE_BRIDGE_VERSION }, { capabilities: {} });
|
|
1058
1093
|
const id = randomUUID();
|
|
@@ -1126,13 +1161,15 @@ async function createBridge(opts = {}) {
|
|
|
1126
1161
|
}
|
|
1127
1162
|
if (caps?.logging) {
|
|
1128
1163
|
client.setNotificationHandler(LoggingMessageNotificationSchema, (note) => {
|
|
1129
|
-
|
|
1130
|
-
()
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1164
|
+
for (const agentServer of agentServers) {
|
|
1165
|
+
void Promise.resolve().then(
|
|
1166
|
+
() => agentServer.sendLoggingMessage({
|
|
1167
|
+
...note.params,
|
|
1168
|
+
logger: `${provider.label}/${note.params.logger ?? "page"}`
|
|
1169
|
+
})
|
|
1170
|
+
).catch(() => {
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1136
1173
|
});
|
|
1137
1174
|
}
|
|
1138
1175
|
const cleanup = () => {
|
|
@@ -1163,9 +1200,12 @@ async function createBridge(opts = {}) {
|
|
|
1163
1200
|
}
|
|
1164
1201
|
await new Promise((resolve) => wss.close(() => resolve()));
|
|
1165
1202
|
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1203
|
+
for (const agentServer of [...agentServers]) {
|
|
1204
|
+
agentServers.delete(agentServer);
|
|
1205
|
+
try {
|
|
1206
|
+
await agentServer.close();
|
|
1207
|
+
} catch {
|
|
1208
|
+
}
|
|
1169
1209
|
}
|
|
1170
1210
|
}
|
|
1171
1211
|
};
|
package/dist/cli.js
CHANGED
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
DEFAULT_PORT,
|
|
4
4
|
MCP_PAGE_BRIDGE_VERSION,
|
|
5
5
|
createBridge
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RRWNJUKA.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
|
|
10
11
|
function parseFlag(argv, flag) {
|
|
11
12
|
const idx = argv.indexOf(flag);
|
|
12
13
|
if (idx >= 0 && argv[idx + 1]) return argv[idx + 1];
|
|
@@ -17,11 +18,56 @@ function parsePort(argv) {
|
|
|
17
18
|
const n = Number(fromFlag);
|
|
18
19
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_PORT;
|
|
19
20
|
}
|
|
21
|
+
function isAddrInUse(error) {
|
|
22
|
+
return !!error && typeof error === "object" && error.code === "EADDRINUSE";
|
|
23
|
+
}
|
|
24
|
+
async function connectProxy(port, token) {
|
|
25
|
+
const url = new URL(`ws://127.0.0.1:${port}/agent`);
|
|
26
|
+
if (token) url.searchParams.set("token", token);
|
|
27
|
+
const stdio = new StdioServerTransport();
|
|
28
|
+
const remote = new WebSocketClientTransport(url);
|
|
29
|
+
stdio.onmessage = (message) => {
|
|
30
|
+
void remote.send(message).catch((error) => stdio.onerror?.(error));
|
|
31
|
+
};
|
|
32
|
+
remote.onmessage = (message) => {
|
|
33
|
+
void stdio.send(message).catch((error) => remote.onerror?.(error));
|
|
34
|
+
};
|
|
35
|
+
stdio.onclose = () => {
|
|
36
|
+
void remote.close().catch(() => {
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
remote.onclose = () => {
|
|
40
|
+
void stdio.close().catch(() => {
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
remote.onerror = (error) => {
|
|
44
|
+
console.error(`[mcp-page-bridge] proxy websocket error: ${error.message}`);
|
|
45
|
+
};
|
|
46
|
+
await remote.start();
|
|
47
|
+
await stdio.start();
|
|
48
|
+
console.error(`[mcp-page-bridge] MCP stdio proxy ready \u2192 ws://127.0.0.1:${port}/agent`);
|
|
49
|
+
const shutdown = async () => {
|
|
50
|
+
await Promise.allSettled([remote.close(), stdio.close()]);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
};
|
|
53
|
+
process.on("SIGINT", shutdown);
|
|
54
|
+
process.on("SIGTERM", shutdown);
|
|
55
|
+
}
|
|
20
56
|
async function main() {
|
|
21
57
|
const argv = process.argv.slice(2);
|
|
22
58
|
const port = parsePort(argv);
|
|
23
59
|
const token = parseFlag(argv, "--token") ?? process.env.MCP_PAGE_BRIDGE_TOKEN;
|
|
24
|
-
|
|
60
|
+
let bridge;
|
|
61
|
+
try {
|
|
62
|
+
bridge = await createBridge({ port, token });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!isAddrInUse(error)) throw error;
|
|
65
|
+
console.error(
|
|
66
|
+
`[mcp-page-bridge] port ${port} is already in use; attaching this agent to the existing bridge`
|
|
67
|
+
);
|
|
68
|
+
await connectProxy(port, token);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
25
71
|
console.error(
|
|
26
72
|
`[mcp-page-bridge] v${MCP_PAGE_BRIDGE_VERSION} \u2014 ws://127.0.0.1:${bridge.port} \xB7 dashboard http://127.0.0.1:${bridge.port}/` + (token ? " (token required)" : "")
|
|
27
73
|
);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-page-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP bridge that aggregates live browser-page MCP servers and exposes them to a coding agent over stdio",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Eray Ates",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/rytsh/
|
|
10
|
+
"url": "git+https://github.com/rytsh/mcp-page-bridge.git"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"mcp",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typescript": "^5.7.3",
|
|
48
48
|
"vitest": "^2.1.8",
|
|
49
49
|
"zod": "^3.25.0",
|
|
50
|
-
"
|
|
50
|
+
"mcp-page-bridge-protocol": "0.1.4"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"start": "tsx src/cli.ts",
|