@mcp-b/webmcp-local-relay 5.1.0 → 6.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.
- package/README.md +25 -15
- package/dist/browser/embed.js +2 -2
- package/dist/browser/widget.html +1 -1
- package/dist/browser/widget.js +1 -1
- package/dist/cli.mjs +5 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +1 -1
- package/dist/{mcpRelayServer-DfGkigG5.mjs → mcpRelayServer-DEgebuO2.mjs} +88 -120
- package/dist/mcpRelayServer-DEgebuO2.mjs.map +1 -0
- package/package.json +2 -3
- package/dist/mcpRelayServer-DfGkigG5.mjs.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { EventEmitter } from "node:events";
|
|
3
4
|
import WebSocket, { WebSocketServer } from "ws";
|
|
@@ -5,16 +6,23 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { dirname, join } from "node:path";
|
|
7
8
|
import { CallToolRequestParamsSchema, CallToolResultSchema, ToolAnnotationsSchema, ToolSchema } from "@modelcontextprotocol/core";
|
|
8
|
-
import { z } from "zod/v4";
|
|
9
9
|
import { parseArgs } from "node:util";
|
|
10
10
|
import { execFile } from "node:child_process";
|
|
11
11
|
import { McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
13
13
|
|
|
14
14
|
//#region src/portStrategy.ts
|
|
15
|
+
function isTcpAddress(address) {
|
|
16
|
+
return address !== null && typeof address !== "string";
|
|
17
|
+
}
|
|
15
18
|
const DEFAULT_RELAY_PORT = 9333;
|
|
16
19
|
const DEFAULT_RELAY_PORT_RANGE_END = 9348;
|
|
17
20
|
const RELAY_PORT_CACHE_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
21
|
+
const PersistedRelayPortSchema = z.object({
|
|
22
|
+
port: z.number().int().min(1).max(65535),
|
|
23
|
+
host: z.string().min(1),
|
|
24
|
+
updatedAt: z.string()
|
|
25
|
+
});
|
|
18
26
|
function defaultRelayPortPersistPath() {
|
|
19
27
|
return join(homedir(), ".webmcp", "relay-port.json");
|
|
20
28
|
}
|
|
@@ -27,17 +35,13 @@ async function persistPort(port, path = defaultRelayPortPersistPath(), host = "1
|
|
|
27
35
|
await mkdir(dirname(path), { recursive: true });
|
|
28
36
|
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
29
37
|
}
|
|
30
|
-
async function readPersistedPort(path
|
|
38
|
+
async function readPersistedPort(path, expectedHost) {
|
|
31
39
|
try {
|
|
32
40
|
const raw = await readFile(path, "utf8");
|
|
33
|
-
const parsed = JSON.parse(raw);
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
const maxAgeMs = options.maxAgeMs ?? RELAY_PORT_CACHE_MAX_AGE_MS;
|
|
38
|
-
const now = options.now ?? Date.now();
|
|
39
|
-
const updatedAtMs = typeof parsed.updatedAt === "string" ? Date.parse(parsed.updatedAt) : NaN;
|
|
40
|
-
if (!Number.isFinite(updatedAtMs) || now - updatedAtMs > maxAgeMs) return null;
|
|
41
|
+
const parsed = PersistedRelayPortSchema.parse(JSON.parse(raw));
|
|
42
|
+
if (expectedHost && parsed.host !== expectedHost) return null;
|
|
43
|
+
const updatedAtMs = Date.parse(parsed.updatedAt);
|
|
44
|
+
if (!Number.isFinite(updatedAtMs) || Date.now() - updatedAtMs > RELAY_PORT_CACHE_MAX_AGE_MS) return null;
|
|
41
45
|
return parsed.port;
|
|
42
46
|
} catch {
|
|
43
47
|
return null;
|
|
@@ -50,7 +54,7 @@ async function buildPortCandidates(options) {
|
|
|
50
54
|
wasFixed: true,
|
|
51
55
|
fromCache: false
|
|
52
56
|
}];
|
|
53
|
-
const cachedPort = await readPersistedPort(persistPath,
|
|
57
|
+
const cachedPort = await readPersistedPort(persistPath, host);
|
|
54
58
|
const seen = /* @__PURE__ */ new Set();
|
|
55
59
|
const candidates = [];
|
|
56
60
|
const pushCandidate = (port, fromCache) => {
|
|
@@ -404,11 +408,10 @@ const RelayDescriptorSchema = z.object({
|
|
|
404
408
|
/**
|
|
405
409
|
* Schema for server hello messages sent immediately after WebSocket connect.
|
|
406
410
|
*/
|
|
407
|
-
const ServerHelloMessageSchema =
|
|
411
|
+
const ServerHelloMessageSchema = RelayDescriptorSchema.extend({
|
|
408
412
|
type: z.literal("server-hello"),
|
|
409
413
|
service: z.literal("webmcp-local-relay"),
|
|
410
|
-
version: z.literal(1)
|
|
411
|
-
...RelayDescriptorSchema.shape
|
|
414
|
+
version: z.literal(1)
|
|
412
415
|
});
|
|
413
416
|
/**
|
|
414
417
|
* Schema for acceptance of a browser source hello message.
|
|
@@ -507,6 +510,8 @@ const RELAY_INTERNAL_PROTOCOL = "webmcp-relay.v1";
|
|
|
507
510
|
const RELAY_SERVER_MESSAGE_TIMEOUT_MS = 750;
|
|
508
511
|
const HEARTBEAT_INTERVAL_MS = 15e3;
|
|
509
512
|
const HEARTBEAT_DEAD_THRESHOLD_MS = 25e3;
|
|
513
|
+
const RawMessageTypeSchema = z.object({ type: z.unknown().optional() });
|
|
514
|
+
const RelayProtocolTypeSchema = z.string().startsWith("relay/");
|
|
510
515
|
const SUPPORTED_SUBPROTOCOLS = new Set([
|
|
511
516
|
RELAY_BROWSER_PROTOCOL,
|
|
512
517
|
RELAY_DISCOVERY_PROTOCOL,
|
|
@@ -644,13 +649,14 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
644
649
|
await this.startAsServer(0);
|
|
645
650
|
return;
|
|
646
651
|
}
|
|
647
|
-
const
|
|
652
|
+
const strategyOptions = {
|
|
648
653
|
defaultPort: this.preferredPort,
|
|
649
|
-
...this.portExplicitlySet ? { fixedPort: this.preferredPort } : {},
|
|
650
654
|
host: this.host,
|
|
651
655
|
persistPath: this.persistPath,
|
|
652
656
|
rangeEnd: this.portRangeEnd
|
|
653
|
-
}
|
|
657
|
+
};
|
|
658
|
+
if (this.portExplicitlySet) strategyOptions.fixedPort = this.preferredPort;
|
|
659
|
+
const candidates = await buildPortCandidates(strategyOptions);
|
|
654
660
|
for (const candidate of candidates) try {
|
|
655
661
|
await this.startAsServer(candidate.port);
|
|
656
662
|
await this.persistSelectedPort();
|
|
@@ -666,8 +672,8 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
666
672
|
}
|
|
667
673
|
throw new Error(`No compatible relay port was available in the range ${this.preferredPort}-${this.portRangeEnd}.`);
|
|
668
674
|
}
|
|
669
|
-
isAddressInUseError(
|
|
670
|
-
return
|
|
675
|
+
isAddressInUseError(cause) {
|
|
676
|
+
return cause instanceof Error && ("code" in cause ? cause.code === "EADDRINUSE" : cause.message.includes("EADDRINUSE"));
|
|
671
677
|
}
|
|
672
678
|
async persistSelectedPort() {
|
|
673
679
|
try {
|
|
@@ -825,7 +831,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
825
831
|
this.wss = wss;
|
|
826
832
|
this._mode = "server";
|
|
827
833
|
const address = wss.address();
|
|
828
|
-
if (address
|
|
834
|
+
if (isTcpAddress(address)) this.desiredPort = address.port;
|
|
829
835
|
this.on("stateChanged", this.onStateChangedPushRelay);
|
|
830
836
|
}
|
|
831
837
|
buildServerHello() {
|
|
@@ -848,11 +854,10 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
848
854
|
* for a `relay/result` that never arrives.
|
|
849
855
|
*/
|
|
850
856
|
async invokeToolLocally(toolName, args, options) {
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
});
|
|
857
|
+
const invocation = { toolName };
|
|
858
|
+
if (options.sourceId !== void 0) invocation.sourceId = options.sourceId;
|
|
859
|
+
if (options.requestTabId !== void 0) invocation.requestTabId = options.requestTabId;
|
|
860
|
+
const resolved = this.registry.resolveInvocation(invocation);
|
|
856
861
|
if (!resolved) throw new Error(`No active browser source provides tool "${toolName}"`);
|
|
857
862
|
const socket = this.socketByConnectionId.get(resolved.connectionId);
|
|
858
863
|
if (!socket || socket.readyState !== WebSocket.OPEN) throw new Error(`Tool source ${resolved.connectionId} disconnected before invocation of "${toolName}"`);
|
|
@@ -894,7 +899,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
894
899
|
process.stderr.write(`[webmcp-local-relay] warn: invalid JSON from connection ${connectionId} (${err instanceof Error ? err.message : "parse error"}): ${preview}\n`);
|
|
895
900
|
return;
|
|
896
901
|
}
|
|
897
|
-
const typeField =
|
|
902
|
+
const typeField = RawMessageTypeSchema.safeParse(parsedJson).data?.type;
|
|
898
903
|
const socket = this.socketByConnectionId.get(connectionId);
|
|
899
904
|
if (!socket) return;
|
|
900
905
|
if (socket.protocol === RELAY_INTERNAL_PROTOCOL) {
|
|
@@ -903,7 +908,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
903
908
|
else process.stderr.write(`[webmcp-local-relay] warn: invalid relay message from ${connectionId}: ${relayMsg.error.message}\n`);
|
|
904
909
|
return;
|
|
905
910
|
}
|
|
906
|
-
if (
|
|
911
|
+
if (RelayProtocolTypeSchema.safeParse(typeField).success) {
|
|
907
912
|
process.stderr.write(`[webmcp-local-relay] warn: connection ${connectionId} used the relay protocol without negotiating it\n`);
|
|
908
913
|
socket.close(1008, "Relay protocol was not negotiated");
|
|
909
914
|
return;
|
|
@@ -914,9 +919,9 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
914
919
|
return;
|
|
915
920
|
}
|
|
916
921
|
this.registry.touchConnection(connectionId);
|
|
917
|
-
this.onBrowserClientMessage(connectionId, parsedMessage.data);
|
|
922
|
+
this.onBrowserClientMessage(connectionId, socket, parsedMessage.data);
|
|
918
923
|
}
|
|
919
|
-
onBrowserClientMessage(connectionId, message) {
|
|
924
|
+
onBrowserClientMessage(connectionId, socket, message) {
|
|
920
925
|
if (message.type !== "hello" && !this.browserClientConnectionIds.has(connectionId)) {
|
|
921
926
|
process.stderr.write(`[webmcp-local-relay] warn: connection ${connectionId} sent ${message.type} before hello, ignoring\n`);
|
|
922
927
|
return;
|
|
@@ -924,11 +929,10 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
924
929
|
switch (message.type) {
|
|
925
930
|
case "hello":
|
|
926
931
|
try {
|
|
927
|
-
const socket = this.socketByConnectionId.get(connectionId);
|
|
928
932
|
const origin = this.requestOriginByConnectionId.get(connectionId) ?? message.origin;
|
|
929
933
|
if (!this.isHostOriginAllowed(origin)) {
|
|
930
934
|
process.stderr.write(`[webmcp-local-relay] warn: rejecting source ${connectionId} with disallowed host origin: ${origin ?? "missing"}\n`);
|
|
931
|
-
|
|
935
|
+
this.sendHelloRejected(socket, {
|
|
932
936
|
type: "hello/rejected",
|
|
933
937
|
reason: "host-origin-not-allowed",
|
|
934
938
|
message: "Host page origin is not allowed by this relay."
|
|
@@ -940,7 +944,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
940
944
|
origin
|
|
941
945
|
});
|
|
942
946
|
this.browserClientConnectionIds.add(connectionId);
|
|
943
|
-
|
|
947
|
+
this.sendHelloAccepted(socket, { type: "hello/accepted" });
|
|
944
948
|
this.emit("stateChanged");
|
|
945
949
|
} catch (err) {
|
|
946
950
|
process.stderr.write(`[webmcp-local-relay] error: failed to process hello from connection ${connectionId}: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
@@ -953,7 +957,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
953
957
|
this.emit("stateChanged");
|
|
954
958
|
} catch (err) {
|
|
955
959
|
process.stderr.write(`[webmcp-local-relay] error: failed to register tools for connection ${connectionId}: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
956
|
-
|
|
960
|
+
socket.close(1008, "Invalid tool list");
|
|
957
961
|
}
|
|
958
962
|
break;
|
|
959
963
|
case "result": {
|
|
@@ -1221,7 +1225,7 @@ var RelayBridgeServer = class extends EventEmitter {
|
|
|
1221
1225
|
}
|
|
1222
1226
|
const message = RelayServerToClientMessageSchema.safeParse(parsed);
|
|
1223
1227
|
if (!message.success) {
|
|
1224
|
-
const typeField =
|
|
1228
|
+
const typeField = RawMessageTypeSchema.safeParse(parsed).data?.type ?? "unknown";
|
|
1225
1229
|
process.stderr.write(`[webmcp-local-relay] warn: invalid relay server message (type=${typeField}): ${message.error.message}\n`);
|
|
1226
1230
|
return null;
|
|
1227
1231
|
}
|
|
@@ -1424,18 +1428,19 @@ function parseCliOptions(argv) {
|
|
|
1424
1428
|
const maxPayloadBytes = parsePositiveInteger("max-payload", values["max-payload"]);
|
|
1425
1429
|
const allowedOrigins = (values["widget-origin"] ?? values["allowed-origin"] ?? values["ws-origin"])?.split(",").map((origin) => origin.trim()).filter(Boolean) ?? ["*"];
|
|
1426
1430
|
if (allowedOrigins.length === 0) throw new Error("--widget-origin must include at least one origin.");
|
|
1427
|
-
|
|
1431
|
+
const options = {
|
|
1428
1432
|
allowedOrigins,
|
|
1429
1433
|
help: values.help ?? false,
|
|
1430
1434
|
host: values.host ?? "127.0.0.1",
|
|
1431
1435
|
port,
|
|
1432
|
-
portExplicitlySet: values.port !== void 0
|
|
1433
|
-
...values.label === void 0 ? {} : { label: values.label },
|
|
1434
|
-
...invokeTimeoutMs === void 0 ? {} : { invokeTimeoutMs },
|
|
1435
|
-
...maxPayloadBytes === void 0 ? {} : { maxPayloadBytes },
|
|
1436
|
-
...values["relay-id"] === void 0 ? {} : { relayId: values["relay-id"] },
|
|
1437
|
-
...values.workspace === void 0 ? {} : { workspace: values.workspace }
|
|
1436
|
+
portExplicitlySet: values.port !== void 0
|
|
1438
1437
|
};
|
|
1438
|
+
if (values.label !== void 0) options.label = values.label;
|
|
1439
|
+
if (invokeTimeoutMs !== void 0) options.invokeTimeoutMs = invokeTimeoutMs;
|
|
1440
|
+
if (maxPayloadBytes !== void 0) options.maxPayloadBytes = maxPayloadBytes;
|
|
1441
|
+
if (values["relay-id"] !== void 0) options.relayId = values["relay-id"];
|
|
1442
|
+
if (values.workspace !== void 0) options.workspace = values.workspace;
|
|
1443
|
+
return options;
|
|
1439
1444
|
}
|
|
1440
1445
|
/**
|
|
1441
1446
|
* Prints CLI usage to stderr.
|
|
@@ -1465,6 +1470,22 @@ function printHelp() {
|
|
|
1465
1470
|
|
|
1466
1471
|
//#endregion
|
|
1467
1472
|
//#region src/mcpRelayServer.ts
|
|
1473
|
+
const JsonSchemaObjectSchema = z.record(z.string(), z.unknown());
|
|
1474
|
+
function textResult(text) {
|
|
1475
|
+
return { content: [{
|
|
1476
|
+
type: "text",
|
|
1477
|
+
text
|
|
1478
|
+
}] };
|
|
1479
|
+
}
|
|
1480
|
+
function errorResult(text) {
|
|
1481
|
+
return {
|
|
1482
|
+
content: [{
|
|
1483
|
+
type: "text",
|
|
1484
|
+
text
|
|
1485
|
+
}],
|
|
1486
|
+
isError: true
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1468
1489
|
/**
|
|
1469
1490
|
* MCP server facade that exposes browser-relayed tools over MCP transport.
|
|
1470
1491
|
*/
|
|
@@ -1474,6 +1495,7 @@ var LocalRelayMcpServer = class {
|
|
|
1474
1495
|
*/
|
|
1475
1496
|
bridge;
|
|
1476
1497
|
mcpServer;
|
|
1498
|
+
launchBrowser;
|
|
1477
1499
|
dynamicTools = /* @__PURE__ */ new Map();
|
|
1478
1500
|
syncDebounceTimer = null;
|
|
1479
1501
|
connected = false;
|
|
@@ -1482,6 +1504,7 @@ var LocalRelayMcpServer = class {
|
|
|
1482
1504
|
*/
|
|
1483
1505
|
constructor(options = {}) {
|
|
1484
1506
|
this.bridge = options.bridge ?? new RelayBridgeServer(options.bridgeOptions);
|
|
1507
|
+
this.launchBrowser = options.launchBrowser ?? execFile;
|
|
1485
1508
|
this.mcpServer = new McpServer({
|
|
1486
1509
|
name: options.serverName ?? "webmcp-local-relay",
|
|
1487
1510
|
version: options.serverVersion ?? "0.0.0"
|
|
@@ -1557,10 +1580,10 @@ var LocalRelayMcpServer = class {
|
|
|
1557
1580
|
const clientMode = this.bridge.mode === "client";
|
|
1558
1581
|
const sources = clientMode ? this.bridge.listSourcesFromRelay() : this.bridge.registry.listSources();
|
|
1559
1582
|
const info = {
|
|
1560
|
-
...clientMode ? { mode: "client" } : {},
|
|
1561
1583
|
count: sources.length,
|
|
1562
1584
|
sources
|
|
1563
1585
|
};
|
|
1586
|
+
if (clientMode) info.mode = "client";
|
|
1564
1587
|
return {
|
|
1565
1588
|
content: [{
|
|
1566
1589
|
type: "text",
|
|
@@ -1598,76 +1621,26 @@ var LocalRelayMcpServer = class {
|
|
|
1598
1621
|
}),
|
|
1599
1622
|
annotations: { readOnlyHint: false }
|
|
1600
1623
|
}, async ({ url, refresh }) => {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
} catch {
|
|
1605
|
-
return {
|
|
1606
|
-
content: [{
|
|
1607
|
-
type: "text",
|
|
1608
|
-
text: `Invalid URL: ${url}`
|
|
1609
|
-
}],
|
|
1610
|
-
isError: true
|
|
1611
|
-
};
|
|
1612
|
-
}
|
|
1613
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return {
|
|
1614
|
-
content: [{
|
|
1615
|
-
type: "text",
|
|
1616
|
-
text: `Only http: and https: URLs are allowed. Got: ${parsed.protocol}`
|
|
1617
|
-
}],
|
|
1618
|
-
isError: true
|
|
1619
|
-
};
|
|
1624
|
+
const parsed = URL.parse(url);
|
|
1625
|
+
if (!parsed) return errorResult(`Invalid URL: ${url}`);
|
|
1626
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return errorResult(`Only http: and https: URLs are allowed. Got: ${parsed.protocol}`);
|
|
1620
1627
|
const existing = this.bridge.mode === "server" ? this.bridge.registry.listSources().find((source) => source.url && URL.parse(source.url)?.origin === parsed.origin) : void 0;
|
|
1621
1628
|
if (refresh) {
|
|
1622
|
-
if (this.bridge.mode === "client") return
|
|
1623
|
-
|
|
1624
|
-
type: "text",
|
|
1625
|
-
text: "Refresh is not supported in client mode. Only the server relay can reload sources."
|
|
1626
|
-
}],
|
|
1627
|
-
isError: true
|
|
1628
|
-
};
|
|
1629
|
-
if (!existing) return {
|
|
1630
|
-
content: [{
|
|
1631
|
-
type: "text",
|
|
1632
|
-
text: `No connected source matches origin ${parsed.origin}. The page may not be open or connected.`
|
|
1633
|
-
}],
|
|
1634
|
-
isError: true
|
|
1635
|
-
};
|
|
1629
|
+
if (this.bridge.mode === "client") return errorResult("Refresh is not supported in client mode. Only the server relay can reload sources.");
|
|
1630
|
+
if (!existing) return errorResult(`No connected source matches origin ${parsed.origin}. The page may not be open or connected.`);
|
|
1636
1631
|
try {
|
|
1637
1632
|
this.bridge.reloadSource(existing.sourceId);
|
|
1638
|
-
return { content: [{
|
|
1639
|
-
type: "text",
|
|
1640
|
-
text: `Reload sent to source ${existing.sourceId} (${existing.url ?? existing.origin}).`
|
|
1641
|
-
}] };
|
|
1642
1633
|
} catch (err) {
|
|
1643
|
-
return {
|
|
1644
|
-
content: [{
|
|
1645
|
-
type: "text",
|
|
1646
|
-
text: `Failed to reload source: ${err instanceof Error ? err.message : String(err)}`
|
|
1647
|
-
}],
|
|
1648
|
-
isError: true
|
|
1649
|
-
};
|
|
1634
|
+
return errorResult(`Failed to reload source: ${err instanceof Error ? err.message : String(err)}`);
|
|
1650
1635
|
}
|
|
1636
|
+
return textResult(`Reload sent to source ${existing.sourceId} (${existing.url ?? existing.origin}).`);
|
|
1651
1637
|
}
|
|
1652
1638
|
try {
|
|
1653
1639
|
await this.openInBrowser(url);
|
|
1654
1640
|
} catch (err) {
|
|
1655
|
-
return {
|
|
1656
|
-
content: [{
|
|
1657
|
-
type: "text",
|
|
1658
|
-
text: `Failed to open browser: ${err instanceof Error ? err.message : String(err)}`
|
|
1659
|
-
}],
|
|
1660
|
-
isError: true
|
|
1661
|
-
};
|
|
1641
|
+
return errorResult(`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
1662
1642
|
}
|
|
1663
|
-
|
|
1664
|
-
type: "text",
|
|
1665
|
-
text: `Opened ${url} in the default browser. Note: a source from ${existing.url ?? existing.origin} is already connected.`
|
|
1666
|
-
}] };
|
|
1667
|
-
return { content: [{
|
|
1668
|
-
type: "text",
|
|
1669
|
-
text: `Opened ${url} in the default browser.`
|
|
1670
|
-
}] };
|
|
1643
|
+
return textResult(existing ? `Opened ${url} in the default browser. Note: a source from ${existing.url ?? existing.origin} is already connected.` : `Opened ${url} in the default browser.`);
|
|
1671
1644
|
});
|
|
1672
1645
|
}
|
|
1673
1646
|
/**
|
|
@@ -1680,7 +1653,7 @@ var LocalRelayMcpServer = class {
|
|
|
1680
1653
|
const safeUrl = new URL(url).href;
|
|
1681
1654
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
|
|
1682
1655
|
return new Promise((resolve, reject) => {
|
|
1683
|
-
|
|
1656
|
+
this.launchBrowser(command, [safeUrl], (err) => {
|
|
1684
1657
|
if (err) reject(err);
|
|
1685
1658
|
else resolve();
|
|
1686
1659
|
});
|
|
@@ -1770,30 +1743,25 @@ var LocalRelayMcpServer = class {
|
|
|
1770
1743
|
* Registers a single dynamic tool and returns a removal handle.
|
|
1771
1744
|
*/
|
|
1772
1745
|
registerDynamicTool(tool) {
|
|
1773
|
-
const inputSchema = fromJsonSchema(tool.inputSchema);
|
|
1774
|
-
const outputSchema = tool.outputSchema ? fromJsonSchema(tool.outputSchema) : void 0;
|
|
1775
|
-
|
|
1776
|
-
...tool.title !== void 0 ? { title: tool.title } : {},
|
|
1746
|
+
const inputSchema = fromJsonSchema(JsonSchemaObjectSchema.parse(tool.inputSchema));
|
|
1747
|
+
const outputSchema = tool.outputSchema !== void 0 ? fromJsonSchema(JsonSchemaObjectSchema.parse(tool.outputSchema)) : void 0;
|
|
1748
|
+
const registration = {
|
|
1777
1749
|
description: this.dynamicToolDescription(tool),
|
|
1778
|
-
inputSchema
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1750
|
+
inputSchema
|
|
1751
|
+
};
|
|
1752
|
+
if (tool.title !== void 0) registration.title = tool.title;
|
|
1753
|
+
if (outputSchema) registration.outputSchema = outputSchema;
|
|
1754
|
+
if (tool.annotations) registration.annotations = tool.annotations;
|
|
1755
|
+
if (tool.icons) registration.icons = tool.icons;
|
|
1756
|
+
if (tool._meta) registration._meta = tool._meta;
|
|
1757
|
+
return this.mcpServer.registerTool(tool.name, registration, async (args) => {
|
|
1784
1758
|
try {
|
|
1785
1759
|
return await this.bridge.invokeTool(tool.name, args);
|
|
1786
1760
|
} catch (err) {
|
|
1787
1761
|
const message = err instanceof Error ? err.message : String(err);
|
|
1788
1762
|
const details = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
1789
1763
|
process.stderr.write(`[webmcp-local-relay] error: dynamic tool "${tool.name}" invocation failed: ${details}\n`);
|
|
1790
|
-
return {
|
|
1791
|
-
content: [{
|
|
1792
|
-
type: "text",
|
|
1793
|
-
text: `Failed to invoke relayed tool "${tool.name}": ${message}`
|
|
1794
|
-
}],
|
|
1795
|
-
isError: true
|
|
1796
|
-
};
|
|
1764
|
+
return errorResult(`Failed to invoke relayed tool "${tool.name}": ${message}`);
|
|
1797
1765
|
}
|
|
1798
1766
|
});
|
|
1799
1767
|
}
|
|
@@ -1832,4 +1800,4 @@ var LocalRelayMcpServer = class {
|
|
|
1832
1800
|
|
|
1833
1801
|
//#endregion
|
|
1834
1802
|
export { NormalizedToolSchema as C, normalizeInboundTool as D, ToolSchema as E, InboundToolSchema as S, ToolAnnotationsSchema as T, extractSanitizedDomain as _, BrowserToRelayMessageSchema as a, CallToolResultSchema as b, RelayHelloAcceptedMessageSchema as c, RelaySourceInfoSchema as d, RelayToBrowserMessageSchema as f, buildPublicToolName as g, RelayRegistry as h, RelayBridgeServer as i, RelayHelloRejectedMessageSchema as l, HelloRequiredError as m, parseCliOptions as n, RelayClientToServerMessageSchema as o, ServerHelloMessageSchema as p, printHelp as r, RelayDescriptorSchema as s, LocalRelayMcpServer as t, RelayServerToClientMessageSchema as u, sanitizeName as v, RelayInvokeArgsSchema as w, DEFAULT_TOOL_INPUT_SCHEMA as x, CallToolRequestParamsSchema as y };
|
|
1835
|
-
//# sourceMappingURL=mcpRelayServer-
|
|
1803
|
+
//# sourceMappingURL=mcpRelayServer-DEgebuO2.mjs.map
|