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.
Files changed (2) hide show
  1. package/dist/cli.mjs +333 -271
  2. 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 connection status").option("--json", "Output as JSON").action(async (opts) => {
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 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) => {
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 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) => {
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 (use - for stdin, --async for background)").argument("<server/tool>", "Tool identifier (e.g. myserver/mytool)").argument("[json]", "JSON arguments (use - for stdin)").option("--timeout <ms>", "Request timeout in milliseconds").option("--async", "Use task-based execution (return task handle immediately)").option("--dry-run", "Validate arguments against tool schema without executing").option("--fields <paths>", "Comma-separated dot-notation paths to extract from response").option("--json", "Output as JSON").action(async (serverTool, jsonArgs, opts) => {
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 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) => {
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, optionally filtered by server name").argument("[server]", "Filter by server name").option("--json", "Output as JSON").action(async (server, opts) => {
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 a resource by server/resource").argument("<server/resource>", "Resource identifier (e.g. myserver/myresource)").argument("[uri]", "Resource URI (optional, uses resource name as URI if not provided)").option("--json", "Output as JSON").action(async (serverResource, uri, opts) => {
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("Start, stop, reload, or check status of the muxed background daemon").enablePositionalOptions();
3582
- daemonCommand.command("start").description("Start the daemon process in the background").option("--json", "Output as JSON").action(async (opts) => {
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 without restarting").option("--json", "Output as JSON").action(async (opts) => {
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 daemon status including uptime and connected servers").option("--json", "Output as JSON").action(async (opts) => {
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, optionally filtered by server name").argument("[server]", "Filter by server name").option("--json", "Output as JSON").action(async (server, opts) => {
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 optional JSON arguments").argument("<server/prompt>", "Prompt identifier (e.g. myserver/myprompt)").argument("[args-json]", "JSON arguments").option("--json", "Output as JSON").action(async (serverPrompt, argsJson, opts) => {
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 auto-completion suggestions for prompt or resource arguments").argument("<type>", "Reference type (prompt or resource)").argument("<name>", "Prompt or resource template name (server/name)").argument("<arg>", "Argument name").argument("<value>", "Partial value for completion").option("--json", "Output as JSON").action(async (type, name, arg, value, opts) => {
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, optionally filtered by server name").argument("[server]", "Filter by server name").option("--json", "Output as JSON").action(async (server, opts) => {
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 current status of an async task").argument("<server/taskId>", "Task identifier (e.g. myserver/task-123)").option("--json", "Output as JSON").action(async (serverTaskId, opts) => {
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("Retrieve the output of a completed async task").argument("<server/taskId>", "Task identifier (e.g. myserver/task-123)").option("--json", "Output as JSON").action(async (serverTaskId, opts) => {
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>", "Task identifier (e.g. myserver/task-123)").option("--json", "Output as JSON").action(async (serverTaskId, opts) => {
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
- 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)
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 client.tools();
4172
- const result = await client.call('server/tool', { param: 'value' });
4173
- const data = await client.call('db/query', { sql: 'SELECT ...' });
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: \`${bun ? "bun" : "npx tsx"} script.ts\`.
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 server aggregator - CLI usage instructions
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 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) => {
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
- return buildTemplate(mode === "tool" ? toolFragments : cliFragments, serverList, serverInstructions).trim();
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("Add, remove, list, or inspect individual MCP server config entries").enablePositionalOptions().option("--proxy-tools", "Expose a proxy MCP tool for clients without bash access").action(async (opts, cmd) => {
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 (for stdio servers)").option("-e, --env <env>", "Set environment variables (KEY=value), repeatable", collectValues, []).option("-H, --header <header>", "Set HTTP headers (Key: value), repeatable", collectValues, []).option("-s, --scope <scope>", "Config scope: local, global", "local").option("-t, --transport <transport>", "Transport: stdio, sse, http").option("--client-id <clientId>", "OAuth client ID").option("--client-secret", "Prompt for OAuth client secret (or use MCP_CLIENT_SECRET env)").option("--callback-port <port>", "Fixed port for OAuth callback").option("--oauth-scope <oauthScope>", "OAuth scope string").action(async (name, commandOrUrl, args, opts) => {
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 an MCP server from a JSON config string").argument("<name>", "Server name").argument("<json>", "JSON server configuration").option("-s, --scope <scope>", "Config scope: local, global", "local").action(async (name, jsonStr, opts) => {
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 MCP servers from Claude Desktop config").option("-s, --scope <scope>", "Config scope: local, global", "local").action(async (opts) => {
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("Get details of a configured MCP server").argument("<name>", "Server name").option("--json", "Output as JSON").action(async (name, opts) => {
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 MCP servers").option("--json", "Output as JSON").action(async (opts) => {
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 an MCP server").argument("<name>", "Server name").option("-s, --scope <scope>", "Config scope: local, global (searches both if not specified)").action(async (name, opts) => {
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 for type-safe tool calls").option("-c, --config <path>", "Path to muxed.config.json").action(async (opts) => {
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("Manage anonymous telemetry (on, off, status)").argument("[action]", "on | off | status (default: status)").action((action) => {
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("The optimization layer for MCP").version(getVersion());
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 file");
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.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/skoob13/muxed.git",
48
+ "url": "https://github.com/muxedai/muxed.git",
49
49
  "directory": "packages/muxed"
50
50
  },
51
- "homepage": "https://github.com/skoob13/muxed#readme",
51
+ "homepage": "https://github.com/muxedai/muxed#readme",
52
52
  "bugs": {
53
- "url": "https://github.com/skoob13/muxed/issues"
53
+ "url": "https://github.com/muxedai/muxed/issues"
54
54
  },
55
55
  "author": "Georgiy Tarasov",
56
56
  "license": "MIT",