@evantahler/mcpx 0.16.1 → 0.16.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.
@@ -25,7 +25,8 @@ This shows parameters, types, required fields, and the full JSON Schema.
25
25
  ## 3. Execute the tool
26
26
 
27
27
  ```bash
28
- mcpx exec <server> <tool> '<json args>'
28
+ mcpx exec <tool> '<json args>' # server auto-resolved if unambiguous
29
+ mcpx exec <server> <tool> '<json args>' # explicit server (required if tool name exists on multiple servers)
29
30
  mcpx exec <server> <tool> -f params.json
30
31
  ```
31
32
 
@@ -52,7 +53,10 @@ mcpx search "send a message"
52
53
  # See what parameters Slack_SendMessage needs
53
54
  mcpx info arcade Slack_SendMessage
54
55
 
55
- # Send a message
56
+ # Send a message (server optional if tool name is unique)
57
+ mcpx exec Slack_SendMessage '{"channel":"#general","message":"hello"}'
58
+
59
+ # Or explicitly specify the server
56
60
  mcpx exec arcade Slack_SendMessage '{"channel":"#general","message":"hello"}'
57
61
 
58
62
  # Chain commands — search repos and read the first result
@@ -118,7 +122,8 @@ mcpx deauth <server> # remove stored auth
118
122
  | `mcpx info <server>` | Server overview (version, capabilities, tools) |
119
123
  | `mcpx info <server> <tool>` | Show tool schema |
120
124
  | `mcpx exec <server>` | List tools for a server |
121
- | `mcpx exec <server> <tool> '<json>'` | Execute a tool |
125
+ | `mcpx exec <tool> '<json>'` | Execute tool (server auto-resolved) |
126
+ | `mcpx exec <server> <tool> '<json>'` | Execute tool (explicit server) |
122
127
  | `mcpx exec <server> <tool> -f file` | Execute with args from file |
123
128
  | `mcpx search "<query>"` | Search tools (keyword + semantic) |
124
129
  | `mcpx search -k "<pattern>"` | Keyword/glob search only |
@@ -25,7 +25,8 @@ This shows parameters, types, required fields, and the full JSON Schema.
25
25
  ## 3. Execute the tool
26
26
 
27
27
  ```bash
28
- mcpx exec <server> <tool> '<json args>'
28
+ mcpx exec <tool> '<json args>' # server auto-resolved if unambiguous
29
+ mcpx exec <server> <tool> '<json args>' # explicit server (required if tool name exists on multiple servers)
29
30
  mcpx exec <server> <tool> -f params.json
30
31
  ```
31
32
 
@@ -52,7 +53,10 @@ mcpx search "send a message"
52
53
  # See what parameters Slack_SendMessage needs
53
54
  mcpx info arcade Slack_SendMessage
54
55
 
55
- # Send a message
56
+ # Send a message (server optional if tool name is unique)
57
+ mcpx exec Slack_SendMessage '{"channel":"#general","message":"hello"}'
58
+
59
+ # Or explicitly specify the server
56
60
  mcpx exec arcade Slack_SendMessage '{"channel":"#general","message":"hello"}'
57
61
 
58
62
  # Chain commands — search repos and read the first result
@@ -118,7 +122,8 @@ mcpx deauth <server> # remove stored auth
118
122
  | `mcpx info <server>` | Server overview (version, capabilities, tools) |
119
123
  | `mcpx info <server> <tool>` | Show tool schema |
120
124
  | `mcpx exec <server>` | List tools for a server |
121
- | `mcpx exec <server> <tool> '<json>'` | Execute a tool |
125
+ | `mcpx exec <tool> '<json>'` | Execute tool (server auto-resolved) |
126
+ | `mcpx exec <server> <tool> '<json>'` | Execute tool (explicit server) |
122
127
  | `mcpx exec <server> <tool> -f file` | Execute with args from file |
123
128
  | `mcpx search "<query>"` | Search tools (keyword + semantic) |
124
129
  | `mcpx search -k "<pattern>"` | Keyword/glob search only |
package/README.md CHANGED
@@ -12,14 +12,19 @@ Two audiences:
12
12
  ## Install
13
13
 
14
14
  ```bash
15
- # Via bun
15
+ # Via bun (all platforms)
16
16
  bun install -g @evantahler/mcpx
17
17
 
18
- # Via curl
18
+ # Via curl (macOS/Linux)
19
19
  curl -fsSL https://raw.githubusercontent.com/evantahler/mcpx/main/install.sh | bash
20
20
  ```
21
21
 
