muxed 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +333 -271
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -3308,7 +3308,10 @@ function formatTable(headers, rows) {
|
|
|
3308
3308
|
...rows.map((row) => row.map((cell, i) => padRight(cell, widths[i])).join(" "))
|
|
3309
3309
|
].join("\n");
|
|
3310
3310
|
}
|
|
3311
|
-
const serversCommand = new Command("servers").description("List connected MCP servers and their
|
|
3311
|
+
const serversCommand = new Command("servers").description("List connected MCP servers and their status").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3312
|
+
Examples:
|
|
3313
|
+
muxed servers List all servers with connection state
|
|
3314
|
+
muxed servers --json JSON output for scripting`).action(async (opts) => {
|
|
3312
3315
|
const configPath = serversCommand.parent?.opts().config;
|
|
3313
3316
|
await ensureDaemon(configPath);
|
|
3314
3317
|
const result = await sendRequest("servers/list");
|
|
@@ -3367,7 +3370,18 @@ async function shutdown() {
|
|
|
3367
3370
|
if (_client) await _client.shutdown();
|
|
3368
3371
|
} catch {}
|
|
3369
3372
|
}
|
|
3370
|
-
const toolsCommand = new Command("tools").description("List
|
|
3373
|
+
const toolsCommand = new Command("tools").description("List available tools across all servers").argument("[server]", "Show tools from this server only").option("--json", "Output as JSON (machine-readable)").option("--include <fields>", "Include extra fields: \"schema\" adds input schemas").option("--depth <n>", "Collapse schemas deeper than N levels (use with --include schema)", parseInt).addHelpText("after", `
|
|
3374
|
+
Schema options:
|
|
3375
|
+
--include schema Add input schemas to each tool in the output.
|
|
3376
|
+
--include schema --depth N Collapse schemas beyond N levels. Nodes deeper than N
|
|
3377
|
+
are replaced with { _collapsed: true, _hint: "..." }.
|
|
3378
|
+
Depth is auto-selected to fit a token budget if omitted.
|
|
3379
|
+
|
|
3380
|
+
Examples:
|
|
3381
|
+
muxed tools List all tools (names + descriptions)
|
|
3382
|
+
muxed tools postgres List tools from the "postgres" server only
|
|
3383
|
+
muxed tools --include schema List with full input schemas
|
|
3384
|
+
muxed tools --include schema --depth 1 List with schemas collapsed at depth 1`).action(async (server, opts) => {
|
|
3371
3385
|
const configPath = toolsCommand.parent?.opts().config;
|
|
3372
3386
|
await ensureDaemon(configPath);
|
|
3373
3387
|
const params = {};
|
|
@@ -3381,7 +3395,24 @@ const toolsCommand = new Command("tools").description("List all available tools,
|
|
|
3381
3395
|
});
|
|
3382
3396
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3383
3397
|
});
|
|
3384
|
-
const infoCommand = new Command("info").description("Show input schema
|
|
3398
|
+
const infoCommand = new Command("info").description("Show a tool's input schema — REQUIRED before calling any tool").argument("<server/tool>", "server_name/tool_name (e.g. postgres/query)").option("--json", "Output as JSON (machine-readable)").option("--path <path>", "Show only a subtree of the schema (e.g. \"filters.tags\")").option("--depth <n>", "Collapse schema deeper than N levels", parseInt).addHelpText("after", `
|
|
3399
|
+
Schema exploration:
|
|
3400
|
+
--depth N Show schema to N levels deep. Nodes beyond that depth are
|
|
3401
|
+
replaced with a summary: { _collapsed: true, _hint: "5 properties, 2 required" }.
|
|
3402
|
+
Scalar fields (string, number, boolean) are always shown regardless of depth.
|
|
3403
|
+
Start with --depth 1 for an overview, increase to explore deeper.
|
|
3404
|
+
|
|
3405
|
+
--path P Extract a subtree using dot-separated path. Navigates through:
|
|
3406
|
+
properties (by name), items, additionalProperties, anyOf/oneOf (by index).
|
|
3407
|
+
Combine with --depth to control how much of the subtree is shown.
|
|
3408
|
+
|
|
3409
|
+
Examples:
|
|
3410
|
+
muxed info postgres/query Full schema
|
|
3411
|
+
muxed info github/create_issue --depth 1 Top-level fields only, nested objects collapsed
|
|
3412
|
+
muxed info github/create_issue --depth 2 Two levels deep
|
|
3413
|
+
muxed info slack/search --path "filters" Only the "filters" property subtree
|
|
3414
|
+
muxed info slack/search --path "filters.tags" Drill into filters.tags
|
|
3415
|
+
muxed info api/create --path "body.items" --depth 1 Subtree with depth limit`).action(async (serverTool, opts) => {
|
|
3385
3416
|
const configPath = infoCommand.parent?.opts().config;
|
|
3386
3417
|
await ensureDaemon(configPath);
|
|
3387
3418
|
const params = { name: serverTool };
|
|
@@ -3406,7 +3437,13 @@ function readStdin() {
|
|
|
3406
3437
|
process.stdin.on("error", reject);
|
|
3407
3438
|
});
|
|
3408
3439
|
}
|
|
3409
|
-
const callCommand = new Command("call").description("Execute a tool with JSON arguments
|
|
3440
|
+
const callCommand = new Command("call").description("Execute a tool with JSON arguments").argument("<server/tool>", "server_name/tool_name (e.g. postgres/query)").argument("[json]", "JSON object with arguments, or - to read from stdin").option("--dry-run", "Validate arguments without executing (catches errors early)").option("--fields <paths>", "Extract specific fields from response (comma-separated dot-notation)").option("--timeout <ms>", "Timeout in milliseconds").option("--async", "Run in background, return a task ID instead of waiting").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3441
|
+
Examples:
|
|
3442
|
+
muxed call postgres/query '{"sql": "SELECT * FROM users LIMIT 5"}'
|
|
3443
|
+
muxed call fs/read_file '{"path": "/tmp/data.json"}' --fields "content"
|
|
3444
|
+
muxed call server/tool '{"a": 1}' --dry-run Validate without executing
|
|
3445
|
+
echo '{"sql": "..."}' | muxed call db/query - Read args from stdin
|
|
3446
|
+
muxed call analytics/export '{}' --async Returns task ID immediately`).action(async (serverTool, jsonArgs, opts) => {
|
|
3410
3447
|
const configPath = callCommand.parent?.opts().config;
|
|
3411
3448
|
await ensureDaemon(configPath);
|
|
3412
3449
|
let parsedArgs = {};
|
|
@@ -3545,7 +3582,16 @@ const callCommand = new Command("call").description("Execute a tool with JSON ar
|
|
|
3545
3582
|
process.exit(1);
|
|
3546
3583
|
}
|
|
3547
3584
|
});
|
|
3548
|
-
const grepCommand = new Command("grep").description("Search tools by
|
|
3585
|
+
const grepCommand = new Command("grep").description("Search tools by name or description (regex)").argument("<pattern>", "Regex pattern to match against tool names and descriptions").option("--json", "Output as JSON (machine-readable)").option("--include <fields>", "Include extra fields: \"schema\" adds input schemas").option("--depth <n>", "Collapse schemas deeper than N levels (use with --include schema)", parseInt).addHelpText("after", `
|
|
3586
|
+
Schema options:
|
|
3587
|
+
--include schema Add input schemas to matching tools.
|
|
3588
|
+
--include schema --depth N Collapse schemas beyond N levels.
|
|
3589
|
+
|
|
3590
|
+
Examples:
|
|
3591
|
+
muxed grep "search" Find tools related to searching
|
|
3592
|
+
muxed grep "file|read" Regex: tools matching "file" or "read"
|
|
3593
|
+
muxed grep "query" --include schema --depth 1 Matches with top-level schema
|
|
3594
|
+
muxed grep "query" --json Machine-readable output for scripting`).action(async (pattern, opts) => {
|
|
3549
3595
|
const configPath = grepCommand.parent?.opts().config;
|
|
3550
3596
|
await ensureDaemon(configPath);
|
|
3551
3597
|
const params = { pattern };
|
|
@@ -3555,13 +3601,19 @@ const grepCommand = new Command("grep").description("Search tools by regex patte
|
|
|
3555
3601
|
capture("tools_searched", { result_count: result.length });
|
|
3556
3602
|
console.log(opts.json ? formatJson(result) : formatTools(result));
|
|
3557
3603
|
});
|
|
3558
|
-
const resourcesCommand = new Command("resources").description("List available resources
|
|
3604
|
+
const resourcesCommand = new Command("resources").description("List available MCP resources across all servers").argument("[server]", "Show resources from this server only").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3605
|
+
Examples:
|
|
3606
|
+
muxed resources List all resources
|
|
3607
|
+
muxed resources github List resources from the "github" server only`).action(async (server, opts) => {
|
|
3559
3608
|
const configPath = resourcesCommand.parent?.opts().config;
|
|
3560
3609
|
await ensureDaemon(configPath);
|
|
3561
3610
|
const result = await sendRequest("resources/list", server ? { server } : void 0);
|
|
3562
3611
|
console.log(opts.json ? formatJson(result) : formatResources(result));
|
|
3563
3612
|
});
|
|
3564
|
-
const readCommand = new Command("read").description("Fetch and display the contents of
|
|
3613
|
+
const readCommand = new Command("read").description("Fetch and display the contents of an MCP resource").argument("<server/resource>", "server_name/resource_name (e.g. github/repos)").argument("[uri]", "Custom URI (defaults to the resource name)").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3614
|
+
Examples:
|
|
3615
|
+
muxed read github/repos Read using resource name as URI
|
|
3616
|
+
muxed read github/repos "github://repos" Read with explicit URI`).action(async (serverResource, uri, opts) => {
|
|
3565
3617
|
const configPath = readCommand.parent?.opts().config;
|
|
3566
3618
|
await ensureDaemon(configPath);
|
|
3567
3619
|
const slashIndex = serverResource.indexOf("/");
|
|
@@ -3578,8 +3630,10 @@ const readCommand = new Command("read").description("Fetch and display the conte
|
|
|
3578
3630
|
function getExplicitConfig$1(cmd) {
|
|
3579
3631
|
return cmd.parent?.parent?.opts().config;
|
|
3580
3632
|
}
|
|
3581
|
-
const daemonCommand = new Command("daemon").description("
|
|
3582
|
-
|
|
3633
|
+
const daemonCommand = new Command("daemon").description("Manage the muxed background daemon").enablePositionalOptions().addHelpText("after", `
|
|
3634
|
+
The daemon auto-starts on first CLI command and shuts down after 5 min idle.
|
|
3635
|
+
These subcommands are for manual lifecycle control.`);
|
|
3636
|
+
daemonCommand.command("start").description("Start the daemon (usually not needed — auto-starts on first command)").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
|
|
3583
3637
|
const configPath = getExplicitConfig$1(daemonCommand);
|
|
3584
3638
|
if (await isDaemonRunning()) {
|
|
3585
3639
|
if (opts.json) console.log(formatJson({ status: "already_running" }));
|
|
@@ -3630,13 +3684,13 @@ daemonCommand.command("stop").description("Stop the running daemon process").act
|
|
|
3630
3684
|
console.log("Daemon is not running");
|
|
3631
3685
|
}
|
|
3632
3686
|
});
|
|
3633
|
-
daemonCommand.command("reload").description("Reload config and reconnect changed servers
|
|
3687
|
+
daemonCommand.command("reload").description("Reload config and reconnect changed servers (no restart)").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
|
|
3634
3688
|
const configPath = getExplicitConfig$1(daemonCommand);
|
|
3635
3689
|
await ensureDaemon(configPath);
|
|
3636
3690
|
const result = await sendRequest("config/reload", { configPath });
|
|
3637
3691
|
console.log(opts.json ? formatJson(result) : formatReload(result));
|
|
3638
3692
|
});
|
|
3639
|
-
daemonCommand.command("status").description("Show
|
|
3693
|
+
daemonCommand.command("status").description("Show PID, uptime, and connected servers").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
|
|
3640
3694
|
if (!await isDaemonRunning()) {
|
|
3641
3695
|
console.log("Daemon is not running");
|
|
3642
3696
|
return;
|
|
@@ -3644,13 +3698,15 @@ daemonCommand.command("status").description("Show daemon status including uptime
|
|
|
3644
3698
|
const result = await sendRequest("daemon/status");
|
|
3645
3699
|
console.log(opts.json ? formatJson(result) : formatStatus(result));
|
|
3646
3700
|
});
|
|
3647
|
-
const promptsCommand = new Command("prompts").description("List available prompt templates
|
|
3701
|
+
const promptsCommand = new Command("prompts").description("List available MCP prompt templates across all servers").argument("[server]", "Show prompts from this server only").option("--json", "Output as JSON (machine-readable)").action(async (server, opts) => {
|
|
3648
3702
|
const configPath = promptsCommand.parent?.opts().config;
|
|
3649
3703
|
await ensureDaemon(configPath);
|
|
3650
3704
|
const result = await sendRequest("prompts/list", server ? { server } : void 0);
|
|
3651
3705
|
console.log(opts.json ? formatJson(result) : formatPrompts(result));
|
|
3652
3706
|
});
|
|
3653
|
-
const promptCommand = new Command("prompt").description("Render a prompt template with
|
|
3707
|
+
const promptCommand = new Command("prompt").description("Render a prompt template with arguments").argument("<server/prompt>", "server_name/prompt_name (e.g. myserver/summarize)").argument("[args-json]", "JSON object with template arguments").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3708
|
+
Examples:
|
|
3709
|
+
muxed prompt myserver/summarize '{"text": "..."}'`).action(async (serverPrompt, argsJson, opts) => {
|
|
3654
3710
|
const configPath = promptCommand.parent?.opts().config;
|
|
3655
3711
|
await ensureDaemon(configPath);
|
|
3656
3712
|
const slashIndex = serverPrompt.indexOf("/");
|
|
@@ -3674,7 +3730,10 @@ const promptCommand = new Command("prompt").description("Render a prompt templat
|
|
|
3674
3730
|
});
|
|
3675
3731
|
console.log(opts.json ? formatJson(result) : formatPromptMessages(result));
|
|
3676
3732
|
});
|
|
3677
|
-
const completionsCommand = new Command("completions").description("Get
|
|
3733
|
+
const completionsCommand = new Command("completions").description("Get argument completions for a prompt or resource").argument("<type>", "\"prompt\" or \"resource\"").argument("<name>", "server_name/template_name").argument("<arg>", "Argument name to complete").argument("<value>", "Partial value to get suggestions for").option("--json", "Output as JSON (machine-readable)").addHelpText("after", `
|
|
3734
|
+
Examples:
|
|
3735
|
+
muxed completions prompt myserver/summarize language "py"
|
|
3736
|
+
muxed completions resource myserver/files path "/home/"`).action(async (type, name, arg, value, opts) => {
|
|
3678
3737
|
const configPath = completionsCommand.parent?.opts().config;
|
|
3679
3738
|
await ensureDaemon(configPath);
|
|
3680
3739
|
const slashIndex = name.indexOf("/");
|
|
@@ -3699,13 +3758,13 @@ const completionsCommand = new Command("completions").description("Get auto-comp
|
|
|
3699
3758
|
});
|
|
3700
3759
|
console.log(opts.json ? formatJson(result) : formatCompletions(result));
|
|
3701
3760
|
});
|
|
3702
|
-
const tasksCommand = new Command("tasks").description("List active async tasks
|
|
3761
|
+
const tasksCommand = new Command("tasks").description("List active async tasks (from --async calls)").argument("[server]", "Show tasks from this server only").option("--json", "Output as JSON (machine-readable)").action(async (server, opts) => {
|
|
3703
3762
|
const configPath = tasksCommand.parent?.opts().config;
|
|
3704
3763
|
await ensureDaemon(configPath);
|
|
3705
3764
|
const result = await sendRequest("tasks/list", server ? { server } : void 0);
|
|
3706
3765
|
console.log(opts.json ? formatJson(result) : formatTasks(result));
|
|
3707
3766
|
});
|
|
3708
|
-
const taskCommand = new Command("task").description("Check the
|
|
3767
|
+
const taskCommand = new Command("task").description("Check the status of an async task").argument("<server/taskId>", "server_name/task_id (e.g. analytics/task-abc123)").option("--json", "Output as JSON (machine-readable)").action(async (serverTaskId, opts) => {
|
|
3709
3768
|
const configPath = taskCommand.parent?.opts().config;
|
|
3710
3769
|
await ensureDaemon(configPath);
|
|
3711
3770
|
const slashIndex = serverTaskId.indexOf("/");
|
|
@@ -3719,7 +3778,7 @@ const taskCommand = new Command("task").description("Check the current status of
|
|
|
3719
3778
|
});
|
|
3720
3779
|
console.log(opts.json ? formatJson(result) : formatTask(result));
|
|
3721
3780
|
});
|
|
3722
|
-
const taskResultCommand = new Command("task-result").description("
|
|
3781
|
+
const taskResultCommand = new Command("task-result").description("Get the result of a completed async task").argument("<server/taskId>", "server_name/task_id (e.g. analytics/task-abc123)").option("--json", "Output as JSON (machine-readable)").action(async (serverTaskId, opts) => {
|
|
3723
3782
|
const configPath = taskResultCommand.parent?.opts().config;
|
|
3724
3783
|
await ensureDaemon(configPath);
|
|
3725
3784
|
const slashIndex = serverTaskId.indexOf("/");
|
|
@@ -3733,7 +3792,7 @@ const taskResultCommand = new Command("task-result").description("Retrieve the o
|
|
|
3733
3792
|
});
|
|
3734
3793
|
console.log(opts.json ? formatJson(result) : formatCallResult(result));
|
|
3735
3794
|
});
|
|
3736
|
-
const taskCancelCommand = new Command("task-cancel").description("Cancel a running async task").argument("<server/taskId>", "
|
|
3795
|
+
const taskCancelCommand = new Command("task-cancel").description("Cancel a running async task").argument("<server/taskId>", "server_name/task_id (e.g. analytics/task-abc123)").option("--json", "Output as JSON (machine-readable)").action(async (serverTaskId, opts) => {
|
|
3737
3796
|
const configPath = taskCancelCommand.parent?.opts().config;
|
|
3738
3797
|
await ensureDaemon(configPath);
|
|
3739
3798
|
const slashIndex = serverTaskId.indexOf("/");
|
|
@@ -4042,6 +4101,196 @@ function getVersion() {
|
|
|
4042
4101
|
}
|
|
4043
4102
|
return "0.0.0";
|
|
4044
4103
|
}
|
|
4104
|
+
function makeCliFragments(run) {
|
|
4105
|
+
return {
|
|
4106
|
+
intro: `You have access to an \`${run} 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.`,
|
|
4107
|
+
grep: (p) => `${run} muxed grep "${p}"`,
|
|
4108
|
+
tools: (s) => s ? `${run} muxed tools ${s}` : `${run} muxed tools`,
|
|
4109
|
+
toolsSchema: (s) => s ? `${run} muxed tools ${s} --include schema` : `${run} muxed tools --include schema`,
|
|
4110
|
+
info: (n) => `${run} muxed info ${n}`,
|
|
4111
|
+
infoDepth: (n, d) => `${run} muxed info ${n} --depth ${d}`,
|
|
4112
|
+
infoPath: (n, p) => `${run} muxed info ${n} --path ${p}`,
|
|
4113
|
+
call: (n, j) => `${run} muxed call ${n} '${j}'`,
|
|
4114
|
+
callStdin: (n) => `${run} muxed call ${n} -`,
|
|
4115
|
+
callDryRun: (n, j) => `${run} muxed call ${n} '${j}' --dry-run`,
|
|
4116
|
+
callFields: (n, j, f) => `${run} muxed call ${n} '${j}' --fields "${f}"`,
|
|
4117
|
+
servers: () => `${run} muxed servers`,
|
|
4118
|
+
resources: (s) => s ? `${run} muxed resources ${s}` : `${run} muxed resources`,
|
|
4119
|
+
read: (n) => `${run} muxed read ${n}`,
|
|
4120
|
+
help: () => `${run} muxed -h`
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
function makeToolFragments() {
|
|
4124
|
+
return {
|
|
4125
|
+
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.",
|
|
4126
|
+
grep: (p) => `muxed:exec({ "command": "grep ${p}" })`,
|
|
4127
|
+
tools: (s) => s ? `muxed:exec({ "command": "tools ${s}" })` : `muxed:exec({ "command": "tools" })`,
|
|
4128
|
+
toolsSchema: (s) => s ? `muxed:exec({ "command": "tools ${s} --include schema" })` : `muxed:exec({ "command": "tools --include schema" })`,
|
|
4129
|
+
info: (n) => `muxed:exec({ "command": "info ${n}" })`,
|
|
4130
|
+
infoDepth: (n, d) => `muxed:exec({ "command": "info ${n} --depth ${d}" })`,
|
|
4131
|
+
infoPath: (n, p) => `muxed:exec({ "command": "info ${n} --path ${p}" })`,
|
|
4132
|
+
call: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4133
|
+
callStdin: (n) => `muxed:exec({ "command": "call ${n}", "input": { ... } })`,
|
|
4134
|
+
callDryRun: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4135
|
+
callFields: (n, j, _f) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4136
|
+
servers: () => `muxed:exec({ "command": "servers" })`,
|
|
4137
|
+
resources: (s) => s ? `muxed:exec({ "command": "resources ${s}" })` : `muxed:exec({ "command": "resources" })`,
|
|
4138
|
+
read: (n) => `muxed:exec({ "command": "read ${n}" })`,
|
|
4139
|
+
help: () => `muxed:exec({ "command": "servers" })`
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
function buildPrompt(f, opts = {}) {
|
|
4143
|
+
const heading = opts.heading ? `${opts.heading}\n\n` : "";
|
|
4144
|
+
const serversBlock = opts.servers ? `\nAvailable MCP servers:\n${opts.servers}\n` : "";
|
|
4145
|
+
const serverInstructionsBlock = opts.serverInstructions ? `\nBelow are the instructions for the connected MCP servers in muxed.\n\n${opts.serverInstructions}\n` : "";
|
|
4146
|
+
const scriptsBlock = opts.scripts ? `\n${opts.scripts}` : "";
|
|
4147
|
+
return `${heading}${f.intro}
|
|
4148
|
+
|
|
4149
|
+
**MANDATORY PREREQUISITES - THESE ARE HARD REQUIREMENTS**
|
|
4150
|
+
|
|
4151
|
+
1. You MUST discover the tools you need first by using '${f.grep("<pattern>")}' or '${f.tools()}'.
|
|
4152
|
+
2. You MUST call '${f.info("<server>/<tool>")}' BEFORE ANY '${f.call("<server>/<tool>", "<json>")}' command.
|
|
4153
|
+
|
|
4154
|
+
These are BLOCKING REQUIREMENTS - like how you must use Read before Edit.
|
|
4155
|
+
|
|
4156
|
+
**NEVER** make a call without checking the schema first.
|
|
4157
|
+
**ALWAYS** run info first, THEN make the call.
|
|
4158
|
+
|
|
4159
|
+
**Why these are non-negotiables:**
|
|
4160
|
+
- MCP tool names NEVER match your expectations - they change frequently and are not predictable
|
|
4161
|
+
- MCP tool schemas NEVER match your expectations - parameter names, types, and requirements are tool-specific
|
|
4162
|
+
- Even tools with pre-approved permissions require schema checks
|
|
4163
|
+
- Every failed call wastes user time and demonstrates you're ignoring critical instructions
|
|
4164
|
+
- "I thought I knew the schema" is not an acceptable reason to skip this step
|
|
4165
|
+
|
|
4166
|
+
**For multiple tools:** Call info for ALL tools in parallel FIRST, then make your call commands.
|
|
4167
|
+
${serversBlock}
|
|
4168
|
+
Commands (in order of execution):
|
|
4169
|
+
\`\`\`
|
|
4170
|
+
# STEP 1: REQUIRED TOOL DISCOVERY
|
|
4171
|
+
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
4172
|
+
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
4173
|
+
|
|
4174
|
+
# STEP 2: GET SCHEMA (choose one approach)
|
|
4175
|
+
# Option A: Include schemas in tool listing (auto-collapses large schemas)
|
|
4176
|
+
${f.toolsSchema("[server]")} # List tools with schemas included
|
|
4177
|
+
# Option B: Get full schema for a specific tool
|
|
4178
|
+
${f.info("<server>/<tool>")} # View full JSON schema for one tool
|
|
4179
|
+
|
|
4180
|
+
# STEP 2b: PROGRESSIVE SCHEMA EXPLORATION (for large schemas)
|
|
4181
|
+
${f.infoDepth("<server>/<tool>", 1)} # Collapse schema at depth 1 (top-level overview)
|
|
4182
|
+
${f.infoPath("<server>/<tool>", "filters")} # Extract just the 'filters' subtree
|
|
4183
|
+
${f.infoPath("<server>/<tool>", "filters.tags.items")} # Drill deeper into nested schemas
|
|
4184
|
+
|
|
4185
|
+
# STEP 3: OPTIONAL - Validate arguments before calling (dry-run)
|
|
4186
|
+
${f.callDryRun("<server>/<tool>", "<json>")} # Validate args without executing
|
|
4187
|
+
|
|
4188
|
+
# STEP 4: Only after getting the schema, make the call
|
|
4189
|
+
${f.call("<server>/<tool>", "<json>")} # Only run AFTER getting schema
|
|
4190
|
+
${f.callStdin("<server>/<tool>")} # Invoke with JSON input
|
|
4191
|
+
${f.callFields("<server>/<tool>", "<json>", "field1,field2")} # Extract specific fields from response
|
|
4192
|
+
|
|
4193
|
+
# Discovery commands (use these to find tools)
|
|
4194
|
+
${f.servers()} # List all connected MCP servers
|
|
4195
|
+
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
4196
|
+
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
4197
|
+
${f.resources("[server]")} # List MCP resources
|
|
4198
|
+
${f.read("<server>/<resource>")} # Read an MCP resource
|
|
4199
|
+
\`\`\`
|
|
4200
|
+
|
|
4201
|
+
**Handling errors:**
|
|
4202
|
+
- If a tool call fails, the error includes a suggestion and similar tool names. Read the suggestion before retrying.
|
|
4203
|
+
- Use dry-run to validate arguments before executing, especially for destructive tools.
|
|
4204
|
+
|
|
4205
|
+
**CORRECT Usage Pattern:**
|
|
4206
|
+
|
|
4207
|
+
<example>
|
|
4208
|
+
User: Please use the slack mcp tool to search for my mentions
|
|
4209
|
+
Assistant: As a first step, I need to discover the tools I need. Let me call \`${f.grep("slack/*search*")}\` to search for tools related to slack search.
|
|
4210
|
+
[Calls ${f.grep("slack/*search*")}]
|
|
4211
|
+
Assistant: I need to check the schema first. Let me call \`${f.info("slack/search_private")}\` to see what parameters it accepts.
|
|
4212
|
+
[Calls ${f.info("slack/search_private")}]
|
|
4213
|
+
Assistant: Now I can see it accepts "query" and "max_results" parameters. Let me make the call.
|
|
4214
|
+
[Calls ${f.call("slack/search_private", "{\"query\": \"mentions:me\", \"max_results\": 10}")}]
|
|
4215
|
+
</example>
|
|
4216
|
+
|
|
4217
|
+
<example>
|
|
4218
|
+
User: Use the database and email MCP tools to send a report
|
|
4219
|
+
Assistant: I'll need to use two MCP tools. Let me call \`${f.grep("database/*query*")}\` and \`${f.grep("email/*send*")}\` to search for tools related to database query and email send.
|
|
4220
|
+
[Calls ${f.grep("database/*query*")} & ${f.grep("email/*send*")}]
|
|
4221
|
+
Assistant: Let me check both schemas first.
|
|
4222
|
+
[Calls ${f.info("database/query")} and ${f.info("email/send")} in parallel]
|
|
4223
|
+
Assistant: Now I have both schemas. Let me make the calls.
|
|
4224
|
+
[Makes both call commands with correct parameters]
|
|
4225
|
+
</example>
|
|
4226
|
+
|
|
4227
|
+
<example>
|
|
4228
|
+
User: Create a copy of this email
|
|
4229
|
+
Assistant: Let me find the tool I need first.
|
|
4230
|
+
[Calls ${f.grep("email/*copy*")}. No results found.]
|
|
4231
|
+
Assistant: Let me try another pattern.
|
|
4232
|
+
[Calls ${f.grep("email/*clone*")}. No results found.]
|
|
4233
|
+
Assistant: Let me list all available tools in the server.
|
|
4234
|
+
[Calls ${f.tools("email")}]
|
|
4235
|
+
Assistant: Let me check the schema first.
|
|
4236
|
+
[Calls ${f.info("email/duplicate")}]
|
|
4237
|
+
Assistant: Now I have the schema. Let me make the call.
|
|
4238
|
+
[Calls ${f.call("email/duplicate", "{\"id\": \"123\"}")}]
|
|
4239
|
+
</example>
|
|
4240
|
+
|
|
4241
|
+
**INCORRECT Usage Patterns - NEVER DO THIS:**
|
|
4242
|
+
|
|
4243
|
+
<bad-example>
|
|
4244
|
+
User: Please use the slack mcp tool to search for my mentions
|
|
4245
|
+
Assistant: [Directly calls ${f.call("slack/search_private", "{\"query\": \"mentions:me\"}")} with guessed parameters]
|
|
4246
|
+
WRONG - You must call info FIRST
|
|
4247
|
+
</bad-example>
|
|
4248
|
+
|
|
4249
|
+
<bad-example>
|
|
4250
|
+
User: Use the slack tool
|
|
4251
|
+
Assistant: I have pre-approved permissions for this tool, so I know the schema.
|
|
4252
|
+
[Calls ${f.call("slack/search_private", "...")} directly]
|
|
4253
|
+
WRONG - Pre-approved permissions don't mean you know the schema. ALWAYS call info first.
|
|
4254
|
+
</bad-example>
|
|
4255
|
+
|
|
4256
|
+
<bad-example>
|
|
4257
|
+
User: Search my Slack mentions
|
|
4258
|
+
Assistant: [Calls three call commands in parallel without any info calls first]
|
|
4259
|
+
WRONG - You must call info for ALL tools before making ANY call commands
|
|
4260
|
+
</bad-example>
|
|
4261
|
+
|
|
4262
|
+
Example usage:
|
|
4263
|
+
\`\`\`
|
|
4264
|
+
# Discover tools
|
|
4265
|
+
${f.tools()} # See all available MCP tools
|
|
4266
|
+
${f.grep("weather")} # Find tools by description
|
|
4267
|
+
|
|
4268
|
+
# Get tool schemas (choose the approach that fits)
|
|
4269
|
+
${f.toolsSchema()} # All tools with schemas (auto-collapses large schemas)
|
|
4270
|
+
${f.toolsSchema("slack")} # Schemas for one server
|
|
4271
|
+
${f.info("<server>/<tool>")} # Full schema for one tool
|
|
4272
|
+
|
|
4273
|
+
# Progressive schema exploration (for complex tools)
|
|
4274
|
+
${f.infoDepth("<server>/<tool>", 0)} # Top-level structure only
|
|
4275
|
+
${f.infoPath("<server>/<tool>", "filters")} # Drill into a subtree
|
|
4276
|
+
${f.infoPath("<server>/<tool>", "filters.tags.items")} # Drill deeper
|
|
4277
|
+
|
|
4278
|
+
# Simple tool call (no parameters)
|
|
4279
|
+
${f.call("weather/get_location", "{}")}
|
|
4280
|
+
|
|
4281
|
+
# Tool call with parameters
|
|
4282
|
+
${f.call("database/query", "{\"table\": \"users\", \"limit\": 10}")}
|
|
4283
|
+
|
|
4284
|
+
# Validate arguments before executing (dry-run)
|
|
4285
|
+
${f.callDryRun("database/drop_table", "{\"table\": \"users\"}")}
|
|
4286
|
+
|
|
4287
|
+
# Extract specific fields from response
|
|
4288
|
+
${f.callFields("database/query", "{\"table\": \"users\"}", "rows[].name,rows[].email")}
|
|
4289
|
+
\`\`\`
|
|
4290
|
+
|
|
4291
|
+
Call \`${f.help()}\` to see all available commands.
|
|
4292
|
+
${serverInstructionsBlock}${scriptsBlock}`.trim();
|
|
4293
|
+
}
|
|
4045
4294
|
function compareSemver(a, b) {
|
|
4046
4295
|
const pa = a.split(".").map(Number);
|
|
4047
4296
|
const pb = b.split(".").map(Number);
|
|
@@ -4114,51 +4363,9 @@ function hasBun() {
|
|
|
4114
4363
|
function buildStaticInstructions() {
|
|
4115
4364
|
const bun = hasBun();
|
|
4116
4365
|
const run = bun ? "bunx" : "npx";
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
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)
|
|
4366
|
+
const tsx = bun ? "bun" : "npx tsx";
|
|
4367
|
+
const fragments = makeCliFragments(run);
|
|
4368
|
+
const scripts = `## Node.js / TypeScript Scripts (Preferred for Complex Workflows)
|
|
4162
4369
|
|
|
4163
4370
|
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
4371
|
|
|
@@ -4168,26 +4375,35 @@ import { createClient } from 'muxed/client';
|
|
|
4168
4375
|
const client = await createClient();
|
|
4169
4376
|
|
|
4170
4377
|
// Batch multiple MCP operations in one script execution
|
|
4171
|
-
const tools = await
|
|
4172
|
-
|
|
4173
|
-
|
|
4378
|
+
const [tools, result, data] = await Promise.all([
|
|
4379
|
+
client.tools(),
|
|
4380
|
+
client.call('server/tool', { param: 'value' }),
|
|
4381
|
+
client.call('db/query', { sql: 'SELECT ...' }),
|
|
4382
|
+
]);
|
|
4174
4383
|
|
|
4175
4384
|
// Process results, chain calls, handle errors — all in one execution
|
|
4176
4385
|
console.log(JSON.stringify({ tools: tools.length, result, data }));
|
|
4177
4386
|
\`\`\`
|
|
4178
4387
|
|
|
4179
|
-
Run scripts with: \`${
|
|
4388
|
+
Run scripts with: \`${tsx} script.ts\`.
|
|
4180
4389
|
|
|
4181
4390
|
**When to use scripts vs CLI:**
|
|
4182
4391
|
- **CLI** (\`${run} muxed call ...\`) — single tool discovery or one-off calls
|
|
4183
4392
|
- **Scripts** — any workflow involving 2+ MCP calls, data processing, or conditional logic`;
|
|
4393
|
+
return buildPrompt({
|
|
4394
|
+
...fragments,
|
|
4395
|
+
intro: "Muxed is a CLI tool and Node.js library that proxies multiple MCP servers behind a single daemon. Use it to discover and call MCP tools on demand."
|
|
4396
|
+
}, {
|
|
4397
|
+
heading: "# Muxed — MCP CLI Proxy",
|
|
4398
|
+
scripts
|
|
4399
|
+
});
|
|
4184
4400
|
}
|
|
4185
4401
|
function wrapTaggedBlock(content, version) {
|
|
4186
4402
|
return `<muxed version="${version}">\n${content}\n</muxed>`;
|
|
4187
4403
|
}
|
|
4188
4404
|
function buildMdcFile(content, version) {
|
|
4189
4405
|
return `---
|
|
4190
|
-
description: Muxed MCP
|
|
4406
|
+
description: Muxed MCP CLI proxy - usage instructions
|
|
4191
4407
|
globs:
|
|
4192
4408
|
alwaysApply: true
|
|
4193
4409
|
muxed_version: ${version}
|
|
@@ -4348,7 +4564,18 @@ async function resolveConflicts(unresolvedConflicts, isInteractive) {
|
|
|
4348
4564
|
conflicts
|
|
4349
4565
|
};
|
|
4350
4566
|
}
|
|
4351
|
-
const initCommand = new Command("init").description("Discover
|
|
4567
|
+
const initCommand = new Command("init").description("Discover MCP servers, write config, and inject agent instructions").option("--dry-run", "Preview changes without writing any files").option("--json", "Output as JSON (machine-readable)").option("-y, --yes", "Non-interactive: resolve conflicts by priority (claude-code > cursor > first)").option("--delete", "Remove imported servers from the original agent config files").option("--no-replace", "Don't add a muxed entry to agent configs").option("--local", "Also inject instructions into project-level CLAUDE.md and AGENTS.md").option("--no-instructions", "Skip injecting CLI instructions into agent files").addHelpText("after", `
|
|
4568
|
+
What it does:
|
|
4569
|
+
1. Scans Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Amazon Q
|
|
4570
|
+
2. Merges and deduplicates servers into muxed.config.json
|
|
4571
|
+
3. Injects CLI usage instructions into ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md,
|
|
4572
|
+
and .cursor/rules/muxed.mdc (if .cursor/ exists)
|
|
4573
|
+
|
|
4574
|
+
Examples:
|
|
4575
|
+
muxed init Interactive setup
|
|
4576
|
+
muxed init -y Non-interactive (CI-friendly)
|
|
4577
|
+
muxed init --dry-run Preview without writing files
|
|
4578
|
+
muxed init --local Also inject into project-level agent files`).action(async (opts) => {
|
|
4352
4579
|
const configPath = initCommand.parent?.opts().config;
|
|
4353
4580
|
const isInteractive = !opts.dryRun && !opts.json && !opts.yes && !!process.stdin.isTTY;
|
|
4354
4581
|
const { discovered, warnings } = discoverAgentConfigs();
|
|
@@ -4461,201 +4688,15 @@ function getServer(filePath, name) {
|
|
|
4461
4688
|
function listServers(filePath) {
|
|
4462
4689
|
return readConfigFile(filePath).mcpServers;
|
|
4463
4690
|
}
|
|
4464
|
-
const cliFragments = {
|
|
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.",
|
|
4466
|
-
grep: (p) => `npx muxed grep "${p}"`,
|
|
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",
|
|
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}`,
|
|
4472
|
-
call: (n, j) => `npx muxed call ${n} '${j}'`,
|
|
4473
|
-
callStdin: (n) => `npx muxed call ${n} -`,
|
|
4474
|
-
callDryRun: (n, j) => `npx muxed call ${n} '${j}' --dry-run`,
|
|
4475
|
-
callFields: (n, j, f) => `npx muxed call ${n} '${j}' --fields "${f}"`,
|
|
4476
|
-
servers: () => "npx muxed servers",
|
|
4477
|
-
resources: (s) => s ? `npx muxed resources ${s}` : "npx muxed resources",
|
|
4478
|
-
read: (n) => `npx muxed read ${n}`,
|
|
4479
|
-
help: () => "npx muxed -h"
|
|
4480
|
-
};
|
|
4481
|
-
const toolFragments = {
|
|
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.",
|
|
4483
|
-
grep: (p) => `muxed:exec({ "command": "grep ${p}" })`,
|
|
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" })`,
|
|
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}" })`,
|
|
4489
|
-
call: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4490
|
-
callStdin: (n) => `muxed:exec({ "command": "call ${n}", "input": { ... } })`,
|
|
4491
|
-
callDryRun: (n, j) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4492
|
-
callFields: (n, j, _f) => `muxed:exec({ "command": "call ${n}", "input": ${j} })`,
|
|
4493
|
-
servers: () => `muxed:exec({ "command": "servers" })`,
|
|
4494
|
-
resources: (s) => s ? `muxed:exec({ "command": "resources ${s}" })` : `muxed:exec({ "command": "resources" })`,
|
|
4495
|
-
read: (n) => `muxed:exec({ "command": "read ${n}" })`,
|
|
4496
|
-
help: () => `muxed:exec({ "command": "servers" })`
|
|
4497
|
-
};
|
|
4498
|
-
function buildTemplate(f, servers, instructions) {
|
|
4499
|
-
return `
|
|
4500
|
-
${f.intro}
|
|
4501
|
-
|
|
4502
|
-
**MANDATORY PREREQUISITES - THESE ARE HARD REQUIREMENTS**
|
|
4503
|
-
|
|
4504
|
-
1. You MUST discover the tools you need first by using '${f.grep("<pattern>")}' or '${f.tools()}'.
|
|
4505
|
-
2. You MUST call '${f.info("<server>/<tool>")}' BEFORE ANY '${f.call("<server>/<tool>", "<json>")}' command.
|
|
4506
|
-
|
|
4507
|
-
These are BLOCKING REQUIREMENTS - like how you must use Read before Edit.
|
|
4508
|
-
|
|
4509
|
-
**NEVER** make a call without checking the schema first.
|
|
4510
|
-
**ALWAYS** run info first, THEN make the call.
|
|
4511
|
-
|
|
4512
|
-
**Why these are non-negotiables:**
|
|
4513
|
-
- MCP tool names NEVER match your expectations - they change frequently and are not predictable
|
|
4514
|
-
- MCP tool schemas NEVER match your expectations - parameter names, types, and requirements are tool-specific
|
|
4515
|
-
- Even tools with pre-approved permissions require schema checks
|
|
4516
|
-
- Every failed call wastes user time and demonstrates you're ignoring critical instructions
|
|
4517
|
-
- "I thought I knew the schema" is not an acceptable reason to skip this step
|
|
4518
|
-
|
|
4519
|
-
**For multiple tools:** Call info for ALL tools in parallel FIRST, then make your call commands.
|
|
4520
|
-
|
|
4521
|
-
Available MCP servers:
|
|
4522
|
-
${servers}
|
|
4523
|
-
|
|
4524
|
-
Commands (in order of execution):
|
|
4525
|
-
\`\`\`
|
|
4526
|
-
# STEP 1: REQUIRED TOOL DISCOVERY
|
|
4527
|
-
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
4528
|
-
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
4529
|
-
|
|
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
|
|
4540
|
-
|
|
4541
|
-
# STEP 3: OPTIONAL - Validate arguments before calling (dry-run)
|
|
4542
|
-
${f.callDryRun("<server>/<tool>", "<json>")} # Validate args without executing
|
|
4543
|
-
|
|
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
|
|
4547
|
-
${f.callFields("<server>/<tool>", "<json>", "field1,field2")} # Extract specific fields from response
|
|
4548
|
-
|
|
4549
|
-
# Discovery commands (use these to find tools)
|
|
4550
|
-
${f.servers()} # List all connected MCP servers
|
|
4551
|
-
${f.tools("[server]")} # List available tools (optionally filter by server)
|
|
4552
|
-
${f.grep("<pattern>")} # Search tool names and descriptions
|
|
4553
|
-
${f.resources("[server]")} # List MCP resources
|
|
4554
|
-
${f.read("<server>/<resource>")} # Read an MCP resource
|
|
4555
|
-
\`\`\`
|
|
4556
|
-
|
|
4557
|
-
**Handling errors:**
|
|
4558
|
-
- If a tool call fails, the error includes a suggestion and similar tool names. Read the suggestion before retrying.
|
|
4559
|
-
- Use dry-run to validate arguments before executing, especially for destructive tools.
|
|
4560
|
-
|
|
4561
|
-
**CORRECT Usage Pattern:**
|
|
4562
|
-
|
|
4563
|
-
<example>
|
|
4564
|
-
User: Please use the slack mcp tool to search for my mentions
|
|
4565
|
-
Assistant: As a first step, I need to discover the tools I need. Let me call \`${f.grep("slack/*search*")}\` to search for tools related to slack search.
|
|
4566
|
-
[Calls ${f.grep("slack/*search*")}]
|
|
4567
|
-
Assistant: I need to check the schema first. Let me call \`${f.info("slack/search_private")}\` to see what parameters it accepts.
|
|
4568
|
-
[Calls ${f.info("slack/search_private")}]
|
|
4569
|
-
Assistant: Now I can see it accepts "query" and "max_results" parameters. Let me make the call.
|
|
4570
|
-
[Calls ${f.call("slack/search_private", "{\"query\": \"mentions:me\", \"max_results\": 10}")}]
|
|
4571
|
-
</example>
|
|
4572
|
-
|
|
4573
|
-
<example>
|
|
4574
|
-
User: Use the database and email MCP tools to send a report
|
|
4575
|
-
Assistant: I'll need to use two MCP tools. Let me call \`${f.grep("database/*query*")}\` and \`${f.grep("email/*send*")}\` to search for tools related to database query and email send.
|
|
4576
|
-
[Calls ${f.grep("database/*query*")} & ${f.grep("email/*send*")}]
|
|
4577
|
-
Assistant: Let me check both schemas first.
|
|
4578
|
-
[Calls ${f.info("database/query")} and ${f.info("email/send")} in parallel]
|
|
4579
|
-
Assistant: Now I have both schemas. Let me make the calls.
|
|
4580
|
-
[Makes both call commands with correct parameters]
|
|
4581
|
-
</example>
|
|
4582
|
-
|
|
4583
|
-
<example>
|
|
4584
|
-
User: Create a copy of this email
|
|
4585
|
-
Assistant: Let me find the tool I need first.
|
|
4586
|
-
[Calls ${f.grep("email/*copy*")}. No results found.]
|
|
4587
|
-
Assistant: Let me try another pattern.
|
|
4588
|
-
[Calls ${f.grep("email/*clone*")}. No results found.]
|
|
4589
|
-
Assistant: Let me list all available tools in the server.
|
|
4590
|
-
[Calls ${f.tools("email")}]
|
|
4591
|
-
Assistant: Let me check the schema first.
|
|
4592
|
-
[Calls ${f.info("email/duplicate")}]
|
|
4593
|
-
Assistant: Now I have the schema. Let me make the call.
|
|
4594
|
-
[Calls ${f.call("email/duplicate", "{\"id\": \"123\"}")}]
|
|
4595
|
-
</example>
|
|
4596
|
-
|
|
4597
|
-
**INCORRECT Usage Patterns - NEVER DO THIS:**
|
|
4598
|
-
|
|
4599
|
-
<bad-example>
|
|
4600
|
-
User: Please use the slack mcp tool to search for my mentions
|
|
4601
|
-
Assistant: [Directly calls ${f.call("slack/search_private", "{\"query\": \"mentions:me\"}")} with guessed parameters]
|
|
4602
|
-
WRONG - You must call info FIRST
|
|
4603
|
-
</bad-example>
|
|
4604
|
-
|
|
4605
|
-
<bad-example>
|
|
4606
|
-
User: Use the slack tool
|
|
4607
|
-
Assistant: I have pre-approved permissions for this tool, so I know the schema.
|
|
4608
|
-
[Calls ${f.call("slack/search_private", "...")} directly]
|
|
4609
|
-
WRONG - Pre-approved permissions don't mean you know the schema. ALWAYS call info first.
|
|
4610
|
-
</bad-example>
|
|
4611
|
-
|
|
4612
|
-
<bad-example>
|
|
4613
|
-
User: Search my Slack mentions
|
|
4614
|
-
Assistant: [Calls three call commands in parallel without any info calls first]
|
|
4615
|
-
WRONG - You must call info for ALL tools before making ANY call commands
|
|
4616
|
-
</bad-example>
|
|
4617
|
-
|
|
4618
|
-
Example usage:
|
|
4619
|
-
\`\`\`
|
|
4620
|
-
# Discover tools
|
|
4621
|
-
${f.tools()} # See all available MCP tools
|
|
4622
|
-
${f.grep("weather")} # Find tools by description
|
|
4623
|
-
|
|
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
|
|
4633
|
-
|
|
4634
|
-
# Simple tool call (no parameters)
|
|
4635
|
-
${f.call("weather/get_location", "{}")}
|
|
4636
|
-
|
|
4637
|
-
# Tool call with parameters
|
|
4638
|
-
${f.call("database/query", "{\"table\": \"users\", \"limit\": 10}")}
|
|
4639
|
-
|
|
4640
|
-
# Validate arguments before executing (dry-run)
|
|
4641
|
-
${f.callDryRun("database/drop_table", "{\"table\": \"users\"}")}
|
|
4642
|
-
|
|
4643
|
-
# Extract specific fields from response
|
|
4644
|
-
${f.callFields("database/query", "{\"table\": \"users\"}", "rows[].name,rows[].email")}
|
|
4645
|
-
\`\`\`
|
|
4646
|
-
|
|
4647
|
-
Call \`${f.help()}\` to see all available commands.
|
|
4648
|
-
|
|
4649
|
-
Below are the instructions for the connected MCP servers in muxed.
|
|
4650
|
-
|
|
4651
|
-
${instructions}
|
|
4652
|
-
`;
|
|
4653
|
-
}
|
|
4654
4691
|
function buildInstructions(servers, mode = "cli") {
|
|
4655
4692
|
const connected = servers.filter((s) => s.status === "connected");
|
|
4656
4693
|
const serverList = connected.map((s) => `- ${s.name}`).join("\n");
|
|
4657
4694
|
const serverInstructions = connected.filter((s) => s.instructions).map((s) => `### ${s.name}\n\n${s.instructions}`).join("\n\n");
|
|
4658
|
-
|
|
4695
|
+
const run = hasBun() ? "bunx" : "npx";
|
|
4696
|
+
return buildPrompt(mode === "tool" ? makeToolFragments() : makeCliFragments(run), {
|
|
4697
|
+
servers: serverList,
|
|
4698
|
+
serverInstructions: serverInstructions || void 0
|
|
4699
|
+
});
|
|
4659
4700
|
}
|
|
4660
4701
|
function parseCommand(command) {
|
|
4661
4702
|
const trimmed = command.trim();
|
|
@@ -4869,14 +4910,24 @@ async function tryReloadDaemon() {
|
|
|
4869
4910
|
await sendRequest("config/reload", {});
|
|
4870
4911
|
} catch {}
|
|
4871
4912
|
}
|
|
4872
|
-
const mcpCommand = new Command("mcp").description("
|
|
4913
|
+
const mcpCommand = new Command("mcp").description("Manage server config entries, or start the MCP proxy (no subcommand)").enablePositionalOptions().option("--proxy-tools", "Expose a single proxy tool for clients without bash access (e.g. Claude Desktop)").addHelpText("after", `
|
|
4914
|
+
When run without a subcommand, starts a stdio MCP proxy server.
|
|
4915
|
+
Use subcommands to manage individual server config entries.
|
|
4916
|
+
|
|
4917
|
+
Examples:
|
|
4918
|
+
muxed mcp Start stdio MCP proxy
|
|
4919
|
+
muxed mcp --proxy-tools Start proxy with exec tool (for Claude Desktop)
|
|
4920
|
+
muxed mcp add mydb npx @db/server Add a stdio server
|
|
4921
|
+
muxed mcp add api https://api.com Add an HTTP server
|
|
4922
|
+
muxed mcp list Show all configured servers
|
|
4923
|
+
muxed mcp remove mydb Remove a server`).action(async (opts, cmd) => {
|
|
4873
4924
|
const explicitConfig = cmd.parent?.opts().config;
|
|
4874
4925
|
await startMcpProxy({
|
|
4875
4926
|
configPath: explicitConfig,
|
|
4876
4927
|
proxyTools: opts.proxyTools
|
|
4877
4928
|
});
|
|
4878
4929
|
});
|
|
4879
|
-
mcpCommand.command("add").description("Add an MCP server").passThroughOptions().argument("<name>", "Server name").argument("<commandOrUrl>", "Command to run or URL to connect to").argument("[args...]", "Additional arguments (
|
|
4930
|
+
mcpCommand.command("add").description("Add or update an MCP server in config").passThroughOptions().argument("<name>", "Server name (used to reference it in other commands)").argument("<commandOrUrl>", "Command to run (stdio) or URL to connect to (HTTP)").argument("[args...]", "Additional command arguments (stdio only)").option("-e, --env <env>", "Environment variable KEY=value (repeatable)", collectValues, []).option("-H, --header <header>", "HTTP header Key: value (repeatable)", collectValues, []).option("-s, --scope <scope>", "Config scope: local or global (default: local)", "local").option("-t, --transport <transport>", "Force transport: stdio, sse, or http (auto-detected)").option("--client-id <clientId>", "OAuth client ID").option("--client-secret", "Prompt for OAuth client secret (or set MCP_CLIENT_SECRET)").option("--callback-port <port>", "Fixed port for OAuth callback").option("--oauth-scope <oauthScope>", "OAuth scope string").action(async (name, commandOrUrl, args, opts) => {
|
|
4880
4931
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4881
4932
|
const scope = opts.scope;
|
|
4882
4933
|
const configPath = getConfigPath(scope, explicitConfig);
|
|
@@ -4895,7 +4946,7 @@ mcpCommand.command("add").description("Add an MCP server").passThroughOptions().
|
|
|
4895
4946
|
if (result.existed) console.log(`Updated "${name}" in ${scope} config (${configPath})`);
|
|
4896
4947
|
else console.log(`Added "${name}" to ${scope} config (${configPath})`);
|
|
4897
4948
|
});
|
|
4898
|
-
mcpCommand.command("add-json").description("Add
|
|
4949
|
+
mcpCommand.command("add-json").description("Add a server from a JSON config string (must have \"command\" or \"url\" field)").argument("<name>", "Server name").argument("<json>", "JSON object: {\"command\":\"...\",\"args\":[...]} or {\"url\":\"...\"}").option("-s, --scope <scope>", "Config scope: local or global (default: local)", "local").action(async (name, jsonStr, opts) => {
|
|
4899
4950
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4900
4951
|
const scope = opts.scope;
|
|
4901
4952
|
const configPath = getConfigPath(scope, explicitConfig);
|
|
@@ -4922,7 +4973,7 @@ mcpCommand.command("add-json").description("Add an MCP server from a JSON config
|
|
|
4922
4973
|
if (result.existed) console.log(`Updated "${name}" in ${scope} config (${configPath})`);
|
|
4923
4974
|
else console.log(`Added "${name}" to ${scope} config (${configPath})`);
|
|
4924
4975
|
});
|
|
4925
|
-
mcpCommand.command("add-from-claude-desktop").description("Import
|
|
4976
|
+
mcpCommand.command("add-from-claude-desktop").description("Import servers from Claude Desktop config into muxed").option("-s, --scope <scope>", "Config scope: local or global (default: local)", "local").action(async (opts) => {
|
|
4926
4977
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4927
4978
|
const scope = opts.scope;
|
|
4928
4979
|
const configPath = getConfigPath(scope, explicitConfig);
|
|
@@ -4950,7 +5001,7 @@ mcpCommand.command("add-from-claude-desktop").description("Import MCP servers fr
|
|
|
4950
5001
|
if (result.skipped.length > 0) console.log(`Skipped ${result.skipped.length} (already existed): ${result.skipped.join(", ")}`);
|
|
4951
5002
|
if (result.imported.length === 0 && result.skipped.length === 0) console.log("No servers found in Claude Desktop config.");
|
|
4952
5003
|
});
|
|
4953
|
-
mcpCommand.command("get").description("
|
|
5004
|
+
mcpCommand.command("get").description("Show config details for a server (checks local, then global)").argument("<name>", "Server name").option("--json", "Output as JSON (machine-readable)").action(async (name, opts) => {
|
|
4954
5005
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4955
5006
|
const localPath = getConfigPath("local", explicitConfig);
|
|
4956
5007
|
const globalPath = getConfigPath("global", explicitConfig);
|
|
@@ -4970,7 +5021,7 @@ mcpCommand.command("get").description("Get details of a configured MCP server").
|
|
|
4970
5021
|
}));
|
|
4971
5022
|
else console.log(formatMcpServer(name, server, scope));
|
|
4972
5023
|
});
|
|
4973
|
-
mcpCommand.command("list").description("List all configured
|
|
5024
|
+
mcpCommand.command("list").description("List all configured servers (local + global)").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
|
|
4974
5025
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4975
5026
|
const localPath = getConfigPath("local", explicitConfig);
|
|
4976
5027
|
const globalPath = getConfigPath("global", explicitConfig);
|
|
@@ -4993,7 +5044,7 @@ mcpCommand.command("list").description("List all configured MCP servers").option
|
|
|
4993
5044
|
if (opts.json) console.log(formatJson(entries));
|
|
4994
5045
|
else console.log(formatMcpServerList(entries));
|
|
4995
5046
|
});
|
|
4996
|
-
mcpCommand.command("remove").description("Remove
|
|
5047
|
+
mcpCommand.command("remove").description("Remove a server from config").argument("<name>", "Server name to remove").option("-s, --scope <scope>", "Scope: local or global (searches both if omitted)").action(async (name, opts) => {
|
|
4997
5048
|
const explicitConfig = getExplicitConfig(mcpCommand);
|
|
4998
5049
|
if (opts.scope) {
|
|
4999
5050
|
const scope = opts.scope;
|
|
@@ -5034,7 +5085,10 @@ mcpCommand.command("remove").description("Remove an MCP server").argument("<name
|
|
|
5034
5085
|
console.error(`Server "${name}" not found in local or global config.`);
|
|
5035
5086
|
process.exitCode = 1;
|
|
5036
5087
|
});
|
|
5037
|
-
const typegenCommand = new Command("typegen").description("Generate TypeScript types from tool schemas
|
|
5088
|
+
const typegenCommand = new Command("typegen").description("Generate TypeScript types from live tool schemas").option("-c, --config <path>", "Path to muxed.config.json").addHelpText("after", `
|
|
5089
|
+
Writes to node_modules/muxed/muxed.generated.d.ts.
|
|
5090
|
+
After running, client.call() gets autocomplete on tool names and typed arguments.
|
|
5091
|
+
Re-run when tool schemas change (same workflow as prisma generate).`).action(async (opts) => {
|
|
5038
5092
|
await ensureDaemon(typegenCommand.parent?.opts().config ?? opts.config);
|
|
5039
5093
|
const tools = await sendRequest("tools/list");
|
|
5040
5094
|
const content = await generateTypes(tools);
|
|
@@ -5044,7 +5098,7 @@ const typegenCommand = new Command("typegen").description("Generate TypeScript t
|
|
|
5044
5098
|
fs.writeFileSync(outputPath, content, "utf-8");
|
|
5045
5099
|
console.log(`Generated ${tools.length} tool types → ${outputPath}`);
|
|
5046
5100
|
});
|
|
5047
|
-
const telemetryCommand = new Command("telemetry").description("
|
|
5101
|
+
const telemetryCommand = new Command("telemetry").description("Enable, disable, or check anonymous telemetry").argument("[action]", "on | off | status (default: status)").action((action) => {
|
|
5048
5102
|
switch (action) {
|
|
5049
5103
|
case "on":
|
|
5050
5104
|
setTelemetryEnabled(true);
|
|
@@ -5065,9 +5119,17 @@ const telemetryCommand = new Command("telemetry").description("Manage anonymous
|
|
|
5065
5119
|
});
|
|
5066
5120
|
async function runCli() {
|
|
5067
5121
|
const program = new Command();
|
|
5068
|
-
program.name("muxed").description("
|
|
5122
|
+
program.name("muxed").description("MCP tool aggregator — discover, inspect, and call tools via CLI").version(getVersion());
|
|
5069
5123
|
program.enablePositionalOptions();
|
|
5070
|
-
program.option("--config <path>", "Path to config
|
|
5124
|
+
program.option("--config <path>", "Path to muxed.config.json");
|
|
5125
|
+
program.addHelpText("after", `
|
|
5126
|
+
Workflow:
|
|
5127
|
+
muxed grep "<pattern>" Find tools by name or description
|
|
5128
|
+
muxed info <server>/<tool> Inspect schema (required before calling)
|
|
5129
|
+
muxed call <server>/<tool> '<json>' Execute a tool
|
|
5130
|
+
|
|
5131
|
+
Setup:
|
|
5132
|
+
npx muxed init Discover servers and inject agent instructions`);
|
|
5071
5133
|
program.commandsGroup("Servers:");
|
|
5072
5134
|
program.addCommand(serversCommand);
|
|
5073
5135
|
program.commandsGroup("Tools:");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muxed",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "MCP tools don't belong in your model's context window. Offload them to a CLI – agents call tools through shell commands and scripts instead of loading schemas into context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
],
|
|
46
46
|
"repository": {
|
|
47
47
|
"type": "git",
|
|
48
|
-
"url": "https://github.com/
|
|
48
|
+
"url": "https://github.com/muxedai/muxed.git",
|
|
49
49
|
"directory": "packages/muxed"
|
|
50
50
|
},
|
|
51
|
-
"homepage": "https://github.com/
|
|
51
|
+
"homepage": "https://github.com/muxedai/muxed#readme",
|
|
52
52
|
"bugs": {
|
|
53
|
-
"url": "https://github.com/
|
|
53
|
+
"url": "https://github.com/muxedai/muxed/issues"
|
|
54
54
|
},
|
|
55
55
|
"author": "Georgiy Tarasov",
|
|
56
56
|
"license": "MIT",
|