mcp-squared 0.3.0 → 0.3.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 +15 -0
- package/dist/index.js +298 -216
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,6 +27,21 @@ npx mcp-squared --help
|
|
|
27
27
|
npm exec --yes mcp-squared -- --help
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
If you see `Cannot find module '@/version.js'`, check for a stale global install:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
which mcp-squared
|
|
34
|
+
npm ls -g --depth=0 mcp-squared
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`mcp-squared@0.3.0` shipped unresolved `@/...` aliases in `dist/index.js`. Remove the old global package and re-run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm uninstall -g mcp-squared
|
|
41
|
+
hash -r
|
|
42
|
+
npx --yes mcp-squared@latest --help
|
|
43
|
+
```
|
|
44
|
+
|
|
30
45
|
### Install globally
|
|
31
46
|
|
|
32
47
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
-
var __returnValue = (v) => v;
|
|
5
|
-
function __exportSetter(name, newValue) {
|
|
6
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
-
}
|
|
8
4
|
var __export = (target, all) => {
|
|
9
5
|
for (var name in all)
|
|
10
6
|
__defProp(target, name, {
|
|
11
7
|
get: all[name],
|
|
12
8
|
enumerable: true,
|
|
13
9
|
configurable: true,
|
|
14
|
-
set:
|
|
10
|
+
set: (newValue) => all[name] = () => newValue
|
|
15
11
|
});
|
|
16
12
|
};
|
|
17
13
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -405,7 +401,6 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
405
401
|
import { UnauthorizedError as UnauthorizedError4 } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
406
402
|
import { Client as Client4 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
407
403
|
import { StreamableHTTPClientTransport as StreamableHTTPClientTransport4 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
408
|
-
import { VERSION as VERSION4 } from "@/version.js";
|
|
409
404
|
|
|
410
405
|
// src/cli/index.ts
|
|
411
406
|
function isValidSecurityProfile(value) {
|
|
@@ -1433,173 +1428,9 @@ function computeConfigHash(config) {
|
|
|
1433
1428
|
}
|
|
1434
1429
|
|
|
1435
1430
|
// src/daemon/proxy.ts
|
|
1431
|
+
init_paths();
|
|
1436
1432
|
import { spawn } from "child_process";
|
|
1437
1433
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1438
|
-
import { getDaemonSocketPath as getDaemonSocketPath2 } from "@/config/paths.js";
|
|
1439
|
-
import {
|
|
1440
|
-
loadLiveDaemonRegistry
|
|
1441
|
-
} from "@/daemon/registry.js";
|
|
1442
|
-
import { SocketClientTransport } from "@/daemon/transport.js";
|
|
1443
|
-
var DEFAULT_STARTUP_TIMEOUT_MS = 5000;
|
|
1444
|
-
var HEARTBEAT_INTERVAL_MS = 5000;
|
|
1445
|
-
function sleep(ms) {
|
|
1446
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1447
|
-
}
|
|
1448
|
-
async function waitForDaemon(timeoutMs, configHash) {
|
|
1449
|
-
const start = Date.now();
|
|
1450
|
-
while (Date.now() - start < timeoutMs) {
|
|
1451
|
-
const entry = await loadLiveDaemonRegistry(configHash);
|
|
1452
|
-
if (entry) {
|
|
1453
|
-
return entry;
|
|
1454
|
-
}
|
|
1455
|
-
await sleep(100);
|
|
1456
|
-
}
|
|
1457
|
-
return null;
|
|
1458
|
-
}
|
|
1459
|
-
function spawnDaemonProcess(sharedSecret) {
|
|
1460
|
-
const execPath = process.execPath;
|
|
1461
|
-
const scriptPath = process.argv[1];
|
|
1462
|
-
const args = scriptPath ? [scriptPath, "daemon"] : ["daemon"];
|
|
1463
|
-
const child = spawn(execPath, args, {
|
|
1464
|
-
detached: true,
|
|
1465
|
-
stdio: "ignore",
|
|
1466
|
-
env: {
|
|
1467
|
-
...process.env,
|
|
1468
|
-
...sharedSecret ? { MCP_SQUARED_DAEMON_SECRET: sharedSecret } : {}
|
|
1469
|
-
}
|
|
1470
|
-
});
|
|
1471
|
-
child.unref();
|
|
1472
|
-
}
|
|
1473
|
-
async function createProxyBridge(options) {
|
|
1474
|
-
const spawnDaemon = options.spawnDaemon ?? spawnDaemonProcess;
|
|
1475
|
-
let endpoint = options.endpoint;
|
|
1476
|
-
let sharedSecret = options.sharedSecret?.trim();
|
|
1477
|
-
let sessionId = null;
|
|
1478
|
-
let heartbeatTimer = null;
|
|
1479
|
-
let isOwner = false;
|
|
1480
|
-
let daemonClosed = false;
|
|
1481
|
-
let stdioClosed = false;
|
|
1482
|
-
const debug = options.debug ?? process.env["MCP_SQUARED_PROXY_DEBUG"] === "1";
|
|
1483
|
-
if (!endpoint) {
|
|
1484
|
-
const registry = await loadLiveDaemonRegistry(options.configHash);
|
|
1485
|
-
if (registry) {
|
|
1486
|
-
endpoint = registry.endpoint;
|
|
1487
|
-
sharedSecret ??= registry.sharedSecret;
|
|
1488
|
-
} else if (!options.noSpawn) {
|
|
1489
|
-
spawnDaemon(sharedSecret);
|
|
1490
|
-
const entry = await waitForDaemon(DEFAULT_STARTUP_TIMEOUT_MS, options.configHash);
|
|
1491
|
-
if (!entry) {
|
|
1492
|
-
throw new Error("Timed out waiting for daemon to start");
|
|
1493
|
-
}
|
|
1494
|
-
endpoint = entry.endpoint;
|
|
1495
|
-
sharedSecret ??= entry.sharedSecret;
|
|
1496
|
-
} else if (options.configHash) {
|
|
1497
|
-
endpoint = getDaemonSocketPath2(options.configHash);
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
if (!endpoint) {
|
|
1501
|
-
throw new Error("Daemon endpoint not available");
|
|
1502
|
-
}
|
|
1503
|
-
const transportOptions = {
|
|
1504
|
-
endpoint
|
|
1505
|
-
};
|
|
1506
|
-
if (options.timeoutMs !== undefined) {
|
|
1507
|
-
transportOptions.timeoutMs = options.timeoutMs;
|
|
1508
|
-
}
|
|
1509
|
-
const daemonTransport = new SocketClientTransport(transportOptions);
|
|
1510
|
-
const stdioTransport = options.stdioTransport;
|
|
1511
|
-
const closeDaemon = async (sendGoodbye) => {
|
|
1512
|
-
if (daemonClosed) {
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
daemonClosed = true;
|
|
1516
|
-
if (sendGoodbye && sessionId) {
|
|
1517
|
-
await daemonTransport.sendControl({ type: "goodbye", sessionId }).catch(() => {});
|
|
1518
|
-
}
|
|
1519
|
-
await daemonTransport.close().catch(() => {});
|
|
1520
|
-
};
|
|
1521
|
-
daemonTransport.oncontrol = (message) => {
|
|
1522
|
-
switch (message.type) {
|
|
1523
|
-
case "helloAck":
|
|
1524
|
-
sessionId = message.sessionId;
|
|
1525
|
-
isOwner = message.isOwner;
|
|
1526
|
-
if (debug) {
|
|
1527
|
-
console.error(`[proxy] session ${message.sessionId} owner=${message.isOwner}`);
|
|
1528
|
-
}
|
|
1529
|
-
break;
|
|
1530
|
-
case "ownerChanged":
|
|
1531
|
-
isOwner = message.ownerSessionId === sessionId;
|
|
1532
|
-
if (debug) {
|
|
1533
|
-
console.error(`[proxy] owner changed: ${message.ownerSessionId} (isOwner=${isOwner})`);
|
|
1534
|
-
}
|
|
1535
|
-
break;
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
daemonTransport.onmessage = (message) => {
|
|
1539
|
-
stdioTransport.send(message);
|
|
1540
|
-
};
|
|
1541
|
-
daemonTransport.onclose = () => {
|
|
1542
|
-
daemonClosed = true;
|
|
1543
|
-
if (heartbeatTimer) {
|
|
1544
|
-
clearInterval(heartbeatTimer);
|
|
1545
|
-
heartbeatTimer = null;
|
|
1546
|
-
}
|
|
1547
|
-
stdioTransport.close();
|
|
1548
|
-
};
|
|
1549
|
-
daemonTransport.onerror = (error) => {
|
|
1550
|
-
console.error(`Daemon transport error: ${error.message}`);
|
|
1551
|
-
};
|
|
1552
|
-
stdioTransport.onmessage = (message) => {
|
|
1553
|
-
daemonTransport.send(message);
|
|
1554
|
-
};
|
|
1555
|
-
stdioTransport.onclose = () => {
|
|
1556
|
-
stdioClosed = true;
|
|
1557
|
-
if (heartbeatTimer) {
|
|
1558
|
-
clearInterval(heartbeatTimer);
|
|
1559
|
-
heartbeatTimer = null;
|
|
1560
|
-
}
|
|
1561
|
-
closeDaemon(true);
|
|
1562
|
-
};
|
|
1563
|
-
stdioTransport.onerror = (error) => {
|
|
1564
|
-
console.error(`Stdio transport error: ${error.message}`);
|
|
1565
|
-
};
|
|
1566
|
-
await daemonTransport.start();
|
|
1567
|
-
const launcherHint = process.env["MCP_SQUARED_LAUNCHER"] ?? process.env["MCP_CLIENT_NAME"] ?? process.env["MCP_SQUARED_AGENT"];
|
|
1568
|
-
const clientId = launcherHint ? `${launcherHint}-${process.pid}` : `proxy-${process.pid}`;
|
|
1569
|
-
daemonTransport.sendControl({
|
|
1570
|
-
type: "hello",
|
|
1571
|
-
clientId,
|
|
1572
|
-
...sharedSecret ? { sharedSecret } : {}
|
|
1573
|
-
});
|
|
1574
|
-
heartbeatTimer = setInterval(() => {
|
|
1575
|
-
if (!sessionId) {
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
daemonTransport.sendControl({
|
|
1579
|
-
type: "heartbeat",
|
|
1580
|
-
sessionId
|
|
1581
|
-
});
|
|
1582
|
-
}, options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS);
|
|
1583
|
-
await stdioTransport.start();
|
|
1584
|
-
return {
|
|
1585
|
-
stop: async () => {
|
|
1586
|
-
if (heartbeatTimer) {
|
|
1587
|
-
clearInterval(heartbeatTimer);
|
|
1588
|
-
heartbeatTimer = null;
|
|
1589
|
-
}
|
|
1590
|
-
await closeDaemon(true);
|
|
1591
|
-
if (!stdioClosed) {
|
|
1592
|
-
await stdioTransport.close();
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
async function runProxy(options = {}) {
|
|
1598
|
-
return createProxyBridge({
|
|
1599
|
-
...options,
|
|
1600
|
-
stdioTransport: new StdioServerTransport
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
1434
|
|
|
1604
1435
|
// src/daemon/registry.ts
|
|
1605
1436
|
init_paths();
|
|
@@ -1698,7 +1529,7 @@ async function isDaemonAlive(entry, timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS2) {
|
|
|
1698
1529
|
}
|
|
1699
1530
|
return canConnect2(entry.endpoint, timeoutMs);
|
|
1700
1531
|
}
|
|
1701
|
-
async function
|
|
1532
|
+
async function loadLiveDaemonRegistry(configHash) {
|
|
1702
1533
|
const entry = readDaemonRegistry(configHash);
|
|
1703
1534
|
if (!entry) {
|
|
1704
1535
|
return null;
|
|
@@ -1711,20 +1542,8 @@ async function loadLiveDaemonRegistry2(configHash) {
|
|
|
1711
1542
|
return entry;
|
|
1712
1543
|
}
|
|
1713
1544
|
|
|
1714
|
-
// src/daemon/server.ts
|
|
1715
|
-
init_paths();
|
|
1716
|
-
import { randomUUID } from "crypto";
|
|
1717
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
1718
|
-
import {
|
|
1719
|
-
connect as connect3,
|
|
1720
|
-
createServer,
|
|
1721
|
-
isIPv4,
|
|
1722
|
-
isIPv6
|
|
1723
|
-
} from "net";
|
|
1724
|
-
import { dirname as dirname2 } from "path";
|
|
1725
|
-
import { VERSION } from "@/version.js";
|
|
1726
|
-
|
|
1727
1545
|
// src/daemon/transport.ts
|
|
1546
|
+
import { connect as connect3 } from "net";
|
|
1728
1547
|
var HEADER_LENGTH = 4;
|
|
1729
1548
|
function encodeFrame(message) {
|
|
1730
1549
|
const payload = Buffer.from(JSON.stringify(message), "utf8");
|
|
@@ -1822,10 +1641,275 @@ class SocketServerTransport extends BaseSocketTransport {
|
|
|
1822
1641
|
this.socket = socket;
|
|
1823
1642
|
}
|
|
1824
1643
|
}
|
|
1644
|
+
function isTcpEndpoint3(endpoint) {
|
|
1645
|
+
return endpoint.startsWith("tcp://");
|
|
1646
|
+
}
|
|
1647
|
+
function parseTcpEndpoint3(endpoint) {
|
|
1648
|
+
const url = new URL(endpoint);
|
|
1649
|
+
if (url.protocol !== "tcp:") {
|
|
1650
|
+
throw new Error(`Invalid TCP endpoint protocol: ${url.protocol}`);
|
|
1651
|
+
}
|
|
1652
|
+
const host = url.hostname;
|
|
1653
|
+
const port = Number.parseInt(url.port, 10);
|
|
1654
|
+
if (!host || Number.isNaN(port)) {
|
|
1655
|
+
throw new Error(`Invalid TCP endpoint: ${endpoint}`);
|
|
1656
|
+
}
|
|
1657
|
+
return { host, port };
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
class SocketClientTransport extends BaseSocketTransport {
|
|
1661
|
+
endpoint;
|
|
1662
|
+
timeoutMs;
|
|
1663
|
+
constructor(options) {
|
|
1664
|
+
super();
|
|
1665
|
+
this.endpoint = options.endpoint;
|
|
1666
|
+
this.timeoutMs = options.timeoutMs ?? 5000;
|
|
1667
|
+
}
|
|
1668
|
+
async start() {
|
|
1669
|
+
if (this.started) {
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
await new Promise((resolve2, reject) => {
|
|
1673
|
+
const socket = isTcpEndpoint3(this.endpoint) ? connect3(parseTcpEndpoint3(this.endpoint)) : connect3(this.endpoint);
|
|
1674
|
+
this.socket = socket;
|
|
1675
|
+
const timeoutId = setTimeout(() => {
|
|
1676
|
+
socket.destroy();
|
|
1677
|
+
reject(new Error(`Connection timeout after ${this.timeoutMs}ms`));
|
|
1678
|
+
}, this.timeoutMs);
|
|
1679
|
+
socket.once("connect", () => {
|
|
1680
|
+
clearTimeout(timeoutId);
|
|
1681
|
+
this.started = true;
|
|
1682
|
+
this.attachHandlers();
|
|
1683
|
+
resolve2();
|
|
1684
|
+
});
|
|
1685
|
+
socket.once("error", (error) => {
|
|
1686
|
+
clearTimeout(timeoutId);
|
|
1687
|
+
reject(error);
|
|
1688
|
+
});
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/daemon/proxy.ts
|
|
1694
|
+
var DEFAULT_STARTUP_TIMEOUT_MS = 5000;
|
|
1695
|
+
var HEARTBEAT_INTERVAL_MS = 5000;
|
|
1696
|
+
function sleep(ms) {
|
|
1697
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1698
|
+
}
|
|
1699
|
+
async function waitForDaemon(timeoutMs, configHash) {
|
|
1700
|
+
const start = Date.now();
|
|
1701
|
+
while (Date.now() - start < timeoutMs) {
|
|
1702
|
+
const entry = await loadLiveDaemonRegistry(configHash);
|
|
1703
|
+
if (entry) {
|
|
1704
|
+
return entry;
|
|
1705
|
+
}
|
|
1706
|
+
await sleep(100);
|
|
1707
|
+
}
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
function spawnDaemonProcess(sharedSecret) {
|
|
1711
|
+
const execPath = process.execPath;
|
|
1712
|
+
const scriptPath = process.argv[1];
|
|
1713
|
+
const args = scriptPath ? [scriptPath, "daemon"] : ["daemon"];
|
|
1714
|
+
const child = spawn(execPath, args, {
|
|
1715
|
+
detached: true,
|
|
1716
|
+
stdio: "ignore",
|
|
1717
|
+
env: {
|
|
1718
|
+
...process.env,
|
|
1719
|
+
...sharedSecret ? { MCP_SQUARED_DAEMON_SECRET: sharedSecret } : {}
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
child.unref();
|
|
1723
|
+
}
|
|
1724
|
+
async function createProxyBridge(options) {
|
|
1725
|
+
const spawnDaemon = options.spawnDaemon ?? spawnDaemonProcess;
|
|
1726
|
+
let endpoint = options.endpoint;
|
|
1727
|
+
let sharedSecret = options.sharedSecret?.trim();
|
|
1728
|
+
let sessionId = null;
|
|
1729
|
+
let heartbeatTimer = null;
|
|
1730
|
+
let isOwner = false;
|
|
1731
|
+
let daemonClosed = false;
|
|
1732
|
+
let stdioClosed = false;
|
|
1733
|
+
const debug = options.debug ?? process.env["MCP_SQUARED_PROXY_DEBUG"] === "1";
|
|
1734
|
+
if (!endpoint) {
|
|
1735
|
+
const registry = await loadLiveDaemonRegistry(options.configHash);
|
|
1736
|
+
if (registry) {
|
|
1737
|
+
endpoint = registry.endpoint;
|
|
1738
|
+
sharedSecret ??= registry.sharedSecret;
|
|
1739
|
+
} else if (!options.noSpawn) {
|
|
1740
|
+
spawnDaemon(sharedSecret);
|
|
1741
|
+
const entry = await waitForDaemon(DEFAULT_STARTUP_TIMEOUT_MS, options.configHash);
|
|
1742
|
+
if (!entry) {
|
|
1743
|
+
throw new Error("Timed out waiting for daemon to start");
|
|
1744
|
+
}
|
|
1745
|
+
endpoint = entry.endpoint;
|
|
1746
|
+
sharedSecret ??= entry.sharedSecret;
|
|
1747
|
+
} else if (options.configHash) {
|
|
1748
|
+
endpoint = getDaemonSocketPath(options.configHash);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
if (!endpoint) {
|
|
1752
|
+
throw new Error("Daemon endpoint not available");
|
|
1753
|
+
}
|
|
1754
|
+
const transportOptions = {
|
|
1755
|
+
endpoint
|
|
1756
|
+
};
|
|
1757
|
+
if (options.timeoutMs !== undefined) {
|
|
1758
|
+
transportOptions.timeoutMs = options.timeoutMs;
|
|
1759
|
+
}
|
|
1760
|
+
const daemonTransport = new SocketClientTransport(transportOptions);
|
|
1761
|
+
const stdioTransport = options.stdioTransport;
|
|
1762
|
+
const closeDaemon = async (sendGoodbye) => {
|
|
1763
|
+
if (daemonClosed) {
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
daemonClosed = true;
|
|
1767
|
+
if (sendGoodbye && sessionId) {
|
|
1768
|
+
await daemonTransport.sendControl({ type: "goodbye", sessionId }).catch(() => {});
|
|
1769
|
+
}
|
|
1770
|
+
await daemonTransport.close().catch(() => {});
|
|
1771
|
+
};
|
|
1772
|
+
daemonTransport.oncontrol = (message) => {
|
|
1773
|
+
switch (message.type) {
|
|
1774
|
+
case "helloAck":
|
|
1775
|
+
sessionId = message.sessionId;
|
|
1776
|
+
isOwner = message.isOwner;
|
|
1777
|
+
if (debug) {
|
|
1778
|
+
console.error(`[proxy] session ${message.sessionId} owner=${message.isOwner}`);
|
|
1779
|
+
}
|
|
1780
|
+
break;
|
|
1781
|
+
case "ownerChanged":
|
|
1782
|
+
isOwner = message.ownerSessionId === sessionId;
|
|
1783
|
+
if (debug) {
|
|
1784
|
+
console.error(`[proxy] owner changed: ${message.ownerSessionId} (isOwner=${isOwner})`);
|
|
1785
|
+
}
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
daemonTransport.onmessage = (message) => {
|
|
1790
|
+
stdioTransport.send(message);
|
|
1791
|
+
};
|
|
1792
|
+
daemonTransport.onclose = () => {
|
|
1793
|
+
daemonClosed = true;
|
|
1794
|
+
if (heartbeatTimer) {
|
|
1795
|
+
clearInterval(heartbeatTimer);
|
|
1796
|
+
heartbeatTimer = null;
|
|
1797
|
+
}
|
|
1798
|
+
stdioTransport.close();
|
|
1799
|
+
};
|
|
1800
|
+
daemonTransport.onerror = (error) => {
|
|
1801
|
+
console.error(`Daemon transport error: ${error.message}`);
|
|
1802
|
+
};
|
|
1803
|
+
stdioTransport.onmessage = (message) => {
|
|
1804
|
+
daemonTransport.send(message);
|
|
1805
|
+
};
|
|
1806
|
+
stdioTransport.onclose = () => {
|
|
1807
|
+
stdioClosed = true;
|
|
1808
|
+
if (heartbeatTimer) {
|
|
1809
|
+
clearInterval(heartbeatTimer);
|
|
1810
|
+
heartbeatTimer = null;
|
|
1811
|
+
}
|
|
1812
|
+
closeDaemon(true);
|
|
1813
|
+
};
|
|
1814
|
+
stdioTransport.onerror = (error) => {
|
|
1815
|
+
console.error(`Stdio transport error: ${error.message}`);
|
|
1816
|
+
};
|
|
1817
|
+
await daemonTransport.start();
|
|
1818
|
+
const launcherHint = process.env["MCP_SQUARED_LAUNCHER"] ?? process.env["MCP_CLIENT_NAME"] ?? process.env["MCP_SQUARED_AGENT"];
|
|
1819
|
+
const clientId = launcherHint ? `${launcherHint}-${process.pid}` : `proxy-${process.pid}`;
|
|
1820
|
+
daemonTransport.sendControl({
|
|
1821
|
+
type: "hello",
|
|
1822
|
+
clientId,
|
|
1823
|
+
...sharedSecret ? { sharedSecret } : {}
|
|
1824
|
+
});
|
|
1825
|
+
heartbeatTimer = setInterval(() => {
|
|
1826
|
+
if (!sessionId) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
daemonTransport.sendControl({
|
|
1830
|
+
type: "heartbeat",
|
|
1831
|
+
sessionId
|
|
1832
|
+
});
|
|
1833
|
+
}, options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS);
|
|
1834
|
+
await stdioTransport.start();
|
|
1835
|
+
return {
|
|
1836
|
+
stop: async () => {
|
|
1837
|
+
if (heartbeatTimer) {
|
|
1838
|
+
clearInterval(heartbeatTimer);
|
|
1839
|
+
heartbeatTimer = null;
|
|
1840
|
+
}
|
|
1841
|
+
await closeDaemon(true);
|
|
1842
|
+
if (!stdioClosed) {
|
|
1843
|
+
await stdioTransport.close();
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
async function runProxy(options = {}) {
|
|
1849
|
+
return createProxyBridge({
|
|
1850
|
+
...options,
|
|
1851
|
+
stdioTransport: new StdioServerTransport
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// src/daemon/server.ts
|
|
1856
|
+
init_paths();
|
|
1857
|
+
import { randomUUID } from "crypto";
|
|
1858
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
1859
|
+
import {
|
|
1860
|
+
connect as connect4,
|
|
1861
|
+
createServer,
|
|
1862
|
+
isIPv4,
|
|
1863
|
+
isIPv6
|
|
1864
|
+
} from "net";
|
|
1865
|
+
import { dirname as dirname2 } from "path";
|
|
1866
|
+
|
|
1867
|
+
// src/version.ts
|
|
1868
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1869
|
+
import { createRequire } from "module";
|
|
1870
|
+
function normalizeVersion(value) {
|
|
1871
|
+
if (typeof value !== "string") {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const trimmed = value.trim();
|
|
1875
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
1876
|
+
}
|
|
1877
|
+
function readManifestFile(manifestUrl) {
|
|
1878
|
+
const raw = readFileSync3(manifestUrl, "utf8");
|
|
1879
|
+
return JSON.parse(raw);
|
|
1880
|
+
}
|
|
1881
|
+
function readBundledManifestFile() {
|
|
1882
|
+
const require2 = createRequire(import.meta.url);
|
|
1883
|
+
return require2("../package.json");
|
|
1884
|
+
}
|
|
1885
|
+
function resolveVersion(options = {}) {
|
|
1886
|
+
const readManifest = options.readManifest ?? readManifestFile;
|
|
1887
|
+
const manifestUrl = options.manifestUrl ?? new URL("../package.json", import.meta.url);
|
|
1888
|
+
try {
|
|
1889
|
+
const manifest = readManifest(manifestUrl);
|
|
1890
|
+
const manifestVersion = normalizeVersion(manifest.version);
|
|
1891
|
+
if (manifestVersion) {
|
|
1892
|
+
return manifestVersion;
|
|
1893
|
+
}
|
|
1894
|
+
} catch {}
|
|
1895
|
+
const envVersion = normalizeVersion((options.env ?? process.env)["npm_package_version"]);
|
|
1896
|
+
if (envVersion) {
|
|
1897
|
+
return envVersion;
|
|
1898
|
+
}
|
|
1899
|
+
const readBundledManifest = options.readBundledManifest ?? readBundledManifestFile;
|
|
1900
|
+
try {
|
|
1901
|
+
const bundledVersion = normalizeVersion(readBundledManifest().version);
|
|
1902
|
+
if (bundledVersion) {
|
|
1903
|
+
return bundledVersion;
|
|
1904
|
+
}
|
|
1905
|
+
} catch {}
|
|
1906
|
+
return normalizeVersion(options.fallbackVersion) ?? "0.0.0";
|
|
1907
|
+
}
|
|
1908
|
+
var VERSION = resolveVersion();
|
|
1825
1909
|
|
|
1826
1910
|
// src/daemon/server.ts
|
|
1827
1911
|
var DEFAULT_CONNECT_TIMEOUT_MS3 = 300;
|
|
1828
|
-
function
|
|
1912
|
+
function isTcpEndpoint4(endpoint) {
|
|
1829
1913
|
return endpoint.startsWith("tcp://");
|
|
1830
1914
|
}
|
|
1831
1915
|
function parseMappedIpv4Address(normalizedHost) {
|
|
@@ -1850,7 +1934,7 @@ function parseMappedIpv4Address(normalizedHost) {
|
|
|
1850
1934
|
}
|
|
1851
1935
|
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
1852
1936
|
}
|
|
1853
|
-
function
|
|
1937
|
+
function parseTcpEndpoint4(endpoint) {
|
|
1854
1938
|
const url = new URL(endpoint);
|
|
1855
1939
|
if (url.protocol !== "tcp:") {
|
|
1856
1940
|
throw new Error(`Invalid TCP endpoint protocol: ${url.protocol}`);
|
|
@@ -1898,7 +1982,7 @@ function formatTcpEndpoint(host, port) {
|
|
|
1898
1982
|
return `tcp://${normalized}:${port}`;
|
|
1899
1983
|
}
|
|
1900
1984
|
function assertLoopbackTcpEndpoint(endpoint) {
|
|
1901
|
-
const { host } =
|
|
1985
|
+
const { host } = parseTcpEndpoint4(endpoint);
|
|
1902
1986
|
if (!isLoopbackHost(host)) {
|
|
1903
1987
|
throw new Error(`Refusing non-loopback daemon TCP endpoint: ${endpoint}. Use localhost, 127.0.0.1, or ::1.`);
|
|
1904
1988
|
}
|
|
@@ -1907,7 +1991,7 @@ async function canConnect3(endpoint, timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS3) {
|
|
|
1907
1991
|
return new Promise((resolve2) => {
|
|
1908
1992
|
let socket = null;
|
|
1909
1993
|
try {
|
|
1910
|
-
socket =
|
|
1994
|
+
socket = isTcpEndpoint4(endpoint) ? connect4(parseTcpEndpoint4(endpoint)) : connect4(endpoint);
|
|
1911
1995
|
} catch {
|
|
1912
1996
|
resolve2(false);
|
|
1913
1997
|
return;
|
|
@@ -1962,7 +2046,7 @@ class DaemonServer {
|
|
|
1962
2046
|
return;
|
|
1963
2047
|
}
|
|
1964
2048
|
ensureDaemonDir(this.configHash);
|
|
1965
|
-
const tcp =
|
|
2049
|
+
const tcp = isTcpEndpoint4(this.socketPath);
|
|
1966
2050
|
if (tcp) {
|
|
1967
2051
|
assertLoopbackTcpEndpoint(this.socketPath);
|
|
1968
2052
|
}
|
|
@@ -1970,7 +2054,7 @@ class DaemonServer {
|
|
|
1970
2054
|
mkdirSync3(dirname2(this.socketPath), { recursive: true });
|
|
1971
2055
|
}
|
|
1972
2056
|
if (tcp) {
|
|
1973
|
-
const port =
|
|
2057
|
+
const port = parseTcpEndpoint4(this.socketPath).port;
|
|
1974
2058
|
if (port > 0 && await canConnect3(this.socketPath)) {
|
|
1975
2059
|
throw new Error(`Daemon already running at ${this.socketPath}. Refusing to start another.`);
|
|
1976
2060
|
}
|
|
@@ -1990,7 +2074,7 @@ class DaemonServer {
|
|
|
1990
2074
|
await new Promise((resolve2, reject) => {
|
|
1991
2075
|
this.server?.once("error", (error) => reject(error));
|
|
1992
2076
|
if (tcp) {
|
|
1993
|
-
const { host, port } =
|
|
2077
|
+
const { host, port } = parseTcpEndpoint4(this.socketPath);
|
|
1994
2078
|
if (!host || Number.isNaN(port)) {
|
|
1995
2079
|
reject(new Error(`Invalid TCP endpoint: ${this.socketPath}`));
|
|
1996
2080
|
return;
|
|
@@ -3667,7 +3751,7 @@ function promptUser(question) {
|
|
|
3667
3751
|
}
|
|
3668
3752
|
|
|
3669
3753
|
// src/install/runner.ts
|
|
3670
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
3754
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
3671
3755
|
import { dirname as dirname3 } from "path";
|
|
3672
3756
|
import { createInterface } from "readline";
|
|
3673
3757
|
import { parse as parseToml3, stringify as stringifyToml2 } from "smol-toml";
|
|
@@ -3987,7 +4071,7 @@ function performInstallation(options) {
|
|
|
3987
4071
|
if (existsSync7(path)) {
|
|
3988
4072
|
configExists = true;
|
|
3989
4073
|
try {
|
|
3990
|
-
const content =
|
|
4074
|
+
const content = readFileSync4(path, "utf-8");
|
|
3991
4075
|
existingConfig = isToml ? parseToml3(content) : JSON.parse(content);
|
|
3992
4076
|
} catch (error) {
|
|
3993
4077
|
return {
|
|
@@ -4404,7 +4488,6 @@ class OAuthCallbackServer {
|
|
|
4404
4488
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
4405
4489
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4406
4490
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4407
|
-
import { VERSION as VERSION2 } from "@/version.js";
|
|
4408
4491
|
|
|
4409
4492
|
// src/oauth/provider.ts
|
|
4410
4493
|
var DEFAULT_OAUTH_CALLBACK_PORT = 8089;
|
|
@@ -4575,7 +4658,7 @@ import {
|
|
|
4575
4658
|
chmodSync,
|
|
4576
4659
|
existsSync as existsSync8,
|
|
4577
4660
|
mkdirSync as mkdirSync5,
|
|
4578
|
-
readFileSync as
|
|
4661
|
+
readFileSync as readFileSync5,
|
|
4579
4662
|
unlinkSync as unlinkSync4,
|
|
4580
4663
|
writeFileSync as writeFileSync4
|
|
4581
4664
|
} from "fs";
|
|
@@ -4613,7 +4696,7 @@ class TokenStorage {
|
|
|
4613
4696
|
return;
|
|
4614
4697
|
}
|
|
4615
4698
|
try {
|
|
4616
|
-
const content =
|
|
4699
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
4617
4700
|
return JSON.parse(content);
|
|
4618
4701
|
} catch {
|
|
4619
4702
|
return;
|
|
@@ -4695,7 +4778,7 @@ class TokenStorage {
|
|
|
4695
4778
|
function getPreflightClientMetadata() {
|
|
4696
4779
|
return {
|
|
4697
4780
|
name: "mcp-squared-preflight",
|
|
4698
|
-
version:
|
|
4781
|
+
version: VERSION
|
|
4699
4782
|
};
|
|
4700
4783
|
}
|
|
4701
4784
|
async function performPreflightAuth(config) {
|
|
@@ -4800,7 +4883,6 @@ async function performInteractiveAuth(name, sseConfig, authProvider, callbackPor
|
|
|
4800
4883
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4801
4884
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4802
4885
|
import { z as z3 } from "zod";
|
|
4803
|
-
import { VERSION as VERSION3 } from "@/version.js";
|
|
4804
4886
|
|
|
4805
4887
|
// agent_safety_kit/policy/matchers.ts
|
|
4806
4888
|
var GLOB_SPECIALS = /[.+^${}()|[\]\\]/g;
|
|
@@ -5158,7 +5240,7 @@ class Guard {
|
|
|
5158
5240
|
}
|
|
5159
5241
|
}
|
|
5160
5242
|
// agent_safety_kit/policy/load.ts
|
|
5161
|
-
import { existsSync as existsSync9, readFileSync as
|
|
5243
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
5162
5244
|
import { resolve as resolve3 } from "path";
|
|
5163
5245
|
import { parse as parseYaml } from "yaml";
|
|
5164
5246
|
|
|
@@ -5221,7 +5303,7 @@ function load_policy(options = {}) {
|
|
|
5221
5303
|
if (!existsSync9(sourcePath)) {
|
|
5222
5304
|
throw new Error(`Agent safety policy file not found at ${sourcePath}`);
|
|
5223
5305
|
}
|
|
5224
|
-
const raw =
|
|
5306
|
+
const raw = readFileSync6(sourcePath, "utf8");
|
|
5225
5307
|
const parsed = parseYaml(raw);
|
|
5226
5308
|
const policy = AgentPolicySchema.parse(parsed);
|
|
5227
5309
|
const playbookName = options.playbook ?? envConfig.playbook;
|
|
@@ -5258,7 +5340,7 @@ class NullSink {
|
|
|
5258
5340
|
}
|
|
5259
5341
|
|
|
5260
5342
|
// agent_safety_kit/observability/sinks/otel.ts
|
|
5261
|
-
import { createRequire } from "module";
|
|
5343
|
+
import { createRequire as createRequire2 } from "module";
|
|
5262
5344
|
|
|
5263
5345
|
// agent_safety_kit/observability/sinks/base.ts
|
|
5264
5346
|
function compactAttributes(attributes) {
|
|
@@ -5275,7 +5357,7 @@ function compactAttributes(attributes) {
|
|
|
5275
5357
|
}
|
|
5276
5358
|
|
|
5277
5359
|
// agent_safety_kit/observability/sinks/otel.ts
|
|
5278
|
-
var require2 =
|
|
5360
|
+
var require2 = createRequire2(import.meta.url);
|
|
5279
5361
|
function loadOpenTelemetryApi() {
|
|
5280
5362
|
try {
|
|
5281
5363
|
return require2("@opentelemetry/api");
|
|
@@ -7154,10 +7236,10 @@ async function testUpstreamConnection(name, config, options = {}) {
|
|
|
7154
7236
|
// src/server/monitor-server.ts
|
|
7155
7237
|
import { existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
7156
7238
|
import { createServer as createServer3 } from "net";
|
|
7157
|
-
function
|
|
7239
|
+
function isTcpEndpoint5(endpoint) {
|
|
7158
7240
|
return endpoint.startsWith("tcp://");
|
|
7159
7241
|
}
|
|
7160
|
-
function
|
|
7242
|
+
function parseTcpEndpoint5(endpoint) {
|
|
7161
7243
|
let url;
|
|
7162
7244
|
try {
|
|
7163
7245
|
url = new URL(endpoint);
|
|
@@ -7197,7 +7279,7 @@ class MonitorServer {
|
|
|
7197
7279
|
if (this.isRunning) {
|
|
7198
7280
|
return;
|
|
7199
7281
|
}
|
|
7200
|
-
const tcp =
|
|
7282
|
+
const tcp = isTcpEndpoint5(this.socketPath);
|
|
7201
7283
|
if (!tcp && existsSync10(this.socketPath)) {
|
|
7202
7284
|
try {
|
|
7203
7285
|
unlinkSync5(this.socketPath);
|
|
@@ -7218,7 +7300,7 @@ class MonitorServer {
|
|
|
7218
7300
|
reject(error);
|
|
7219
7301
|
});
|
|
7220
7302
|
if (tcp) {
|
|
7221
|
-
const { host, port } =
|
|
7303
|
+
const { host, port } = parseTcpEndpoint5(this.socketPath);
|
|
7222
7304
|
server.listen({ host, port }, () => {
|
|
7223
7305
|
const address = server.address();
|
|
7224
7306
|
if (address && typeof address !== "string") {
|
|
@@ -7252,7 +7334,7 @@ class MonitorServer {
|
|
|
7252
7334
|
}
|
|
7253
7335
|
});
|
|
7254
7336
|
});
|
|
7255
|
-
if (!
|
|
7337
|
+
if (!isTcpEndpoint5(this.socketPath)) {
|
|
7256
7338
|
try {
|
|
7257
7339
|
if (existsSync10(this.socketPath)) {
|
|
7258
7340
|
unlinkSync5(this.socketPath);
|
|
@@ -7609,7 +7691,7 @@ class McpSquaredServer {
|
|
|
7609
7691
|
guard;
|
|
7610
7692
|
constructor(options = {}) {
|
|
7611
7693
|
const name = options.name ?? "mcp-squared";
|
|
7612
|
-
const version = options.version ??
|
|
7694
|
+
const version = options.version ?? VERSION;
|
|
7613
7695
|
this.serverName = name;
|
|
7614
7696
|
this.serverVersion = version;
|
|
7615
7697
|
if (options.cataloger) {
|
|
@@ -8186,7 +8268,7 @@ async function startServer() {
|
|
|
8186
8268
|
startedAt: Date.now(),
|
|
8187
8269
|
cwd: process.cwd(),
|
|
8188
8270
|
configPath,
|
|
8189
|
-
version:
|
|
8271
|
+
version: VERSION,
|
|
8190
8272
|
command: process.argv.join(" "),
|
|
8191
8273
|
role: "server",
|
|
8192
8274
|
...launcher ? { launcher } : {}
|
|
@@ -8413,7 +8495,7 @@ Callback URL: ${callbackServer.getCallbackUrl()}`);
|
|
|
8413
8495
|
});
|
|
8414
8496
|
const client = new Client4({
|
|
8415
8497
|
name: "mcp-squared-auth",
|
|
8416
|
-
version:
|
|
8498
|
+
version: VERSION
|
|
8417
8499
|
});
|
|
8418
8500
|
console.log(`
|
|
8419
8501
|
Connecting to server...`);
|
|
@@ -8476,7 +8558,7 @@ async function runMonitor(options) {
|
|
|
8476
8558
|
if (!socketPath) {
|
|
8477
8559
|
const { config, path: configPath } = await loadConfig();
|
|
8478
8560
|
const configHash = computeConfigHash(config);
|
|
8479
|
-
const daemonRegistry = await
|
|
8561
|
+
const daemonRegistry = await loadLiveDaemonRegistry(configHash);
|
|
8480
8562
|
if (!daemonRegistry) {
|
|
8481
8563
|
console.error("Error: No running shared MCP\xB2 daemon found for the active configuration.");
|
|
8482
8564
|
console.error("Start the daemon first:");
|
|
@@ -8690,7 +8772,7 @@ async function runDaemon(options) {
|
|
|
8690
8772
|
startedAt: Date.now(),
|
|
8691
8773
|
cwd: process.cwd(),
|
|
8692
8774
|
configPath,
|
|
8693
|
-
version:
|
|
8775
|
+
version: VERSION,
|
|
8694
8776
|
command: process.argv.join(" "),
|
|
8695
8777
|
role: "daemon",
|
|
8696
8778
|
...daemonLauncher ? { launcher: daemonLauncher } : {}
|
|
@@ -8743,7 +8825,7 @@ async function runProxyCommand(options) {
|
|
|
8743
8825
|
startedAt: Date.now(),
|
|
8744
8826
|
cwd: process.cwd(),
|
|
8745
8827
|
configPath,
|
|
8746
|
-
version:
|
|
8828
|
+
version: VERSION,
|
|
8747
8829
|
command: process.argv.join(" "),
|
|
8748
8830
|
role: "proxy",
|
|
8749
8831
|
...proxyLauncher ? { launcher: proxyLauncher } : {}
|
|
@@ -8818,7 +8900,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
8818
8900
|
process.exit(0);
|
|
8819
8901
|
}
|
|
8820
8902
|
if (args.version) {
|
|
8821
|
-
console.log(`MCP\xB2 v${
|
|
8903
|
+
console.log(`MCP\xB2 v${VERSION}`);
|
|
8822
8904
|
process.exit(0);
|
|
8823
8905
|
}
|
|
8824
8906
|
switch (args.mode) {
|
|
@@ -8888,5 +8970,5 @@ export {
|
|
|
8888
8970
|
main,
|
|
8889
8971
|
logSecurityProfile,
|
|
8890
8972
|
logSearchModeProfile,
|
|
8891
|
-
|
|
8973
|
+
VERSION
|
|
8892
8974
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-squared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "MCP² (Mercury Control Plane) - A local-first meta-server and proxy for the Model Context Protocol",
|
|
5
5
|
"author": "aditzel",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
"build": "bun build src/index.ts --outdir dist --target bun --packages=external",
|
|
23
23
|
"build:compile": "bun build src/index.ts --compile --outfile dist/mcp-squared --external @opentui/core --external @opentui/core-darwin-arm64 --external @opentui/core-darwin-x64 --external @opentui/core-linux-arm64 --external @opentui/core-linux-x64 --external @opentui/core-win32-arm64 --external @opentui/core-win32-x64",
|
|
24
24
|
"build:compile:matrix": "bash scripts/compile-matrix.sh",
|
|
25
|
-
"prepack": "bun run build",
|
|
25
|
+
"prepack": "bun run build && bun run build:verify",
|
|
26
26
|
"audit": "bun audit",
|
|
27
27
|
"test": "bun test",
|
|
28
28
|
"test:fast": "SKIP_SLOW_TESTS=true bun test",
|
|
29
29
|
"test:watch": "bun test --watch",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"lint": "biome check src tests scripts AGENTS.md CLAUDE.md WARP.md README.md CHANGELOG.md package.json biome.json tsconfig.json",
|
|
32
|
-
"release:check": "bun run audit && bun test && bun run build && bun run lint && bun run typecheck && bun pm pack --dry-run",
|
|
32
|
+
"release:check": "bun run audit && bun test && bun run build && bun run build:verify && bun run lint && bun run typecheck && bun pm pack --dry-run",
|
|
33
|
+
"build:verify": "bun run scripts/verify-dist-runtime-imports.ts",
|
|
33
34
|
"lint:fix": "biome check --write .",
|
|
34
35
|
"format": "biome format --write .",
|
|
35
36
|
"clean": "rm -rf dist",
|