22
- The curl installer downloads a pre-built binary (macOS/Linux) — no runtime needed. The bun install method requires [Bun](https://bun.sh). Windows `.exe` binaries are available on the [GitHub Releases](https://github.com/evantahler/mcpx/releases) page.
22
+ ```powershell
23
+ # Via PowerShell (Windows)
24
+ irm https://raw.githubusercontent.com/evantahler/mcpx/main/install.ps1 | iex
25
+ ```
26
+
27
+ The curl/PowerShell installers download a pre-built binary — no runtime needed. The bun install method requires [Bun](https://bun.sh). Binaries for all platforms are also available on the [GitHub Releases](https://github.com/evantahler/mcpx/releases) page.
23
28
 
24
29
  ## Quick Start
25
30
 
@@ -42,6 +47,9 @@ mcpx info github search_repositories
42
47
  # Execute a tool
43
48
  mcpx exec github search_repositories '{"query": "mcp server"}'
44
49
 
50
+ # Execute a tool without specifying the server (auto-resolved)
51
+ mcpx exec search_repositories '{"query": "mcp server"}'
52
+
45
53
  # Search tools — combines keyword and semantic matching
46
54
  mcpx search "post a ticket to linear"
47
55
 
@@ -70,6 +78,7 @@ mcpx search -n 5 "manage pull requests"
70
78
  | `mcpx index` | Build/rebuild the search index |
71
79
  | `mcpx index -i` | Show index status |
72
80
  | `mcpx exec <server> <tool> [json]` | Validate inputs locally, then execute tool |
81
+ | `mcpx exec <tool> [json]` | Execute tool (server auto-resolved if unambiguous) |
73
82
  | `mcpx exec <server> <tool> -f file` | Read tool args from a JSON file |
74
83
  | `mcpx exec <server>` | List available tools for a server |
75
84
  | `mcpx auth <server>` | Authenticate with an HTTP MCP server (OAuth) |
@@ -89,8 +98,8 @@ mcpx search -n 5 "manage pull requests"
89
98
  | `mcpx prompt` | List all prompts across all servers |
90
99
  | `mcpx prompt <server>` | List prompts for a server |
91
100
  | `mcpx prompt <server> <name> [json]` | Get a specific prompt |
92
- | `mcpx exec <server> <tool> --no-wait` | Execute as async task, return task handle immediately |
93
- | `mcpx exec <server> <tool> --ttl <ms>` | Set task TTL in milliseconds (default: 60000) |
101
+ | `mcpx exec [server] <tool> --no-wait` | Execute as async task, return task handle immediately |
102
+ | `mcpx exec [server] <tool> --ttl <ms>` | Set task TTL in milliseconds (default: 60000) |
94
103
  | `mcpx task list <server>` | List tasks on a server |
95
104
  | `mcpx task get <server> <taskId>` | Get task status |
96
105
  | `mcpx task result <server> <taskId>` | Retrieve completed task result |
@@ -579,7 +588,7 @@ Then in any Claude Code session, the agent can use `/mcpx` or the skill triggers
579
588
 
580
589
  1. **Search first** — `mcpx search "<intent>"` to find relevant tools
581
590
  2. **Inspect** — `mcpx info <server> <tool>` to get the schema before calling
582
- 3. **Execute** — `mcpx exec <server> <tool> '<json>'` to execute
591
+ 3. **Execute** — `mcpx exec <tool> '<json>'` to execute (or `mcpx exec <server> <tool> '<json>'` if the tool name is ambiguous)
583
592
 
584
593
  This keeps tool schemas out of the system prompt entirely. The agent discovers what it needs on-demand, saving tokens and context window space.
585
594
 
@@ -614,7 +623,8 @@ To discover tools:
614
623
  mcpx info <server> <tool> # tool schema
615
624
 
616
625
  To execute tools:
617
- mcpx exec <server> <tool> '<json args>'
626
+ mcpx exec <tool> '<json args>' # server auto-resolved
627
+ mcpx exec <server> <tool> '<json args>' # explicit server
618
628
  mcpx exec <server> <tool> -f params.json
619
629
 
620
630
  Always search before executing — don't assume tool names.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evantahler/mcpx",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "description": "A command-line interface for MCP servers. curl for MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,27 +11,108 @@ import { logger } from "../output/logger.ts";
11
11
  import { validateToolInput } from "../validation/schema.ts";
12
12
  import { parseJsonArgs, readStdin } from "../lib/input.ts";
13
13
  import { DEFAULTS } from "../constants.ts";
14
+ import type { ServerManager } from "../client/manager.ts";
15
+
16
+ type ResolvedArgs =
17
+ | { mode: "list-tools"; server: string }
18
+ | { mode: "call-tool"; server: string; tool: string; argsStr: string | undefined };
19
+
20
+ /**
21
+ * Resolve the positional args into either list-tools or call-tool mode.
22
+ * Supports both `exec <server> <tool> [args]` and `exec <tool> [args]`.
23
+ */
24
+ async function resolveExecArgs(
25
+ manager: ServerManager,
26
+ first: string,
27
+ second: string | undefined,
28
+ third: string | undefined,
29
+ ): Promise<ResolvedArgs> {
30
+ const serverNames = manager.getServerNames();
31
+ const isServer = serverNames.includes(first);
32
+
33
+ if (isServer) {
34
+ // Traditional form: exec <server> [tool] [args]
35
+ if (!second) {
36
+ return { mode: "list-tools", server: first };
37
+ }
38
+
39
+ // Validate the tool exists on the specified server
40
+ const serverTools = await manager.listTools(first);
41
+ const toolExists = serverTools.some((t) => t.name === second);
42
+
43
+ if (!toolExists) {
44
+ const { tools } = await manager.getAllTools();
45
+ const matches = tools.filter((t) => t.tool.name === second);
46
+
47
+ if (matches.length === 1) {
48
+ throw new Error(
49
+ `Tool "${second}" not found on server "${first}". Did you mean:\n mcpx exec ${matches[0]!.server} ${second}`,
50
+ );
51
+ } else if (matches.length > 1) {
52
+ const servers = matches.map((m) => m.server).join(", ");
53
+ throw new Error(
54
+ `Tool "${second}" not found on server "${first}". Found on: ${servers}\nUsage: mcpx exec <server> ${second} [args]`,
55
+ );
56
+ } else {
57
+ throw new Error(
58
+ `Tool "${second}" not found on server "${first}". Run "mcpx search ${second}" to find similar tools.`,
59
+ );
60
+ }
61
+ }
62
+
63
+ return { mode: "call-tool", server: first, tool: second, argsStr: third };
64
+ }
65
+
66
+ // Not a server name — treat first as a tool name
67
+ const toolName = first;
68
+ const { tools } = await manager.getAllTools();
69
+ const matches = tools.filter((t) => t.tool.name === toolName);
70
+
71
+ if (matches.length === 0) {
72
+ throw new Error(
73
+ `Unknown server or tool "${first}". Run "mcpx search ${first}" to find similar tools.`,
74
+ );
75
+ }
76
+
77
+ if (matches.length > 1) {
78
+ const servers = matches.map((m) => m.server).join(", ");
79
+ throw new Error(
80
+ `Ambiguous tool "${toolName}" — found on multiple servers: ${servers}\nSpecify the server: mcpx exec <server> ${toolName} [args]`,
81
+ );
82
+ }
83
+
84
+ return { mode: "call-tool", server: matches[0]!.server, tool: toolName, argsStr: second };
85
+ }
14
86
 
15
87
  export function registerExecCommand(program: Command) {
16
88
  program
17
- .command("exec <server> [tool] [args]")
18
- .description("execute a tool (omit tool name to list available tools)")
89
+ .command("exec <first> [second] [third]")
90
+ .description("execute a tool (server is optional if tool name is unambiguous)")
19
91
  .option("-f, --file <path>", "read JSON args from a file")
20
92
  .option("--no-wait", "return task handle immediately without waiting for completion")
21
93
  .option("--ttl <ms>", "task TTL in milliseconds", String(DEFAULTS.TASK_TTL_MS))
22
94
  .action(
23
95
  async (
24
- server: string,
25
- tool: string | undefined,
26
- argsStr: string | undefined,
96
+ first: string,
97
+ second: string | undefined,
98
+ third: string | undefined,
27
99
  options: { file?: string; wait: boolean; ttl: string },
28
100
  ) => {
29
101
  const { manager, formatOptions } = await getContext(program);
30
102
 
31
- if (!tool) {
103
+ let resolved: ResolvedArgs;
104
+ try {
105
+ resolved = await resolveExecArgs(manager, first, second, third);
106
+ } catch (err) {
107
+ console.error(formatError(String(err), formatOptions));
108
+ await manager.close();
109
+ process.exit(1);
110
+ }
111
+
112
+ if (resolved.mode === "list-tools") {
32
113
  try {
33
- const tools = await manager.listTools(server);
34
- console.log(formatServerTools(server, tools, formatOptions));
114
+ const tools = await manager.listTools(resolved.server);
115
+ console.log(formatServerTools(resolved.server, tools, formatOptions));
35
116
  } catch (err) {
36
117
  console.error(formatError(String(err), formatOptions));
37
118
  process.exit(1);
@@ -40,6 +121,9 @@ export function registerExecCommand(program: Command) {
40
121
  }
41
122
  return;
42
123
  }
124
+
125
+ const { server, tool, argsStr } = resolved;
126
+
43
127
  try {
44
128
  // Error if both --file and positional arg provided
45
129
  if (options.file && argsStr) {