muxed 0.2.0 → 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 +743 -57
- 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,
|
|
@@ -474,6 +477,7 @@ var AuthorizationCodeProvider = class {
|
|
|
474
477
|
config;
|
|
475
478
|
_redirectUrl;
|
|
476
479
|
hadTokensBefore = false;
|
|
480
|
+
_state = crypto.randomBytes(32).toString("base64url");
|
|
477
481
|
constructor(config, serverName) {
|
|
478
482
|
this.serverName = serverName;
|
|
479
483
|
this.config = config;
|
|
@@ -481,14 +485,14 @@ var AuthorizationCodeProvider = class {
|
|
|
481
485
|
this.hadTokensBefore = this.store.hasTokens();
|
|
482
486
|
}
|
|
483
487
|
setRedirectUrl(port) {
|
|
484
|
-
this._redirectUrl = `http://
|
|
488
|
+
this._redirectUrl = `http://localhost:${port}/callback`;
|
|
485
489
|
}
|
|
486
490
|
get redirectUrl() {
|
|
487
491
|
return this._redirectUrl;
|
|
488
492
|
}
|
|
489
493
|
get clientMetadata() {
|
|
490
494
|
return {
|
|
491
|
-
redirect_uris: [this._redirectUrl ?? "http://
|
|
495
|
+
redirect_uris: [this._redirectUrl ?? "http://localhost/callback"],
|
|
492
496
|
token_endpoint_auth_method: this.config.clientSecret ? "client_secret_basic" : "none",
|
|
493
497
|
grant_types: ["authorization_code", "refresh_token"],
|
|
494
498
|
response_types: ["code"],
|
|
@@ -501,7 +505,14 @@ var AuthorizationCodeProvider = class {
|
|
|
501
505
|
client_id: this.config.clientId,
|
|
502
506
|
...this.config.clientSecret ? { client_secret: this.config.clientSecret } : {}
|
|
503
507
|
};
|
|
504
|
-
|
|
508
|
+
const cached = this.store.getClientInformation();
|
|
509
|
+
if (cached && this._redirectUrl) {
|
|
510
|
+
if (!(cached.redirect_uris ?? []).includes(this._redirectUrl)) {
|
|
511
|
+
this.store.clearClientInformation();
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return cached;
|
|
505
516
|
}
|
|
506
517
|
saveClientInformation(info) {
|
|
507
518
|
this.store.saveClientInformation(info);
|
|
@@ -522,6 +533,9 @@ var AuthorizationCodeProvider = class {
|
|
|
522
533
|
openBrowser(url);
|
|
523
534
|
}
|
|
524
535
|
}
|
|
536
|
+
async state() {
|
|
537
|
+
return this._state;
|
|
538
|
+
}
|
|
525
539
|
saveCodeVerifier(codeVerifier) {
|
|
526
540
|
this.store.saveCodeVerifier(codeVerifier);
|
|
527
541
|
}
|
|
@@ -580,8 +594,8 @@ var CallbackServer = class {
|
|
|
580
594
|
res.end();
|
|
581
595
|
return;
|
|
582
596
|
}
|
|
583
|
-
const url = new URL(req.url ?? "/", `http://
|
|
584
|
-
if (url.pathname !== "/
|
|
597
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
598
|
+
if (url.pathname !== "/callback") {
|
|
585
599
|
res.writeHead(404);
|
|
586
600
|
res.end("Not found");
|
|
587
601
|
return;
|
|
@@ -615,7 +629,7 @@ var CallbackServer = class {
|
|
|
615
629
|
state
|
|
616
630
|
});
|
|
617
631
|
});
|
|
618
|
-
this.server.listen(port, "
|
|
632
|
+
this.server.listen(port, "localhost", () => {
|
|
619
633
|
const addr = this.server.address();
|
|
620
634
|
if (addr && typeof addr === "object") this._port = addr.port;
|
|
621
635
|
});
|
|
@@ -687,6 +701,7 @@ var ServerManager = class {
|
|
|
687
701
|
stopped = false;
|
|
688
702
|
connectTimeout;
|
|
689
703
|
healthCheckInterval;
|
|
704
|
+
healthCheckTimeout;
|
|
690
705
|
maxRestartAttempts;
|
|
691
706
|
onHealthChange;
|
|
692
707
|
constructor(name, config, options) {
|
|
@@ -694,6 +709,7 @@ var ServerManager = class {
|
|
|
694
709
|
this.config = config;
|
|
695
710
|
this.connectTimeout = options?.connectTimeout;
|
|
696
711
|
this.healthCheckInterval = options?.healthCheckInterval ?? 3e4;
|
|
712
|
+
this.healthCheckTimeout = options?.healthCheckTimeout ?? 1e4;
|
|
697
713
|
this.maxRestartAttempts = options?.maxRestartAttempts ?? -1;
|
|
698
714
|
}
|
|
699
715
|
setHealthCallback(cb) {
|
|
@@ -900,7 +916,7 @@ var ServerManager = class {
|
|
|
900
916
|
if (!this.client || this.status !== "connected") return;
|
|
901
917
|
this.lastHealthCheck = /* @__PURE__ */ new Date();
|
|
902
918
|
try {
|
|
903
|
-
await this.client.ping();
|
|
919
|
+
await this.client.ping({ timeout: this.healthCheckTimeout });
|
|
904
920
|
if (this.consecutiveFailures > 0) getLogger().info(`Health check recovered after ${this.consecutiveFailures} failures`, this.name);
|
|
905
921
|
this.consecutiveFailures = 0;
|
|
906
922
|
} catch (err) {
|
|
@@ -955,10 +971,14 @@ var ServerManager = class {
|
|
|
955
971
|
if (!this.client) return;
|
|
956
972
|
this.tools = (await this.client.listTools()).tools;
|
|
957
973
|
}
|
|
974
|
+
ensureConnected() {
|
|
975
|
+
if (!this.client || this.status !== "connected") throw new Error(`Server "${this.name}" is not connected`);
|
|
976
|
+
return this.client;
|
|
977
|
+
}
|
|
958
978
|
async callTool(name, args, timeout) {
|
|
959
|
-
|
|
979
|
+
const client = this.ensureConnected();
|
|
960
980
|
const options = timeout ? { signal: AbortSignal.timeout(timeout) } : void 0;
|
|
961
|
-
return await
|
|
981
|
+
return await client.callTool({
|
|
962
982
|
name,
|
|
963
983
|
arguments: args
|
|
964
984
|
}, void 0, options);
|
|
@@ -971,8 +991,7 @@ var ServerManager = class {
|
|
|
971
991
|
this.resources = (await this.client.listResources()).resources;
|
|
972
992
|
}
|
|
973
993
|
async readResource(uri) {
|
|
974
|
-
|
|
975
|
-
return await this.client.readResource({ uri });
|
|
994
|
+
return await this.ensureConnected().readResource({ uri });
|
|
976
995
|
}
|
|
977
996
|
listPrompts() {
|
|
978
997
|
return this.prompts;
|
|
@@ -982,40 +1001,35 @@ var ServerManager = class {
|
|
|
982
1001
|
this.prompts = (await this.client.listPrompts()).prompts;
|
|
983
1002
|
}
|
|
984
1003
|
async getPrompt(name, args) {
|
|
985
|
-
|
|
986
|
-
return await this.client.getPrompt({
|
|
1004
|
+
return await this.ensureConnected().getPrompt({
|
|
987
1005
|
name,
|
|
988
1006
|
arguments: args
|
|
989
1007
|
});
|
|
990
1008
|
}
|
|
991
1009
|
async complete(ref, argument) {
|
|
992
|
-
|
|
1010
|
+
const client = this.ensureConnected();
|
|
993
1011
|
if (!this.capabilities?.completions) throw new Error(`Server "${this.name}" does not support completions`);
|
|
994
|
-
return await
|
|
1012
|
+
return await client.complete({
|
|
995
1013
|
ref,
|
|
996
1014
|
argument
|
|
997
1015
|
});
|
|
998
1016
|
}
|
|
999
1017
|
async listTasks(cursor) {
|
|
1000
|
-
|
|
1018
|
+
const client = this.ensureConnected();
|
|
1001
1019
|
if (!this.capabilities?.experimental?.tasks) throw new Error(`Server "${this.name}" does not support tasks`);
|
|
1002
|
-
return await
|
|
1020
|
+
return await client.experimental.tasks.listTasks(cursor);
|
|
1003
1021
|
}
|
|
1004
1022
|
async getTask(taskId) {
|
|
1005
|
-
|
|
1006
|
-
return await this.client.experimental.tasks.getTask(taskId);
|
|
1023
|
+
return await this.ensureConnected().experimental.tasks.getTask(taskId);
|
|
1007
1024
|
}
|
|
1008
1025
|
async getTaskResult(taskId) {
|
|
1009
|
-
|
|
1010
|
-
return await this.client.experimental.tasks.getTaskResult(taskId);
|
|
1026
|
+
return await this.ensureConnected().experimental.tasks.getTaskResult(taskId);
|
|
1011
1027
|
}
|
|
1012
1028
|
async cancelTask(taskId) {
|
|
1013
|
-
|
|
1014
|
-
return await this.client.experimental.tasks.cancelTask(taskId);
|
|
1029
|
+
return await this.ensureConnected().experimental.tasks.cancelTask(taskId);
|
|
1015
1030
|
}
|
|
1016
1031
|
async callToolWithTask(name, args) {
|
|
1017
|
-
|
|
1018
|
-
const stream = this.client.experimental.tasks.callToolStream({
|
|
1032
|
+
const stream = this.ensureConnected().experimental.tasks.callToolStream({
|
|
1019
1033
|
name,
|
|
1020
1034
|
arguments: args
|
|
1021
1035
|
});
|
|
@@ -1169,6 +1183,7 @@ var ServerPool = class {
|
|
|
1169
1183
|
const manager = new ServerManager(name, serverConfig, {
|
|
1170
1184
|
connectTimeout: config.daemon?.connectTimeout,
|
|
1171
1185
|
healthCheckInterval: config.daemon?.healthCheckInterval,
|
|
1186
|
+
healthCheckTimeout: config.daemon?.healthCheckTimeout,
|
|
1172
1187
|
maxRestartAttempts: config.daemon?.maxRestartAttempts
|
|
1173
1188
|
});
|
|
1174
1189
|
manager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1470,6 +1485,7 @@ var ServerPool = class {
|
|
|
1470
1485
|
const manager = new ServerManager(name, serverConfig, {
|
|
1471
1486
|
connectTimeout: newConfig.daemon?.connectTimeout,
|
|
1472
1487
|
healthCheckInterval: newConfig.daemon?.healthCheckInterval,
|
|
1488
|
+
healthCheckTimeout: newConfig.daemon?.healthCheckTimeout,
|
|
1473
1489
|
maxRestartAttempts: newConfig.daemon?.maxRestartAttempts
|
|
1474
1490
|
});
|
|
1475
1491
|
manager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1485,6 +1501,7 @@ var ServerPool = class {
|
|
|
1485
1501
|
const newManager = new ServerManager(name, serverConfig, {
|
|
1486
1502
|
connectTimeout: newConfig.daemon?.connectTimeout,
|
|
1487
1503
|
healthCheckInterval: newConfig.daemon?.healthCheckInterval,
|
|
1504
|
+
healthCheckTimeout: newConfig.daemon?.healthCheckTimeout,
|
|
1488
1505
|
maxRestartAttempts: newConfig.daemon?.maxRestartAttempts
|
|
1489
1506
|
});
|
|
1490
1507
|
newManager.setHealthCallback((serverName, status, error) => {
|
|
@@ -1628,6 +1645,240 @@ function filterFields(data, fields) {
|
|
|
1628
1645
|
}
|
|
1629
1646
|
return data;
|
|
1630
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
|
+
}
|
|
1631
1882
|
function createDaemonServer(serverPool, config) {
|
|
1632
1883
|
const socketPath = getSocketPath();
|
|
1633
1884
|
let idleTimer;
|
|
@@ -1656,11 +1907,37 @@ function createDaemonServer(serverPool, config) {
|
|
|
1656
1907
|
result: serverPool.listServers()
|
|
1657
1908
|
};
|
|
1658
1909
|
case "tools/list": {
|
|
1659
|
-
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
|
+
}
|
|
1660
1930
|
return {
|
|
1661
1931
|
jsonrpc: "2.0",
|
|
1662
1932
|
id,
|
|
1663
|
-
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
|
+
}))
|
|
1664
1941
|
};
|
|
1665
1942
|
}
|
|
1666
1943
|
case "tools/call": {
|
|
@@ -1753,10 +2030,34 @@ function createDaemonServer(serverPool, config) {
|
|
|
1753
2030
|
data: toErrorData(found.error)
|
|
1754
2031
|
}
|
|
1755
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
|
+
};
|
|
1756
2057
|
return {
|
|
1757
2058
|
jsonrpc: "2.0",
|
|
1758
2059
|
id,
|
|
1759
|
-
result:
|
|
2060
|
+
result: tool
|
|
1760
2061
|
};
|
|
1761
2062
|
}
|
|
1762
2063
|
case "tools/validate": {
|
|
@@ -1834,10 +2135,36 @@ function createDaemonServer(serverPool, config) {
|
|
|
1834
2135
|
}
|
|
1835
2136
|
};
|
|
1836
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
|
+
}
|
|
1837
2157
|
return {
|
|
1838
2158
|
jsonrpc: "2.0",
|
|
1839
2159
|
id,
|
|
1840
|
-
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
|
+
}))
|
|
1841
2168
|
};
|
|
1842
2169
|
} catch (err) {
|
|
1843
2170
|
return {
|
|
@@ -2525,16 +2852,32 @@ async function ensureDaemon(configPath) {
|
|
|
2525
2852
|
400
|
|
2526
2853
|
]);
|
|
2527
2854
|
}
|
|
2528
|
-
async function sendRequest(method, params) {
|
|
2855
|
+
async function sendRequest(method, params, timeout = 9e4) {
|
|
2529
2856
|
const socketPath = getSocketPath();
|
|
2530
2857
|
return new Promise((resolve, reject) => {
|
|
2531
2858
|
const socket = net.createConnection(socketPath);
|
|
2532
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);
|
|
2533
2867
|
socket.on("error", (err) => {
|
|
2868
|
+
if (settled) return;
|
|
2869
|
+
settled = true;
|
|
2870
|
+
clearTimeout(timer);
|
|
2534
2871
|
if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `muxed status` to check."));
|
|
2535
2872
|
else if (err.code === "ECONNREFUSED") reject(/* @__PURE__ */ new Error("Daemon may have crashed. Try running a command to auto-restart it."));
|
|
2536
2873
|
else reject(err);
|
|
2537
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
|
+
});
|
|
2538
2881
|
socket.on("connect", () => {
|
|
2539
2882
|
const request = {
|
|
2540
2883
|
jsonrpc: "2.0",
|
|
@@ -2548,6 +2891,9 @@ async function sendRequest(method, params) {
|
|
|
2548
2891
|
buffer += data.toString();
|
|
2549
2892
|
const newlineIndex = buffer.indexOf("\n");
|
|
2550
2893
|
if (newlineIndex === -1) return;
|
|
2894
|
+
if (settled) return;
|
|
2895
|
+
settled = true;
|
|
2896
|
+
clearTimeout(timer);
|
|
2551
2897
|
const line = buffer.slice(0, newlineIndex).trim();
|
|
2552
2898
|
socket.destroy();
|
|
2553
2899
|
try {
|
|
@@ -2593,7 +2939,6 @@ function formatTools(tools) {
|
|
|
2593
2939
|
return formatTable([
|
|
2594
2940
|
"Tool",
|
|
2595
2941
|
"Title",
|
|
2596
|
-
"Description",
|
|
2597
2942
|
"Hints"
|
|
2598
2943
|
], tools.map(({ server, tool }) => {
|
|
2599
2944
|
const hints = [];
|
|
@@ -2603,7 +2948,6 @@ function formatTools(tools) {
|
|
|
2603
2948
|
return [
|
|
2604
2949
|
`${server}/${tool.name}`,
|
|
2605
2950
|
tool.title ?? "—",
|
|
2606
|
-
truncate(tool.description ?? "", 60),
|
|
2607
2951
|
hints.join(" ")
|
|
2608
2952
|
];
|
|
2609
2953
|
}));
|
|
@@ -2837,6 +3181,24 @@ function formatInit(result) {
|
|
|
2837
3181
|
lines.push("");
|
|
2838
3182
|
lines.push("All discovered servers already exist in muxed config. Nothing to do.");
|
|
2839
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
|
+
}
|
|
2840
3202
|
return lines.join("\n");
|
|
2841
3203
|
}
|
|
2842
3204
|
function formatMcpServer(name, config, scope) {
|
|
@@ -3005,20 +3367,27 @@ async function shutdown() {
|
|
|
3005
3367
|
if (_client) await _client.shutdown();
|
|
3006
3368
|
} catch {}
|
|
3007
3369
|
}
|
|
3008
|
-
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) => {
|
|
3009
3371
|
const configPath = toolsCommand.parent?.opts().config;
|
|
3010
3372
|
await ensureDaemon(configPath);
|
|
3011
|
-
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);
|
|
3012
3378
|
capture("tools_listed", {
|
|
3013
3379
|
filtered_by_server: !!server,
|
|
3014
3380
|
tool_count: result.length
|
|
3015
3381
|
});
|
|
3016
3382
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3017
3383
|
});
|
|
3018
|
-
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) => {
|
|
3019
3385
|
const configPath = infoCommand.parent?.opts().config;
|
|
3020
3386
|
await ensureDaemon(configPath);
|
|
3021
|
-
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);
|
|
3022
3391
|
if (opts.json) console.log(formatJson(result));
|
|
3023
3392
|
else {
|
|
3024
3393
|
const slashIndex = serverTool.indexOf("/");
|
|
@@ -3176,10 +3545,13 @@ const callCommand = new Command("call").description("Execute a tool with JSON ar
|
|
|
3176
3545
|
process.exit(1);
|
|
3177
3546
|
}
|
|
3178
3547
|
});
|
|
3179
|
-
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) => {
|
|
3180
3549
|
const configPath = grepCommand.parent?.opts().config;
|
|
3181
3550
|
await ensureDaemon(configPath);
|
|
3182
|
-
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);
|
|
3183
3555
|
capture("tools_searched", { result_count: result.length });
|
|
3184
3556
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3185
3557
|
});
|
|
@@ -3654,6 +4026,261 @@ function getMuxedConfigPath(scope, explicitPath) {
|
|
|
3654
4026
|
if (scope === "local") return path.join(process.cwd(), "muxed.config.json");
|
|
3655
4027
|
return path.join(home, ".muxed", "config.json");
|
|
3656
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
|
+
}
|
|
3657
4284
|
async function confirm(message, opts) {
|
|
3658
4285
|
const rl = readline.createInterface({
|
|
3659
4286
|
input: opts?.input ?? process.stdin,
|
|
@@ -3721,7 +4348,7 @@ async function resolveConflicts(unresolvedConflicts, isInteractive) {
|
|
|
3721
4348
|
conflicts
|
|
3722
4349
|
};
|
|
3723
4350
|
}
|
|
3724
|
-
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) => {
|
|
3725
4352
|
const configPath = initCommand.parent?.opts().config;
|
|
3726
4353
|
const isInteractive = !opts.dryRun && !opts.json && !opts.yes && !!process.stdin.isTTY;
|
|
3727
4354
|
const { discovered, warnings } = discoverAgentConfigs();
|
|
@@ -3744,7 +4371,7 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3744
4371
|
}
|
|
3745
4372
|
if (!opts.dryRun && imported.length > 0) writeMuxedConfig(muxedPath, result.merged);
|
|
3746
4373
|
const modifiedFiles = [];
|
|
3747
|
-
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;
|
|
3748
4375
|
if (!opts.dryRun && shouldDelete) for (const dc of discovered) try {
|
|
3749
4376
|
modifyAgentConfig(dc, {
|
|
3750
4377
|
delete: true,
|
|
@@ -3754,6 +4381,10 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3754
4381
|
} catch (err) {
|
|
3755
4382
|
warnings.push(`Failed to modify ${dc.configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3756
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
|
+
}) : [];
|
|
3757
4388
|
const initResult = {
|
|
3758
4389
|
discovered: discovered.map((d) => ({
|
|
3759
4390
|
agent: d.agent.name,
|
|
@@ -3767,14 +4398,17 @@ const initCommand = new Command("init").description("Discover and import MCP ser
|
|
|
3767
4398
|
warnings,
|
|
3768
4399
|
modifiedFiles,
|
|
3769
4400
|
muxedConfigPath: muxedPath,
|
|
3770
|
-
dryRun: opts.dryRun ?? false
|
|
4401
|
+
dryRun: opts.dryRun ?? false,
|
|
4402
|
+
instructionResults
|
|
3771
4403
|
};
|
|
3772
4404
|
capture("init_run", {
|
|
3773
4405
|
dry_run: opts.dryRun ?? false,
|
|
3774
4406
|
imported_count: imported.length,
|
|
3775
4407
|
conflict_count: conflicts.length,
|
|
3776
4408
|
warning_count: warnings.length,
|
|
3777
|
-
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)
|
|
3778
4412
|
});
|
|
3779
4413
|
console.log(opts.json ? formatJson(initResult) : formatInit(initResult));
|
|
3780
4414
|
});
|
|
@@ -3831,7 +4465,10 @@ const cliFragments = {
|
|
|
3831
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.",
|
|
3832
4466
|
grep: (p) => `npx muxed grep "${p}"`,
|
|
3833
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",
|
|
3834
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}`,
|
|
3835
4472
|
call: (n, j) => `npx muxed call ${n} '${j}'`,
|
|
3836
4473
|
callStdin: (n) => `npx muxed call ${n} -`,
|
|
3837
4474
|
callDryRun: (n, j) => `npx muxed call ${n} '${j}' --dry-run`,
|
|
@@ -3845,7 +4482,10 @@ const toolFragments = {
|
|
|
3845
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.",
|
|
3846
4483
|
grep: (p) => `muxed:exec({ "command": "grep ${p}" })`,
|
|
3847
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" })`,
|
|
3848
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}" })`,
|
|
3849
4489
|
call: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
3850
4490
|
callStdin: (n) => `muxed:exec({ "command": "call ${n}", "input": { ... } })`,
|
|
3851
4491
|
callDryRun: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
@@ -3887,15 +4527,23 @@ Commands (in order of execution):
|
|
|
3887
4527
|
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
3888
4528
|
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
3889
4529
|
|
|
3890
|
-
# STEP 2:
|
|
3891
|
-
|
|
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
|
|
3892
4540
|
|
|
3893
4541
|
# STEP 3: OPTIONAL - Validate arguments before calling (dry-run)
|
|
3894
4542
|
${f.callDryRun("<server>/<tool>", "<json>")} # Validate args without executing
|
|
3895
4543
|
|
|
3896
|
-
# STEP 4: Only after
|
|
3897
|
-
${f.call("<server>/<tool>", "<json>")} # Only run AFTER
|
|
3898
|
-
${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
|
|
3899
4547
|
${f.callFields("<server>/<tool>", "<json>", "field1,field2")} # Extract specific fields from response
|
|
3900
4548
|
|
|
3901
4549
|
# Discovery commands (use these to find tools)
|
|
@@ -3973,8 +4621,15 @@ Example usage:
|
|
|
3973
4621
|
${f.tools()} # See all available MCP tools
|
|
3974
4622
|
${f.grep("weather")} # Find tools by description
|
|
3975
4623
|
|
|
3976
|
-
# Get tool
|
|
3977
|
-
${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
|
|
3978
4633
|
|
|
3979
4634
|
# Simple tool call (no parameters)
|
|
3980
4635
|
${f.call("weather/get_location", "{}")}
|
|
@@ -4014,6 +4669,22 @@ function parseCommand(command) {
|
|
|
4014
4669
|
args: trimmed.slice(spaceIndex + 1).trim()
|
|
4015
4670
|
};
|
|
4016
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
|
+
}
|
|
4017
4688
|
function textResult(data) {
|
|
4018
4689
|
return { content: [{
|
|
4019
4690
|
type: "text",
|
|
@@ -4035,16 +4706,31 @@ async function handleToolCommand(command, input) {
|
|
|
4035
4706
|
switch (subcommand) {
|
|
4036
4707
|
case "servers": return textResult(await sendRequest("servers/list"));
|
|
4037
4708
|
case "tools": {
|
|
4709
|
+
const { positional, flags } = parseFlags(args);
|
|
4038
4710
|
const params = {};
|
|
4039
|
-
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);
|
|
4040
4714
|
return textResult(await sendRequest("tools/list", params));
|
|
4041
4715
|
}
|
|
4042
|
-
case "grep":
|
|
4716
|
+
case "grep": {
|
|
4043
4717
|
if (!args) return errorResult("Usage: grep <pattern>");
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
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
|
+
}
|
|
4048
4734
|
case "call": {
|
|
4049
4735
|
if (!args) return errorResult("Usage: call <server/tool>");
|
|
4050
4736
|
const result = await sendRequest("tools/call", {
|
|
@@ -4379,7 +5065,7 @@ const telemetryCommand = new Command("telemetry").description("Manage anonymous
|
|
|
4379
5065
|
});
|
|
4380
5066
|
async function runCli() {
|
|
4381
5067
|
const program = new Command();
|
|
4382
|
-
program.name("muxed").description("The optimization layer for MCP").version(
|
|
5068
|
+
program.name("muxed").description("The optimization layer for MCP").version(getVersion());
|
|
4383
5069
|
program.enablePositionalOptions();
|
|
4384
5070
|
program.option("--config <path>", "Path to config file");
|
|
4385
5071
|
program.commandsGroup("Servers:");
|