mcp-page-bridge 0.1.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createBridge
4
- } from "./chunk-7A7AECYZ.js";
4
+ } from "./chunk-RRWNJUKA.js";
5
5
  export {
6
6
  createBridge
7
7
  };
@@ -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.3";
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 server = new Server(
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" ? () => server.sendToolListChanged() : kind === "prompts" ? () => server.sendPromptListChanged() : () => server.sendResourceListChanged();
879
- void Promise.resolve().then(send).catch(() => {
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
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: exposedTools() }));
890
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
891
- const name = req.params.name;
892
- if (name === META_LIST_CLIENTS) {
893
- return { content: [{ type: "text", text: JSON.stringify(providerSummary(), null, 2) }] };
894
- }
895
- const route = toolRoutes.get(name);
896
- const provider = route && providers.get(route.providerId);
897
- if (!route || !provider) {
898
- return { content: [{ type: "text", text: `Unknown or disconnected tool: ${name}` }], isError: true };
899
- }
900
- try {
901
- const result = await provider.client.callTool({
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
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({
924
- resources: exposedResources()
925
- }));
926
- server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
927
- const providerId = resourceRoutes.get(req.params.uri);
928
- const provider = providerId ? providers.get(providerId) : void 0;
929
- if (!provider) throw new Error(`Unknown or disconnected resource: ${req.params.uri}`);
930
- return provider.client.readResource({ uri: req.params.uri });
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
- void Promise.resolve().then(
1130
- () => server.sendLoggingMessage({
1131
- ...note.params,
1132
- logger: `${provider.label}/${note.params.logger ?? "page"}`
1133
- })
1134
- ).catch(() => {
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
- try {
1167
- await server.close();
1168
- } catch {
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-7A7AECYZ.js";
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
- const bridge = await createBridge({ port, token });
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,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-page-bridge",
3
- "version": "0.1.3",
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",
@@ -47,7 +47,7 @@
47
47
  "typescript": "^5.7.3",
48
48
  "vitest": "^2.1.8",
49
49
  "zod": "^3.25.0",
50
- "mcp-page-bridge-protocol": "0.1.3"
50
+ "mcp-page-bridge-protocol": "0.1.4"
51
51
  },
52
52
  "scripts": {
53
53
  "start": "tsx src/cli.ts",