muxed 0.2.1 → 0.2.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 +76 -262
- package/dist/cli.mjs +726 -51
- package/dist/client/index.d.mts +1 -0
- package/dist/client/index.mjs +20 -1
- package/package.json +14 -4
package/dist/cli.mjs
CHANGED
|
@@ -11,12 +11,13 @@ import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontex
|
|
|
11
11
|
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
12
12
|
import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js";
|
|
13
13
|
import crypto from "node:crypto";
|
|
14
|
-
import { execFile, fork } from "node:child_process";
|
|
14
|
+
import { execFile, execSync, fork } from "node:child_process";
|
|
15
15
|
import http from "node:http";
|
|
16
16
|
import { compile } from "json-schema-to-typescript";
|
|
17
17
|
import net from "node:net";
|
|
18
18
|
import { Command } from "commander";
|
|
19
19
|
import { PostHog } from "posthog-node";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
20
21
|
import * as readline from "node:readline/promises";
|
|
21
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -77,6 +78,7 @@ const DaemonConfigSchema = z.object({
|
|
|
77
78
|
connectTimeout: z.number().optional(),
|
|
78
79
|
requestTimeout: z.number().optional(),
|
|
79
80
|
healthCheckInterval: z.number().optional(),
|
|
81
|
+
healthCheckTimeout: z.number().optional(),
|
|
80
82
|
maxRestartAttempts: z.number().optional(),
|
|
81
83
|
maxTotalTimeout: z.number().optional(),
|
|
82
84
|
taskExpiryTimeout: z.number().optional(),
|
|
@@ -120,6 +122,7 @@ const DAEMON_DEFAULTS = {
|
|
|
120
122
|
connectTimeout: 3e4,
|
|
121
123
|
requestTimeout: 3e4,
|
|
122
124
|
healthCheckInterval: 3e4,
|
|
125
|
+
healthCheckTimeout: 1e4,
|
|
123
126
|
maxRestartAttempts: -1,
|
|
124
127
|
maxTotalTimeout: 3e5,
|
|
125
128
|
taskExpiryTimeout: 36e5,
|
|
@@ -698,6 +701,7 @@ var ServerManager = class {
|
|
|
698
701
|
stopped = false;
|
|
699
702
|
connectTimeout;
|
|
700
703
|
healthCheckInterval;
|
|
704
|
+
healthCheckTimeout;
|
|
701
705
|
maxRestartAttempts;
|
|
702
706
|
onHealthChange;
|
|
703
707
|
constructor(name, config, options) {
|
|
@@ -705,6 +709,7 @@ var ServerManager = class {
|
|
|
705
709
|
this.config = config;
|
|
706
710
|
this.connectTimeout = options?.connectTimeout;
|
|
707
711
|
this.healthCheckInterval = options?.healthCheckInterval ?? 3e4;
|
|
712
|
+
this.healthCheckTimeout = options?.healthCheckTimeout ?? 1e4;
|
|
708
713
|
this.maxRestartAttempts = options?.maxRestartAttempts ?? -1;
|
|
709
714
|
}
|
|
710
715
|
setHealthCallback(cb) {
|
|
@@ -911,7 +916,7 @@ var ServerManager = class {
|
|
|
911
916
|
if (!this.client || this.status !== "connected") return;
|
|
912
917
|
this.lastHealthCheck = /* @__PURE__ */ new Date();
|
|
913
918
|
try {
|
|
914
|
-
await this.client.ping();
|
|
919
|
+
await this.client.ping({ timeout: this.healthCheckTimeout });
|
|
915
920
|
if (this.consecutiveFailures > 0) getLogger().info(`Health check recovered after ${this.consecutiveFailures} failures`, this.name);
|
|
916
921
|
this.consecutiveFailures = 0;
|
|
917
922
|
} catch (err) {
|
|
@@ -966,10 +971,14 @@ var ServerManager = class {
|
|
|
966
971
|
if (!this.client) return;
|
|
967
972
|
this.tools = (await this.client.listTools()).tools;
|
|
968
973
|
}
|
|
974
|
+
ensureConnected() {
|
|
975
|
+
if (!this.client || this.status !== "connected") throw new Error(`Server "${this.name}" is not connected`);
|
|
976
|
+
return this.client;
|
|
977
|
+
}
|
|
969
978
|
async callTool(name, args, timeout) {
|
|
970
|
-
|
|
979
|
+
const client = this.ensureConnected();
|
|
971
980
|
const options = timeout ? { signal: AbortSignal.timeout(timeout) } : void 0;
|
|
972
|
-
return await
|
|
981
|
+
return await client.callTool({
|
|
973
982
|
name,
|
|
974
983
|
arguments: args
|
|
975
984
|
}, void 0, options);
|
|
@@ -982,8 +991,7 @@ var ServerManager = class {
|
|
|
982
991
|
this.resources = (await this.client.listResources()).resources;
|
|
983
992
|
}
|
|
984
993
|
async readResource(uri) {
|
|
985
|
-
|
|
986
|
-
return await this.client.readResource({ uri });
|
|
994
|
+
return await this.ensureConnected().readResource({ uri });
|
|
987
995
|
}
|
|
988
996
|
listPrompts() {
|
|
989
997
|
return this.prompts;
|
|
@@ -993,40 +1001,35 @@ var ServerManager = class {
|
|
|
993
1001
|
this.prompts = (await this.client.listPrompts()).prompts;
|
|
994
1002
|
}
|
|
995
1003
|
async getPrompt(name, args) {
|
|
996
|
-
|
|
997
|
-
return await this.client.getPrompt({
|
|
1004
|
+
return await this.ensureConnected().getPrompt({
|
|
998
1005
|
name,
|
|
999
1006
|
arguments: args
|
|
1000
1007
|
});
|
|
1001
1008
|
}
|
|
1002
1009
|
async complete(ref, argument) {
|
|
1003
|
-
|
|
1010
|
+
const client = this.ensureConnected();
|
|
1004
1011
|
if (!this.capabilities?.completions) throw new Error(`Server "${this.name}" does not support completions`);
|
|
1005
|
-
return await
|
|
1012
|
+
return await client.complete({
|
|
1006
1013
|
ref,
|
|
1007
1014
|
argument
|
|
1008
1015
|
});
|
|
1009
1016
|
}
|
|
1010
1017
|
async listTasks(cursor) {
|
|
1011
|
-
|
|
1018
|
+
const client = this.ensureConnected();
|
|
1012
1019
|
if (!this.capabilities?.experimental?.tasks) throw new Error(`Server "${this.name}" does not support tasks`);
|
|
1013
|
-
return await
|
|
1020
|
+
return await client.experimental.tasks.listTasks(cursor);
|
|
1014
1021
|
}
|
|
1015
1022
|
async getTask(taskId) {
|
|
1016
|
-
|
|
1017
|
-
return await this.client.experimental.tasks.getTask(taskId);
|
|
1023
|
+
return await this.ensureConnected().experimental.tasks.getTask(taskId);
|
|
1018
1024
|
}
|
|
1019
1025
|
async getTaskResult(taskId) {
|
|
1020
|
-
|
|
1021
|
-
return await this.client.experimental.tasks.getTaskResult(taskId);
|
|
1026
|
+
return await this.ensureConnected().experimental.tasks.getTaskResult(taskId);
|
|
1022
1027
|
}
|
|
1023
1028
|
async cancelTask(taskId) {
|
|
1024
|
-
|
|
1025
|
-
return await this.client.experimental.tasks.cancelTask(taskId);
|
|
1029
|
+
return await this.ensureConnected().experimental.tasks.cancelTask(taskId);
|
|
1026
1030
|
}
|
|
1027
1031
|
async callToolWithTask(name, args) {
|
|
1028
|
-
|
|
1029
|
-
const stream = this.client.experimental.tasks.callToolStream({
|
|
1032
|
+
const stream = this.ensureConnected().experimental.tasks.callToolStream({
|
|
1030
1033
|
name,
|
|
1031
1034
|
arguments: args
|
|
1032
1035
|
});
|
|
@@ -1180,6 +1183,7 @@ var ServerPool = class {
|
|
|
1180
1183
|
const manager = new ServerManager(name, serverConfig, {
|
|
1181
1184
|
connectTimeout: config.daemon?.connectTimeout,
|
|
1182
1185
|
healthCheckInterval: config.daemon?.healthCheckInterval,
|
|
1186
|
+
healthCheckTimeout: config.daemon?.healthCheckTimeout,
|
|
1183
1187
|
maxRestartAttempts: config.daemon?.maxRestartAttempts
|
|
1184
1188
|
});
|
|
1185
1189
|
manager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1481,6 +1485,7 @@ var ServerPool = class {
|
|
|
1481
1485
|
const manager = new ServerManager(name, serverConfig, {
|
|
1482
1486
|
connectTimeout: newConfig.daemon?.connectTimeout,
|
|
1483
1487
|
healthCheckInterval: newConfig.daemon?.healthCheckInterval,
|
|
1488
|
+
healthCheckTimeout: newConfig.daemon?.healthCheckTimeout,
|
|
1484
1489
|
maxRestartAttempts: newConfig.daemon?.maxRestartAttempts
|
|
1485
1490
|
});
|
|
1486
1491
|
manager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1496,6 +1501,7 @@ var ServerPool = class {
|
|
|
1496
1501
|
const newManager = new ServerManager(name, serverConfig, {
|
|
1497
1502
|
connectTimeout: newConfig.daemon?.connectTimeout,
|
|
1498
1503
|
healthCheckInterval: newConfig.daemon?.healthCheckInterval,
|
|
1504
|
+
healthCheckTimeout: newConfig.daemon?.healthCheckTimeout,
|
|
1499
1505
|
maxRestartAttempts: newConfig.daemon?.maxRestartAttempts
|
|
1500
1506
|
});
|
|
1501
1507
|
newManager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1639,6 +1645,240 @@ function filterFields(data, fields) {
|
|
|
1639
1645
|
}
|
|
1640
1646
|
return data;
|
|
1641
1647
|
}
|
|
1648
|
+
const DEFAULT_BUDGET = 48e3;
|
|
1649
|
+
function isLeaf(schema) {
|
|
1650
|
+
const type = schema.type;
|
|
1651
|
+
if (type === "string" || type === "number" || type === "integer" || type === "boolean" || type === "null") {
|
|
1652
|
+
if (!schema.anyOf && !schema.oneOf && !schema.allOf) return true;
|
|
1653
|
+
}
|
|
1654
|
+
if (schema.$ref) return true;
|
|
1655
|
+
if (!schema.properties && !schema.items && !schema.additionalProperties && !schema.patternProperties && !schema.anyOf && !schema.oneOf && !schema.allOf && !schema.if && !schema.not) return true;
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
function buildHint(schema) {
|
|
1659
|
+
const parts = [];
|
|
1660
|
+
const props = schema.properties;
|
|
1661
|
+
if (props) {
|
|
1662
|
+
const count = Object.keys(props).length;
|
|
1663
|
+
const required = schema.required;
|
|
1664
|
+
if (required && required.length > 0) parts.push(`${count} properties, ${required.length} required`);
|
|
1665
|
+
else parts.push(`${count} ${count === 1 ? "property" : "properties"}`);
|
|
1666
|
+
return parts.join(", ");
|
|
1667
|
+
}
|
|
1668
|
+
if (schema.type === "object" && schema.additionalProperties) return `map<string, ${schema.additionalProperties.type ?? "unknown"}>`;
|
|
1669
|
+
if (schema.items) {
|
|
1670
|
+
const items = schema.items;
|
|
1671
|
+
if (items.type === "object" && items.properties) {
|
|
1672
|
+
const count = Object.keys(items.properties).length;
|
|
1673
|
+
return `items: object (${count} ${count === 1 ? "property" : "properties"})`;
|
|
1674
|
+
}
|
|
1675
|
+
return `items: ${items.type ?? "unknown"}`;
|
|
1676
|
+
}
|
|
1677
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
1678
|
+
const variants = schema[keyword];
|
|
1679
|
+
if (variants) return `${keyword}: ${variants.length} variants`;
|
|
1680
|
+
}
|
|
1681
|
+
if (schema.allOf) return `allOf: ${schema.allOf.length} schemas`;
|
|
1682
|
+
if (schema.enum) return `enum: ${schema.enum.length} values`;
|
|
1683
|
+
return schema.type ?? "schema";
|
|
1684
|
+
}
|
|
1685
|
+
function buildCollapsedNode(schema) {
|
|
1686
|
+
const result = {};
|
|
1687
|
+
if (schema.type) result.type = schema.type;
|
|
1688
|
+
if (schema.description) result.description = schema.description;
|
|
1689
|
+
result._collapsed = true;
|
|
1690
|
+
result._hint = buildHint(schema);
|
|
1691
|
+
return result;
|
|
1692
|
+
}
|
|
1693
|
+
function collapseChild(schema, childDepth, maxDepth) {
|
|
1694
|
+
if (typeof schema !== "object" || schema === null) return {
|
|
1695
|
+
schema,
|
|
1696
|
+
hasCollapsed: false
|
|
1697
|
+
};
|
|
1698
|
+
if (isLeaf(schema)) return {
|
|
1699
|
+
schema,
|
|
1700
|
+
hasCollapsed: false
|
|
1701
|
+
};
|
|
1702
|
+
if (childDepth > maxDepth) return {
|
|
1703
|
+
schema: buildCollapsedNode(schema),
|
|
1704
|
+
hasCollapsed: true
|
|
1705
|
+
};
|
|
1706
|
+
return collapseNode(schema, childDepth, maxDepth);
|
|
1707
|
+
}
|
|
1708
|
+
function collapseNode(schema, currentDepth, maxDepth) {
|
|
1709
|
+
if (typeof schema !== "object" || schema === null) return {
|
|
1710
|
+
schema,
|
|
1711
|
+
hasCollapsed: false
|
|
1712
|
+
};
|
|
1713
|
+
if (isLeaf(schema)) return {
|
|
1714
|
+
schema,
|
|
1715
|
+
hasCollapsed: false
|
|
1716
|
+
};
|
|
1717
|
+
let hasCollapsed = false;
|
|
1718
|
+
const result = {};
|
|
1719
|
+
for (const [key, value] of Object.entries(schema)) switch (key) {
|
|
1720
|
+
case "properties": {
|
|
1721
|
+
const props = value;
|
|
1722
|
+
const newProps = {};
|
|
1723
|
+
for (const [propName, propSchema] of Object.entries(props)) {
|
|
1724
|
+
const collapsed = collapseChild(propSchema, currentDepth + 1, maxDepth);
|
|
1725
|
+
newProps[propName] = collapsed.schema;
|
|
1726
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1727
|
+
}
|
|
1728
|
+
result.properties = newProps;
|
|
1729
|
+
break;
|
|
1730
|
+
}
|
|
1731
|
+
case "items": {
|
|
1732
|
+
const collapsed = collapseChild(value, currentDepth + 1, maxDepth);
|
|
1733
|
+
result.items = collapsed.schema;
|
|
1734
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
case "additionalProperties":
|
|
1738
|
+
if (typeof value === "object" && value !== null) {
|
|
1739
|
+
const collapsed = collapseChild(value, currentDepth + 1, maxDepth);
|
|
1740
|
+
result.additionalProperties = collapsed.schema;
|
|
1741
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1742
|
+
} else result.additionalProperties = value;
|
|
1743
|
+
break;
|
|
1744
|
+
case "patternProperties": {
|
|
1745
|
+
const patterns = value;
|
|
1746
|
+
const newPatterns = {};
|
|
1747
|
+
for (const [pattern, patternSchema] of Object.entries(patterns)) {
|
|
1748
|
+
const collapsed = collapseChild(patternSchema, currentDepth + 1, maxDepth);
|
|
1749
|
+
newPatterns[pattern] = collapsed.schema;
|
|
1750
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1751
|
+
}
|
|
1752
|
+
result.patternProperties = newPatterns;
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
case "anyOf":
|
|
1756
|
+
case "oneOf":
|
|
1757
|
+
case "allOf": {
|
|
1758
|
+
const variants = value;
|
|
1759
|
+
const newVariants = [];
|
|
1760
|
+
for (const variant of variants) {
|
|
1761
|
+
const collapsed = collapseNode(variant, currentDepth, maxDepth);
|
|
1762
|
+
newVariants.push(collapsed.schema);
|
|
1763
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1764
|
+
}
|
|
1765
|
+
result[key] = newVariants;
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
case "if":
|
|
1769
|
+
case "then":
|
|
1770
|
+
case "else":
|
|
1771
|
+
case "not": {
|
|
1772
|
+
const collapsed = collapseNode(value, currentDepth, maxDepth);
|
|
1773
|
+
result[key] = collapsed.schema;
|
|
1774
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
case "$defs":
|
|
1778
|
+
case "definitions": {
|
|
1779
|
+
const defs = value;
|
|
1780
|
+
const newDefs = {};
|
|
1781
|
+
for (const [defName, defSchema] of Object.entries(defs)) {
|
|
1782
|
+
const collapsed = collapseChild(defSchema, currentDepth + 1, maxDepth);
|
|
1783
|
+
newDefs[defName] = collapsed.schema;
|
|
1784
|
+
if (collapsed.hasCollapsed) hasCollapsed = true;
|
|
1785
|
+
}
|
|
1786
|
+
result[key] = newDefs;
|
|
1787
|
+
break;
|
|
1788
|
+
}
|
|
1789
|
+
default: result[key] = value;
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
schema: result,
|
|
1793
|
+
hasCollapsed
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
function collapseSchema(schema, maxDepth) {
|
|
1797
|
+
return collapseNode(schema, 0, Math.max(0, maxDepth));
|
|
1798
|
+
}
|
|
1799
|
+
function extractSubtree(schema, path) {
|
|
1800
|
+
const segments = path.split(".");
|
|
1801
|
+
let current = schema;
|
|
1802
|
+
for (const segment of segments) {
|
|
1803
|
+
if (!current || typeof current !== "object") return void 0;
|
|
1804
|
+
const props = current.properties;
|
|
1805
|
+
if (props && segment in props) {
|
|
1806
|
+
current = props[segment];
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (segment === "items" && current.items) {
|
|
1810
|
+
current = current.items;
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
if (segment === "additionalProperties" && typeof current.additionalProperties === "object") {
|
|
1814
|
+
current = current.additionalProperties;
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
const index = parseInt(segment, 10);
|
|
1818
|
+
if (!isNaN(index)) {
|
|
1819
|
+
for (const keyword of [
|
|
1820
|
+
"anyOf",
|
|
1821
|
+
"oneOf",
|
|
1822
|
+
"allOf"
|
|
1823
|
+
]) {
|
|
1824
|
+
const variants = current[keyword];
|
|
1825
|
+
if (variants && index >= 0 && index < variants.length) {
|
|
1826
|
+
current = variants[index];
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (current !== schema) continue;
|
|
1831
|
+
}
|
|
1832
|
+
const patterns = current.patternProperties;
|
|
1833
|
+
if (patterns && segment in patterns) {
|
|
1834
|
+
current = patterns[segment];
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
return current;
|
|
1840
|
+
}
|
|
1841
|
+
function autoDepth(schemas, budgetChars = DEFAULT_BUDGET) {
|
|
1842
|
+
if (schemas.length === 0) return {
|
|
1843
|
+
depth: 0,
|
|
1844
|
+
fullyExpanded: true
|
|
1845
|
+
};
|
|
1846
|
+
let depth = 1;
|
|
1847
|
+
let lastFit = 0;
|
|
1848
|
+
let lastFullyExpanded = false;
|
|
1849
|
+
const depth0 = schemas.map((s) => collapseSchema(s, 0));
|
|
1850
|
+
const depth0Size = depth0.reduce((sum, r) => sum + JSON.stringify(r.schema).length, 0);
|
|
1851
|
+
const depth0Expanded = depth0.every((r) => !r.hasCollapsed);
|
|
1852
|
+
if (depth0Size > budgetChars) return {
|
|
1853
|
+
depth: 0,
|
|
1854
|
+
fullyExpanded: depth0Expanded
|
|
1855
|
+
};
|
|
1856
|
+
lastFit = 0;
|
|
1857
|
+
lastFullyExpanded = depth0Expanded;
|
|
1858
|
+
if (depth0Expanded) return {
|
|
1859
|
+
depth: 0,
|
|
1860
|
+
fullyExpanded: true
|
|
1861
|
+
};
|
|
1862
|
+
for (depth = 1; depth <= 20; depth++) {
|
|
1863
|
+
const results = schemas.map((s) => collapseSchema(s, depth));
|
|
1864
|
+
const totalSize = results.reduce((sum, r) => sum + JSON.stringify(r.schema).length, 0);
|
|
1865
|
+
const fullyExpanded = results.every((r) => !r.hasCollapsed);
|
|
1866
|
+
if (totalSize > budgetChars) return {
|
|
1867
|
+
depth: lastFit,
|
|
1868
|
+
fullyExpanded: lastFullyExpanded
|
|
1869
|
+
};
|
|
1870
|
+
lastFit = depth;
|
|
1871
|
+
lastFullyExpanded = fullyExpanded;
|
|
1872
|
+
if (fullyExpanded) return {
|
|
1873
|
+
depth,
|
|
1874
|
+
fullyExpanded: true
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
return {
|
|
1878
|
+
depth: lastFit,
|
|
1879
|
+
fullyExpanded: lastFullyExpanded
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1642
1882
|
function createDaemonServer(serverPool, config) {
|
|
1643
1883
|
const socketPath = getSocketPath();
|
|
1644
1884
|
let idleTimer;
|
|
@@ -1667,11 +1907,37 @@ function createDaemonServer(serverPool, config) {
|
|
|
1667
1907
|
result: serverPool.listServers()
|
|
1668
1908
|
};
|
|
1669
1909
|
case "tools/list": {
|
|
1670
|
-
const
|
|
1910
|
+
const p = params;
|
|
1911
|
+
const tools = serverPool.listAllTools(p?.server);
|
|
1912
|
+
if (p?.includeSchema) {
|
|
1913
|
+
const schemas = tools.map(({ tool }) => tool.inputSchema ?? {});
|
|
1914
|
+
const depth = p.schemaDepth ?? autoDepth(schemas, p.schemaBudget).depth;
|
|
1915
|
+
return {
|
|
1916
|
+
jsonrpc: "2.0",
|
|
1917
|
+
id,
|
|
1918
|
+
result: tools.map(({ server, tool }) => ({
|
|
1919
|
+
server,
|
|
1920
|
+
tool: {
|
|
1921
|
+
name: tool.name,
|
|
1922
|
+
title: tool.title,
|
|
1923
|
+
description: tool.description,
|
|
1924
|
+
annotations: tool.annotations,
|
|
1925
|
+
inputSchema: collapseSchema(tool.inputSchema ?? {}, depth).schema
|
|
1926
|
+
}
|
|
1927
|
+
}))
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1671
1930
|
return {
|
|
1672
1931
|
jsonrpc: "2.0",
|
|
1673
1932
|
id,
|
|
1674
|
-
result:
|
|
1933
|
+
result: tools.map(({ server, tool }) => ({
|
|
1934
|
+
server,
|
|
1935
|
+
tool: {
|
|
1936
|
+
name: tool.name,
|
|
1937
|
+
title: tool.title,
|
|
1938
|
+
annotations: tool.annotations
|
|
1939
|
+
}
|
|
1940
|
+
}))
|
|
1675
1941
|
};
|
|
1676
1942
|
}
|
|
1677
1943
|
case "tools/call": {
|
|
@@ -1764,10 +2030,34 @@ function createDaemonServer(serverPool, config) {
|
|
|
1764
2030
|
data: toErrorData(found.error)
|
|
1765
2031
|
}
|
|
1766
2032
|
};
|
|
2033
|
+
let tool = found.tool;
|
|
2034
|
+
if (p.path) {
|
|
2035
|
+
const subtree = extractSubtree(tool.inputSchema ?? {}, p.path);
|
|
2036
|
+
if (!subtree) return {
|
|
2037
|
+
jsonrpc: "2.0",
|
|
2038
|
+
id,
|
|
2039
|
+
error: {
|
|
2040
|
+
code: -32602,
|
|
2041
|
+
message: `Path not found in schema: ${p.path}`,
|
|
2042
|
+
data: {
|
|
2043
|
+
code: "SCHEMA_PATH_NOT_FOUND",
|
|
2044
|
+
path: p.path
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
tool = {
|
|
2049
|
+
...tool,
|
|
2050
|
+
inputSchema: subtree
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
if (p.schemaDepth !== void 0) tool = {
|
|
2054
|
+
...tool,
|
|
2055
|
+
inputSchema: collapseSchema(tool.inputSchema ?? {}, p.schemaDepth).schema
|
|
2056
|
+
};
|
|
1767
2057
|
return {
|
|
1768
2058
|
jsonrpc: "2.0",
|
|
1769
2059
|
id,
|
|
1770
|
-
result:
|
|
2060
|
+
result: tool
|
|
1771
2061
|
};
|
|
1772
2062
|
}
|
|
1773
2063
|
case "tools/validate": {
|
|
@@ -1845,10 +2135,36 @@ function createDaemonServer(serverPool, config) {
|
|
|
1845
2135
|
}
|
|
1846
2136
|
};
|
|
1847
2137
|
try {
|
|
2138
|
+
const matches = serverPool.grepTools(p.pattern);
|
|
2139
|
+
if (p.includeSchema) {
|
|
2140
|
+
const schemas = matches.map(({ tool }) => tool.inputSchema ?? {});
|
|
2141
|
+
const depth = p.schemaDepth ?? autoDepth(schemas, p.schemaBudget).depth;
|
|
2142
|
+
return {
|
|
2143
|
+
jsonrpc: "2.0",
|
|
2144
|
+
id,
|
|
2145
|
+
result: matches.map(({ server, tool }) => ({
|
|
2146
|
+
server,
|
|
2147
|
+
tool: {
|
|
2148
|
+
name: tool.name,
|
|
2149
|
+
title: tool.title,
|
|
2150
|
+
description: tool.description,
|
|
2151
|
+
annotations: tool.annotations,
|
|
2152
|
+
inputSchema: collapseSchema(tool.inputSchema ?? {}, depth).schema
|
|
2153
|
+
}
|
|
2154
|
+
}))
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1848
2157
|
return {
|
|
1849
2158
|
jsonrpc: "2.0",
|
|
1850
2159
|
id,
|
|
1851
|
-
result:
|
|
2160
|
+
result: matches.map(({ server, tool }) => ({
|
|
2161
|
+
server,
|
|
2162
|
+
tool: {
|
|
2163
|
+
name: tool.name,
|
|
2164
|
+
title: tool.title,
|
|
2165
|
+
annotations: tool.annotations
|
|
2166
|
+
}
|
|
2167
|
+
}))
|
|
1852
2168
|
};
|
|
1853
2169
|
} catch (err) {
|
|
1854
2170
|
return {
|
|
@@ -2536,16 +2852,32 @@ async function ensureDaemon(configPath) {
|
|
|
2536
2852
|
400
|
|
2537
2853
|
]);
|
|
2538
2854
|
}
|
|
2539
|
-
async function sendRequest(method, params) {
|
|
2855
|
+
async function sendRequest(method, params, timeout = 9e4) {
|
|
2540
2856
|
const socketPath = getSocketPath();
|
|
2541
2857
|
return new Promise((resolve, reject) => {
|
|
2542
2858
|
const socket = net.createConnection(socketPath);
|
|
2543
2859
|
let buffer = "";
|
|
2860
|
+
let settled = false;
|
|
2861
|
+
const timer = setTimeout(() => {
|
|
2862
|
+
if (settled) return;
|
|
2863
|
+
settled = true;
|
|
2864
|
+
socket.destroy();
|
|
2865
|
+
reject(/* @__PURE__ */ new Error(`Request timed out after ${timeout}ms`));
|
|
2866
|
+
}, timeout);
|
|
2544
2867
|
socket.on("error", (err) => {
|
|
2868
|
+
if (settled) return;
|
|
2869
|
+
settled = true;
|
|
2870
|
+
clearTimeout(timer);
|
|
2545
2871
|
if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `muxed status` to check."));
|
|
2546
2872
|
else if (err.code === "ECONNREFUSED") reject(/* @__PURE__ */ new Error("Daemon may have crashed. Try running a command to auto-restart it."));
|
|
2547
2873
|
else reject(err);
|
|
2548
2874
|
});
|
|
2875
|
+
socket.on("close", () => {
|
|
2876
|
+
if (settled) return;
|
|
2877
|
+
settled = true;
|
|
2878
|
+
clearTimeout(timer);
|
|
2879
|
+
reject(/* @__PURE__ */ new Error("Daemon closed the connection before sending a response"));
|
|
2880
|
+
});
|
|
2549
2881
|
socket.on("connect", () => {
|
|
2550
2882
|
const request = {
|
|
2551
2883
|
jsonrpc: "2.0",
|
|
@@ -2559,6 +2891,9 @@ async function sendRequest(method, params) {
|
|
|
2559
2891
|
buffer += data.toString();
|
|
2560
2892
|
const newlineIndex = buffer.indexOf("\n");
|
|
2561
2893
|
if (newlineIndex === -1) return;
|
|
2894
|
+
if (settled) return;
|
|
2895
|
+
settled = true;
|
|
2896
|
+
clearTimeout(timer);
|
|
2562
2897
|
const line = buffer.slice(0, newlineIndex).trim();
|
|
2563
2898
|
socket.destroy();
|
|
2564
2899
|
try {
|
|
@@ -2604,7 +2939,6 @@ function formatTools(tools) {
|
|
|
2604
2939
|
return formatTable([
|
|
2605
2940
|
"Tool",
|
|
2606
2941
|
"Title",
|
|
2607
|
-
"Description",
|
|
2608
2942
|
"Hints"
|
|
2609
2943
|
], tools.map(({ server, tool }) => {
|
|
2610
2944
|
const hints = [];
|
|
@@ -2614,7 +2948,6 @@ function formatTools(tools) {
|
|
|
2614
2948
|
return [
|
|
2615
2949
|
`${server}/${tool.name}`,
|
|
2616
2950
|
tool.title ?? "—",
|
|
2617
|
-
truncate(tool.description ?? "", 60),
|
|
2618
2951
|
hints.join(" ")
|
|
2619
2952
|
];
|
|
2620
2953
|
}));
|
|
@@ -2848,6 +3181,24 @@ function formatInit(result) {
|
|
|
2848
3181
|
lines.push("");
|
|
2849
3182
|
lines.push("All discovered servers already exist in muxed config. Nothing to do.");
|
|
2850
3183
|
}
|
|
3184
|
+
if (result.instructionResults && result.instructionResults.length > 0) {
|
|
3185
|
+
lines.push("");
|
|
3186
|
+
lines.push("Instructions:");
|
|
3187
|
+
for (const r of result.instructionResults) switch (r.action) {
|
|
3188
|
+
case "created":
|
|
3189
|
+
lines.push(` ${r.target} \u2014 created (v${r.newVersion})`);
|
|
3190
|
+
break;
|
|
3191
|
+
case "updated":
|
|
3192
|
+
lines.push(` ${r.target} \u2014 updated (v${r.previousVersion} \u2192 v${r.newVersion})`);
|
|
3193
|
+
break;
|
|
3194
|
+
case "up-to-date":
|
|
3195
|
+
lines.push(` ${r.target} \u2014 up-to-date (v${r.previousVersion})`);
|
|
3196
|
+
break;
|
|
3197
|
+
case "skipped":
|
|
3198
|
+
lines.push(` ${r.target} \u2014 skipped`);
|
|
3199
|
+
break;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
2851
3202
|
return lines.join("\n");
|
|
2852
3203
|
}
|
|
2853
3204
|
function formatMcpServer(name, config, scope) {
|
|
@@ -3016,20 +3367,27 @@ async function shutdown() {
|
|
|
3016
3367
|
if (_client) await _client.shutdown();
|
|
3017
3368
|
} catch {}
|
|
3018
3369
|
}
|
|
3019
|
-
const toolsCommand = new Command("tools").description("List all available tools, optionally filtered by server name").argument("[server]", "Filter by server name").option("--json", "Output as JSON").action(async (server, opts) => {
|
|
3370
|
+
const toolsCommand = new Command("tools").description("List all available tools, optionally filtered by server name").argument("[server]", "Filter by server name").option("--json", "Output as JSON").option("--include <fields>", "Include additional fields (e.g. \"schema\")").option("--depth <n>", "Schema collapse depth (requires --include schema)", parseInt).action(async (server, opts) => {
|
|
3020
3371
|
const configPath = toolsCommand.parent?.opts().config;
|
|
3021
3372
|
await ensureDaemon(configPath);
|
|
3022
|
-
const
|
|
3373
|
+
const params = {};
|
|
3374
|
+
if (server) params.server = server;
|
|
3375
|
+
if (opts.include === "schema") params.includeSchema = true;
|
|
3376
|
+
if (opts.depth !== void 0) params.schemaDepth = opts.depth;
|
|
3377
|
+
const result = await sendRequest("tools/list", params);
|
|
3023
3378
|
capture("tools_listed", {
|
|
3024
3379
|
filtered_by_server: !!server,
|
|
3025
3380
|
tool_count: result.length
|
|
3026
3381
|
});
|
|
3027
3382
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3028
3383
|
});
|
|
3029
|
-
const infoCommand = new Command("info").description("Show input schema and description for a specific tool").argument("<server/tool>", "Tool identifier (e.g. myserver/mytool)").option("--json", "Output as JSON").action(async (serverTool, opts) => {
|
|
3384
|
+
const infoCommand = new Command("info").description("Show input schema and description for a specific tool").argument("<server/tool>", "Tool identifier (e.g. myserver/mytool)").option("--json", "Output as JSON").option("--path <path>", "Extract a subtree of the input schema (e.g. \"filters.tags\")").option("--depth <n>", "Collapse schema at this depth", parseInt).action(async (serverTool, opts) => {
|
|
3030
3385
|
const configPath = infoCommand.parent?.opts().config;
|
|
3031
3386
|
await ensureDaemon(configPath);
|
|
3032
|
-
const
|
|
3387
|
+
const params = { name: serverTool };
|
|
3388
|
+
if (opts.path) params.path = opts.path;
|
|
3389
|
+
if (opts.depth !== void 0) params.schemaDepth = opts.depth;
|
|
3390
|
+
const result = await sendRequest("tools/info", params);
|
|
3033
3391
|
if (opts.json) console.log(formatJson(result));
|
|
3034
3392
|
else {
|
|
3035
3393
|
const slashIndex = serverTool.indexOf("/");
|
|
@@ -3187,10 +3545,13 @@ const callCommand = new Command("call").description("Execute a tool with JSON ar
|
|
|
3187
3545
|
process.exit(1);
|
|
3188
3546
|
}
|
|
3189
3547
|
});
|
|
3190
|
-
const grepCommand = new Command("grep").description("Search tools by regex pattern across names, titles, and descriptions").argument("<pattern>", "Regex pattern to search").option("--json", "Output as JSON").action(async (pattern, opts) => {
|
|
3548
|
+
const grepCommand = new Command("grep").description("Search tools by regex pattern across names, titles, and descriptions").argument("<pattern>", "Regex pattern to search").option("--json", "Output as JSON").option("--include <fields>", "Include additional fields (e.g. \"schema\")").option("--depth <n>", "Schema collapse depth (requires --include schema)", parseInt).action(async (pattern, opts) => {
|
|
3191
3549
|
const configPath = grepCommand.parent?.opts().config;
|
|
3192
3550
|
await ensureDaemon(configPath);
|
|
3193
|
-
const
|
|
3551
|
+
const params = { pattern };
|
|
3552
|
+
if (opts.include === "schema") params.includeSchema = true;
|
|
3553
|
+
if (opts.depth !== void 0) params.schemaDepth = opts.depth;
|
|
3554
|
+
const result = await sendRequest("tools/grep", params);
|
|
3194
3555
|
capture("tools_searched", { result_count: result.length });
|
|
3195
3556
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3196
3557
|
});
|
|
@@ -3665,6 +4026,261 @@ function getMuxedConfigPath(scope, explicitPath) {
|
|
|
3665
4026
|
if (scope === "local") return path.join(process.cwd(), "muxed.config.json");
|
|
3666
4027
|
return path.join(home, ".muxed", "config.json");
|
|
3667
4028
|
}
|
|
4029
|
+
let cached = null;
|
|
4030
|
+
function getVersion() {
|
|
4031
|
+
if (cached) return cached;
|
|
4032
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
4033
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
4034
|
+
const p = path.resolve(dir, rel);
|
|
4035
|
+
try {
|
|
4036
|
+
const pkg = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
4037
|
+
if (pkg.name === "muxed" && pkg.version) {
|
|
4038
|
+
cached = pkg.version;
|
|
4039
|
+
return cached;
|
|
4040
|
+
}
|
|
4041
|
+
} catch {}
|
|
4042
|
+
}
|
|
4043
|
+
return "0.0.0";
|
|
4044
|
+
}
|
|
4045
|
+
function compareSemver(a, b) {
|
|
4046
|
+
const pa = a.split(".").map(Number);
|
|
4047
|
+
const pb = b.split(".").map(Number);
|
|
4048
|
+
for (let i = 0; i < 3; i++) {
|
|
4049
|
+
const na = pa[i] ?? 0;
|
|
4050
|
+
const nb = pb[i] ?? 0;
|
|
4051
|
+
if (na < nb) return -1;
|
|
4052
|
+
if (na > nb) return 1;
|
|
4053
|
+
}
|
|
4054
|
+
return 0;
|
|
4055
|
+
}
|
|
4056
|
+
const MUXED_BLOCK_RE = /<muxed\s+version="([^"]+)">\n?([\s\S]*?)\n?<\/muxed>/;
|
|
4057
|
+
const MDC_VERSION_RE = /muxed_version:\s*(.+)/;
|
|
4058
|
+
function extractMuxedVersion(content, format) {
|
|
4059
|
+
if (format === "tagged") return content.match(MUXED_BLOCK_RE)?.[1]?.trim() ?? null;
|
|
4060
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4061
|
+
if (!fmMatch) return null;
|
|
4062
|
+
return fmMatch[1].match(MDC_VERSION_RE)?.[1]?.trim() ?? null;
|
|
4063
|
+
}
|
|
4064
|
+
function getInstructionTargets(opts) {
|
|
4065
|
+
const home = os.homedir();
|
|
4066
|
+
const cwd = process.cwd();
|
|
4067
|
+
const targets = [];
|
|
4068
|
+
targets.push({
|
|
4069
|
+
name: "CLAUDE.md (global)",
|
|
4070
|
+
filePath: path.join(home, ".claude", "CLAUDE.md"),
|
|
4071
|
+
format: "tagged",
|
|
4072
|
+
scope: "global"
|
|
4073
|
+
});
|
|
4074
|
+
targets.push({
|
|
4075
|
+
name: "AGENTS.md (global)",
|
|
4076
|
+
filePath: path.join(home, ".codex", "AGENTS.md"),
|
|
4077
|
+
format: "tagged",
|
|
4078
|
+
scope: "global"
|
|
4079
|
+
});
|
|
4080
|
+
const cursorDir = path.join(cwd, ".cursor");
|
|
4081
|
+
if (fs.existsSync(cursorDir)) targets.push({
|
|
4082
|
+
name: ".cursor/rules/muxed.mdc",
|
|
4083
|
+
filePath: path.join(cursorDir, "rules", "muxed.mdc"),
|
|
4084
|
+
format: "owned",
|
|
4085
|
+
scope: "local"
|
|
4086
|
+
});
|
|
4087
|
+
if (opts.local) {
|
|
4088
|
+
targets.push({
|
|
4089
|
+
name: "CLAUDE.md (local)",
|
|
4090
|
+
filePath: path.join(cwd, "CLAUDE.md"),
|
|
4091
|
+
format: "tagged",
|
|
4092
|
+
scope: "local"
|
|
4093
|
+
});
|
|
4094
|
+
targets.push({
|
|
4095
|
+
name: "AGENTS.md (local)",
|
|
4096
|
+
filePath: path.join(cwd, "AGENTS.md"),
|
|
4097
|
+
format: "tagged",
|
|
4098
|
+
scope: "local"
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
return targets;
|
|
4102
|
+
}
|
|
4103
|
+
let cachedHasBun = null;
|
|
4104
|
+
function hasBun() {
|
|
4105
|
+
if (cachedHasBun !== null) return cachedHasBun;
|
|
4106
|
+
try {
|
|
4107
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
4108
|
+
cachedHasBun = true;
|
|
4109
|
+
} catch {
|
|
4110
|
+
cachedHasBun = false;
|
|
4111
|
+
}
|
|
4112
|
+
return cachedHasBun;
|
|
4113
|
+
}
|
|
4114
|
+
function buildStaticInstructions() {
|
|
4115
|
+
const bun = hasBun();
|
|
4116
|
+
const run = bun ? "bunx" : "npx";
|
|
4117
|
+
return `# Muxed — MCP Server Aggregator
|
|
4118
|
+
|
|
4119
|
+
Muxed is a CLI tool and Node.js library that aggregates multiple MCP servers behind a single daemon. Use it to discover and call MCP tools on demand.
|
|
4120
|
+
|
|
4121
|
+
## CLI Usage
|
|
4122
|
+
|
|
4123
|
+
### Mandatory Workflow
|
|
4124
|
+
|
|
4125
|
+
**ALWAYS follow this order — never skip the inspect step.**
|
|
4126
|
+
|
|
4127
|
+
1. **Discover** tools:
|
|
4128
|
+
\`\`\`
|
|
4129
|
+
${run} muxed grep "<pattern>" # Search tool names and descriptions
|
|
4130
|
+
${run} muxed tools [server] # List available tools
|
|
4131
|
+
\`\`\`
|
|
4132
|
+
|
|
4133
|
+
2. **Inspect** schema (REQUIRED before calling):
|
|
4134
|
+
\`\`\`
|
|
4135
|
+
${run} muxed info <server>/<tool> # View tool JSON schema
|
|
4136
|
+
\`\`\`
|
|
4137
|
+
|
|
4138
|
+
3. **Call** with correct parameters:
|
|
4139
|
+
\`\`\`
|
|
4140
|
+
${run} muxed call <server>/<tool> '<json>' # Execute a tool
|
|
4141
|
+
\`\`\`
|
|
4142
|
+
|
|
4143
|
+
Tool names and parameter schemas change frequently and cannot be guessed. Every call without inspecting the schema first will fail.
|
|
4144
|
+
|
|
4145
|
+
### Additional Commands
|
|
4146
|
+
|
|
4147
|
+
\`\`\`
|
|
4148
|
+
${run} muxed servers # List connected MCP servers
|
|
4149
|
+
${run} muxed resources [server] # List MCP resources
|
|
4150
|
+
${run} muxed read <server>/<resource> # Read an MCP resource
|
|
4151
|
+
${run} muxed call <s>/<t> '<j>' --dry-run # Validate args without executing
|
|
4152
|
+
${run} muxed call <s>/<t> '<j>' --fields "a,b" # Extract specific fields
|
|
4153
|
+
${run} muxed -h # Full command reference
|
|
4154
|
+
\`\`\`
|
|
4155
|
+
|
|
4156
|
+
### Error Handling
|
|
4157
|
+
|
|
4158
|
+
- If a tool call fails, read the error — it includes suggestions and similar tool names.
|
|
4159
|
+
- Use \`--dry-run\` to validate arguments before executing destructive tools.
|
|
4160
|
+
|
|
4161
|
+
## Node.js / TypeScript Scripts (Preferred for Complex Workflows)
|
|
4162
|
+
|
|
4163
|
+
For multi-step MCP workflows, **write and execute a script** instead of making individual CLI calls. A single script execution replaces many sequential CLI invocations — dramatically reducing round-trips and token usage.
|
|
4164
|
+
|
|
4165
|
+
\`\`\`typescript
|
|
4166
|
+
import { createClient } from 'muxed/client';
|
|
4167
|
+
|
|
4168
|
+
const client = await createClient();
|
|
4169
|
+
|
|
4170
|
+
// Batch multiple MCP operations in one script execution
|
|
4171
|
+
const tools = await client.tools();
|
|
4172
|
+
const result = await client.call('server/tool', { param: 'value' });
|
|
4173
|
+
const data = await client.call('db/query', { sql: 'SELECT ...' });
|
|
4174
|
+
|
|
4175
|
+
// Process results, chain calls, handle errors — all in one execution
|
|
4176
|
+
console.log(JSON.stringify({ tools: tools.length, result, data }));
|
|
4177
|
+
\`\`\`
|
|
4178
|
+
|
|
4179
|
+
Run scripts with: \`${bun ? "bun" : "npx tsx"} script.ts\`.
|
|
4180
|
+
|
|
4181
|
+
**When to use scripts vs CLI:**
|
|
4182
|
+
- **CLI** (\`${run} muxed call ...\`) — single tool discovery or one-off calls
|
|
4183
|
+
- **Scripts** — any workflow involving 2+ MCP calls, data processing, or conditional logic`;
|
|
4184
|
+
}
|
|
4185
|
+
function wrapTaggedBlock(content, version) {
|
|
4186
|
+
return `<muxed version="${version}">\n${content}\n</muxed>`;
|
|
4187
|
+
}
|
|
4188
|
+
function buildMdcFile(content, version) {
|
|
4189
|
+
return `---
|
|
4190
|
+
description: Muxed MCP server aggregator - CLI usage instructions
|
|
4191
|
+
globs:
|
|
4192
|
+
alwaysApply: true
|
|
4193
|
+
muxed_version: ${version}
|
|
4194
|
+
---
|
|
4195
|
+
|
|
4196
|
+
${content}
|
|
4197
|
+
`;
|
|
4198
|
+
}
|
|
4199
|
+
function injectInstructions(target, instructions, version, opts) {
|
|
4200
|
+
const base = {
|
|
4201
|
+
target: target.name,
|
|
4202
|
+
filePath: target.filePath
|
|
4203
|
+
};
|
|
4204
|
+
let existing = null;
|
|
4205
|
+
if (fs.existsSync(target.filePath)) existing = fs.readFileSync(target.filePath, "utf-8");
|
|
4206
|
+
if (target.format === "owned") {
|
|
4207
|
+
if (existing !== null) {
|
|
4208
|
+
const existingVersion = extractMuxedVersion(existing, "owned");
|
|
4209
|
+
if (existingVersion && compareSemver(existingVersion, version) >= 0) return {
|
|
4210
|
+
...base,
|
|
4211
|
+
action: "up-to-date",
|
|
4212
|
+
previousVersion: existingVersion
|
|
4213
|
+
};
|
|
4214
|
+
if (!opts.dryRun) {
|
|
4215
|
+
const dir = path.dirname(target.filePath);
|
|
4216
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4217
|
+
fs.writeFileSync(target.filePath, buildMdcFile(instructions, version));
|
|
4218
|
+
}
|
|
4219
|
+
return {
|
|
4220
|
+
...base,
|
|
4221
|
+
action: existingVersion ? "updated" : "created",
|
|
4222
|
+
previousVersion: existingVersion ?? void 0,
|
|
4223
|
+
newVersion: version
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
if (!opts.dryRun) {
|
|
4227
|
+
const dir = path.dirname(target.filePath);
|
|
4228
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4229
|
+
fs.writeFileSync(target.filePath, buildMdcFile(instructions, version));
|
|
4230
|
+
}
|
|
4231
|
+
return {
|
|
4232
|
+
...base,
|
|
4233
|
+
action: "created",
|
|
4234
|
+
newVersion: version
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
const block = wrapTaggedBlock(instructions, version);
|
|
4238
|
+
if (existing === null) {
|
|
4239
|
+
if (!opts.dryRun) {
|
|
4240
|
+
const dir = path.dirname(target.filePath);
|
|
4241
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4242
|
+
fs.writeFileSync(target.filePath, block + "\n");
|
|
4243
|
+
}
|
|
4244
|
+
return {
|
|
4245
|
+
...base,
|
|
4246
|
+
action: "created",
|
|
4247
|
+
newVersion: version
|
|
4248
|
+
};
|
|
4249
|
+
}
|
|
4250
|
+
const existingVersion = extractMuxedVersion(existing, "tagged");
|
|
4251
|
+
if (existingVersion !== null) {
|
|
4252
|
+
if (compareSemver(existingVersion, version) >= 0) return {
|
|
4253
|
+
...base,
|
|
4254
|
+
action: "up-to-date",
|
|
4255
|
+
previousVersion: existingVersion
|
|
4256
|
+
};
|
|
4257
|
+
if (!opts.dryRun) {
|
|
4258
|
+
const updated = existing.replace(MUXED_BLOCK_RE, block);
|
|
4259
|
+
fs.writeFileSync(target.filePath, updated);
|
|
4260
|
+
}
|
|
4261
|
+
return {
|
|
4262
|
+
...base,
|
|
4263
|
+
action: "updated",
|
|
4264
|
+
previousVersion: existingVersion,
|
|
4265
|
+
newVersion: version
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
if (!opts.dryRun) {
|
|
4269
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4270
|
+
fs.writeFileSync(target.filePath, existing + separator + block + "\n");
|
|
4271
|
+
}
|
|
4272
|
+
return {
|
|
4273
|
+
...base,
|
|
4274
|
+
action: "created",
|
|
4275
|
+
newVersion: version
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4278
|
+
function injectAllInstructions(opts) {
|
|
4279
|
+
const targets = getInstructionTargets({ local: opts.local });
|
|
4280
|
+
const instructions = buildStaticInstructions();
|
|
4281
|
+
const version = getVersion();
|
|
4282
|
+
return targets.map((target) => injectInstructions(target, instructions, version, opts));
|
|
4283
|
+
}
|
|
3668
4284
|
async function confirm(message, opts) {
|
|
3669
4285
|
const rl = readline.createInterface({
|
|
3670
4286
|
input: opts?.input ?? process.stdin,
|
|
@@ -3732,7 +4348,7 @@ async function resolveConflicts(unresolvedConflicts, isInteractive) {
|
|
|
3732
4348
|
conflicts
|
|
3733
4349
|
};
|
|
3734
4350
|
}
|
|
3735
|
-
const initCommand = new Command("init").description("Discover and import MCP servers from agent configs (Claude Code, Cursor)").option("--dry-run", "Show what would be done without writing files").option("--json", "Output as JSON").option("-y, --yes", "Skip prompts; resolve conflicts by priority (claude-code > cursor > first)").option("--
|
|
4351
|
+
const initCommand = new Command("init").description("Discover and import MCP servers from agent configs (Claude Code, Cursor)").option("--dry-run", "Show what would be done without writing files").option("--json", "Output as JSON").option("-y, --yes", "Skip prompts; resolve conflicts by priority (claude-code > cursor > first)").option("--delete", "Remove imported servers from original agent configs").option("--no-replace", "Don't add muxed entry to agent configs").option("--local", "Also inject instructions into local agent files (CLAUDE.md, AGENTS.md)").option("--no-instructions", "Skip injecting CLI instructions into agent files").action(async (opts) => {
|
|
3736
4352
|
const configPath = initCommand.parent?.opts().config;
|
|
3737
4353
|
const isInteractive = !opts.dryRun && !opts.json && !opts.yes && !!process.stdin.isTTY;
|
|
3738
4354
|
const { discovered, warnings } = discoverAgentConfigs();
|
|
@@ -3755,7 +4371,7 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3755
4371
|
}
|
|
3756
4372
|
if (!opts.dryRun && imported.length > 0) writeMuxedConfig(muxedPath, result.merged);
|
|
3757
4373
|
const modifiedFiles = [];
|
|
3758
|
-
const shouldDelete = isInteractive ? await confirm("Remove imported servers from agent config files? (backups will be created)") : opts.delete;
|
|
4374
|
+
const shouldDelete = isInteractive ? await confirm("Remove imported servers from agent config files? (backups will be created)") : !!opts.delete;
|
|
3759
4375
|
if (!opts.dryRun && shouldDelete) for (const dc of discovered) try {
|
|
3760
4376
|
modifyAgentConfig(dc, {
|
|
3761
4377
|
delete: true,
|
|
@@ -3765,6 +4381,10 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3765
4381
|
} catch (err) {
|
|
3766
4382
|
warnings.push(`Failed to modify ${dc.configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3767
4383
|
}
|
|
4384
|
+
const instructionResults = (isInteractive ? await confirm("Inject muxed CLI instructions into agent files? (CLAUDE.md, AGENTS.md)") : opts.instructions) ? injectAllInstructions({
|
|
4385
|
+
local: !!opts.local,
|
|
4386
|
+
dryRun: !!opts.dryRun
|
|
4387
|
+
}) : [];
|
|
3768
4388
|
const initResult = {
|
|
3769
4389
|
discovered: discovered.map((d) => ({
|
|
3770
4390
|
agent: d.agent.name,
|
|
@@ -3778,14 +4398,17 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3778
4398
|
warnings,
|
|
3779
4399
|
modifiedFiles,
|
|
3780
4400
|
muxedConfigPath: muxedPath,
|
|
3781
|
-
dryRun: opts.dryRun ?? false
|
|
4401
|
+
dryRun: opts.dryRun ?? false,
|
|
4402
|
+
instructionResults
|
|
3782
4403
|
};
|
|
3783
4404
|
capture("init_run", {
|
|
3784
4405
|
dry_run: opts.dryRun ?? false,
|
|
3785
4406
|
imported_count: imported.length,
|
|
3786
4407
|
conflict_count: conflicts.length,
|
|
3787
4408
|
warning_count: warnings.length,
|
|
3788
|
-
discovered_agents: initResult.discovered.map((d) => d.agent)
|
|
4409
|
+
discovered_agents: initResult.discovered.map((d) => d.agent),
|
|
4410
|
+
instruction_targets: instructionResults.length,
|
|
4411
|
+
instruction_actions: instructionResults.map((r) => r.action)
|
|
3789
4412
|
});
|
|
3790
4413
|
console.log(opts.json ? formatJson(initResult) : formatInit(initResult));
|
|
3791
4414
|
});
|
|
@@ -3842,7 +4465,10 @@ const cliFragments = {
|
|
|
3842
4465
|
intro: "You have access to an `npx muxed` CLI command for interacting with MCP (Model Context Protocol) servers. This command allows you to discover and call MCP tools on demand. Prioritize the use of skills over MCP tools.",
|
|
3843
4466
|
grep: (p) => `npx muxed grep "${p}"`,
|
|
3844
4467
|
tools: (s) => s ? `npx muxed tools ${s}` : "npx muxed tools",
|
|
4468
|
+
toolsSchema: (s) => s ? `npx muxed tools ${s} --include schema` : "npx muxed tools --include schema",
|
|
3845
4469
|
info: (n) => `npx muxed info ${n}`,
|
|
4470
|
+
infoDepth: (n, d) => `npx muxed info ${n} --depth ${d}`,
|
|
4471
|
+
infoPath: (n, p) => `npx muxed info ${n} --path ${p}`,
|
|
3846
4472
|
call: (n, j) => `npx muxed call ${n} '${j}'`,
|
|
3847
4473
|
callStdin: (n) => `npx muxed call ${n} -`,
|
|
3848
4474
|
callDryRun: (n, j) => `npx muxed call ${n} '${j}' --dry-run`,
|
|
@@ -3856,7 +4482,10 @@ const toolFragments = {
|
|
|
3856
4482
|
intro: "You have access to a `muxed:exec` MCP tool for interacting with MCP (Model Context Protocol) servers. This tool allows you to discover and call MCP tools on demand. Prioritize the use of skills over MCP tools.",
|
|
3857
4483
|
grep: (p) => `muxed:exec({ "command": "grep ${p}" })`,
|
|
3858
4484
|
tools: (s) => s ? `muxed:exec({ "command": "tools ${s}" })` : `muxed:exec({ "command": "tools" })`,
|
|
4485
|
+
toolsSchema: (s) => s ? `muxed:exec({ "command": "tools ${s} --include schema" })` : `muxed:exec({ "command": "tools --include schema" })`,
|
|
3859
4486
|
info: (n) => `muxed:exec({ "command": "info ${n}" })`,
|
|
4487
|
+
infoDepth: (n, d) => `muxed:exec({ "command": "info ${n} --depth ${d}" })`,
|
|
4488
|
+
infoPath: (n, p) => `muxed:exec({ "command": "info ${n} --path ${p}" })`,
|
|
3860
4489
|
call: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
3861
4490
|
callStdin: (n) => `muxed:exec({ "command": "call ${n}", "input": { ... } })`,
|
|
3862
4491
|
callDryRun: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
@@ -3898,15 +4527,23 @@ Commands (in order of execution):
|
|
|
3898
4527
|
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
3899
4528
|
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
3900
4529
|
|
|
3901
|
-
# STEP 2:
|
|
3902
|
-
|
|
4530
|
+
# STEP 2: GET SCHEMA (choose one approach)
|
|
4531
|
+
# Option A: Include schemas in tool listing (auto-collapses to fit 48k budget)
|
|
4532
|
+
${f.toolsSchema("[server]")} # List tools with schemas included
|
|
4533
|
+
# Option B: Get full schema for a specific tool
|
|
4534
|
+
${f.info("<server>/<tool>")} # View full JSON schema for one tool
|
|
4535
|
+
|
|
4536
|
+
# STEP 2b: PROGRESSIVE SCHEMA EXPLORATION (for large schemas)
|
|
4537
|
+
${f.infoDepth("<server>/<tool>", 1)} # Collapse schema at depth 1 (top-level overview)
|
|
4538
|
+
${f.infoPath("<server>/<tool>", "filters")} # Extract just the 'filters' subtree
|
|
4539
|
+
${f.infoPath("<server>/<tool>", "filters.tags.items")} # Drill deeper into nested schemas
|
|
3903
4540
|
|
|
3904
4541
|
# STEP 3: OPTIONAL - Validate arguments before calling (dry-run)
|
|
3905
4542
|
${f.callDryRun("<server>/<tool>", "<json>")} # Validate args without executing
|
|
3906
4543
|
|
|
3907
|
-
# STEP 4: Only after
|
|
3908
|
-
${f.call("<server>/<tool>", "<json>")} # Only run AFTER
|
|
3909
|
-
${f.callStdin("<server>/<tool>")} # Invoke with JSON input
|
|
4544
|
+
# STEP 4: Only after getting the schema, make the call
|
|
4545
|
+
${f.call("<server>/<tool>", "<json>")} # Only run AFTER getting schema
|
|
4546
|
+
${f.callStdin("<server>/<tool>")} # Invoke with JSON input
|
|
3910
4547
|
${f.callFields("<server>/<tool>", "<json>", "field1,field2")} # Extract specific fields from response
|
|
3911
4548
|
|
|
3912
4549
|
# Discovery commands (use these to find tools)
|
|
@@ -3984,8 +4621,15 @@ Example usage:
|
|
|
3984
4621
|
${f.tools()} # See all available MCP tools
|
|
3985
4622
|
${f.grep("weather")} # Find tools by description
|
|
3986
4623
|
|
|
3987
|
-
# Get tool
|
|
3988
|
-
${f.
|
|
4624
|
+
# Get tool schemas (choose the approach that fits)
|
|
4625
|
+
${f.toolsSchema()} # All tools with schemas (auto-collapses large schemas)
|
|
4626
|
+
${f.toolsSchema("slack")} # Schemas for one server
|
|
4627
|
+
${f.info("<server>/<tool>")} # Full schema for one tool
|
|
4628
|
+
|
|
4629
|
+
# Progressive schema exploration (for complex tools)
|
|
4630
|
+
${f.infoDepth("<server>/<tool>", 0)} # Top-level structure only
|
|
4631
|
+
${f.infoPath("<server>/<tool>", "filters")} # Drill into a subtree
|
|
4632
|
+
${f.infoPath("<server>/<tool>", "filters.tags.items")} # Drill deeper
|
|
3989
4633
|
|
|
3990
4634
|
# Simple tool call (no parameters)
|
|
3991
4635
|
${f.call("weather/get_location", "{}")}
|
|
@@ -4025,6 +4669,22 @@ function parseCommand(command) {
|
|
|
4025
4669
|
args: trimmed.slice(spaceIndex + 1).trim()
|
|
4026
4670
|
};
|
|
4027
4671
|
}
|
|
4672
|
+
function parseFlags(args) {
|
|
4673
|
+
const flags = {};
|
|
4674
|
+
const positionalParts = [];
|
|
4675
|
+
const parts = args.split(/\s+/);
|
|
4676
|
+
for (let i = 0; i < parts.length; i++) {
|
|
4677
|
+
const part = parts[i];
|
|
4678
|
+
if (part.startsWith("--") && i + 1 < parts.length) {
|
|
4679
|
+
const key = part.slice(2);
|
|
4680
|
+
flags[key] = parts[++i];
|
|
4681
|
+
} else positionalParts.push(part);
|
|
4682
|
+
}
|
|
4683
|
+
return {
|
|
4684
|
+
positional: positionalParts.join(" "),
|
|
4685
|
+
flags
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4028
4688
|
function textResult(data) {
|
|
4029
4689
|
return { content: [{
|
|
4030
4690
|
type: "text",
|
|
@@ -4046,16 +4706,31 @@ async function handleToolCommand(command, input) {
|
|
|
4046
4706
|
switch (subcommand) {
|
|
4047
4707
|
case "servers": return textResult(await sendRequest("servers/list"));
|
|
4048
4708
|
case "tools": {
|
|
4709
|
+
const { positional, flags } = parseFlags(args);
|
|
4049
4710
|
const params = {};
|
|
4050
|
-
if (
|
|
4711
|
+
if (positional) params.server = positional;
|
|
4712
|
+
if (flags.include === "schema") params.includeSchema = true;
|
|
4713
|
+
if (flags.depth) params.schemaDepth = parseInt(flags.depth, 10);
|
|
4051
4714
|
return textResult(await sendRequest("tools/list", params));
|
|
4052
4715
|
}
|
|
4053
|
-
case "grep":
|
|
4716
|
+
case "grep": {
|
|
4054
4717
|
if (!args) return errorResult("Usage: grep <pattern>");
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4718
|
+
const { positional, flags } = parseFlags(args);
|
|
4719
|
+
if (!positional) return errorResult("Usage: grep <pattern>");
|
|
4720
|
+
const params = { pattern: positional };
|
|
4721
|
+
if (flags.include === "schema") params.includeSchema = true;
|
|
4722
|
+
if (flags.depth) params.schemaDepth = parseInt(flags.depth, 10);
|
|
4723
|
+
return textResult(await sendRequest("tools/grep", params));
|
|
4724
|
+
}
|
|
4725
|
+
case "info": {
|
|
4726
|
+
if (!args) return errorResult("Usage: info <server/tool> [--path <path>] [--depth <n>]");
|
|
4727
|
+
const { positional, flags } = parseFlags(args);
|
|
4728
|
+
if (!positional) return errorResult("Usage: info <server/tool>");
|
|
4729
|
+
const params = { name: positional };
|
|
4730
|
+
if (flags.path) params.path = flags.path;
|
|
4731
|
+
if (flags.depth) params.schemaDepth = parseInt(flags.depth, 10);
|
|
4732
|
+
return textResult(await sendRequest("tools/info", params));
|
|
4733
|
+
}
|
|
4059
4734
|
case "call": {
|
|
4060
4735
|
if (!args) return errorResult("Usage: call <server/tool>");
|
|
4061
4736
|
const result = await sendRequest("tools/call", {
|
|
@@ -4390,7 +5065,7 @@ const telemetryCommand = new Command("telemetry").description("Manage anonymous
|
|
|
4390
5065
|
});
|
|
4391
5066
|
async function runCli() {
|
|
4392
5067
|
const program = new Command();
|
|
4393
|
-
program.name("muxed").description("The optimization layer for MCP").version(
|
|
5068
|
+
program.name("muxed").description("The optimization layer for MCP").version(getVersion());
|
|
4394
5069
|
program.enablePositionalOptions();
|
|
4395
5070
|
program.option("--config <path>", "Path to config file");
|
|
4396
5071
|
program.commandsGroup("Servers:");
|