@evantahler/mcpx 0.18.2 → 0.18.5

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 (53) hide show
  1. package/package.json +63 -62
  2. package/src/cli.ts +46 -54
  3. package/src/client/browser.ts +15 -15
  4. package/src/client/debug-fetch.ts +53 -56
  5. package/src/client/elicitation.ts +279 -291
  6. package/src/client/http.ts +1 -1
  7. package/src/client/manager.ts +481 -514
  8. package/src/client/oauth.ts +272 -282
  9. package/src/client/sse.ts +1 -1
  10. package/src/client/stdio.ts +7 -7
  11. package/src/client/trace.ts +146 -152
  12. package/src/client/transport-options.ts +20 -20
  13. package/src/commands/add.ts +160 -165
  14. package/src/commands/allow.ts +141 -142
  15. package/src/commands/auth.ts +86 -90
  16. package/src/commands/check-update.ts +49 -53
  17. package/src/commands/deny.ts +114 -117
  18. package/src/commands/exec.ts +218 -222
  19. package/src/commands/index.ts +41 -41
  20. package/src/commands/info.ts +48 -50
  21. package/src/commands/list.ts +49 -49
  22. package/src/commands/ping.ts +47 -50
  23. package/src/commands/prompt.ts +40 -50
  24. package/src/commands/remove.ts +54 -56
  25. package/src/commands/resource.ts +31 -36
  26. package/src/commands/search.ts +35 -39
  27. package/src/commands/servers.ts +44 -48
  28. package/src/commands/skill.ts +89 -95
  29. package/src/commands/task.ts +50 -60
  30. package/src/commands/upgrade.ts +191 -208
  31. package/src/commands/with-command.ts +27 -29
  32. package/src/config/env.ts +26 -28
  33. package/src/config/loader.ts +99 -102
  34. package/src/config/schemas.ts +78 -87
  35. package/src/constants.ts +17 -17
  36. package/src/context.ts +51 -51
  37. package/src/lib/client-settings.ts +127 -140
  38. package/src/lib/input.ts +23 -26
  39. package/src/output/format-output.ts +12 -16
  40. package/src/output/format-table.ts +39 -42
  41. package/src/output/formatter.ts +790 -814
  42. package/src/output/logger.ts +140 -152
  43. package/src/sdk.ts +283 -291
  44. package/src/search/index.ts +50 -54
  45. package/src/search/indexer.ts +65 -65
  46. package/src/search/keyword.ts +54 -54
  47. package/src/search/semantic.ts +39 -39
  48. package/src/search/staleness.ts +3 -3
  49. package/src/search/types.ts +4 -4
  50. package/src/update/background.ts +51 -51
  51. package/src/update/cache.ts +21 -21
  52. package/src/update/checker.ts +81 -86
  53. package/src/validation/schema.ts +52 -58
package/package.json CHANGED
@@ -1,64 +1,65 @@
1
1
  {
2
- "name": "@evantahler/mcpx",
3
- "version": "0.18.2",
4
- "description": "A command-line interface for MCP servers. curl for MCP.",
5
- "type": "module",
6
- "exports": {
7
- ".": "./src/sdk.ts",
8
- "./cli": "./src/cli.ts"
9
- },
10
- "main": "./src/sdk.ts",
11
- "types": "./src/sdk.ts",
12
- "bin": {
13
- "mcpx": "./src/cli.ts"
14
- },
15
- "files": [
16
- "src",
17
- ".claude",
18
- ".cursor",
19
- "README.md",
20
- "LICENSE"
21
- ],
22
- "scripts": {
23
- "dev": "bun run src/cli.ts",
24
- "test": "bun test",
25
- "test:e2e": "bun test test/integration/remote-server.test.ts",
26
- "lint": "prettier --check .",
27
- "format": "prettier --write .",
28
- "build": "bun build --compile --minify --sourcemap ./src/cli.ts --outfile dist/mcpx"
29
- },
30
- "publishConfig": {
31
- "access": "public"
32
- },
33
- "repository": {
34
- "type": "git",
35
- "url": "https://github.com/evantahler/mcpx.git"
36
- },
37
- "keywords": [
38
- "mcp",
39
- "cli",
40
- "model-context-protocol",
41
- "ai",
42
- "llm",
43
- "tools"
44
- ],
45
- "author": "Evan Tahler",
46
- "license": "MIT",
47
- "dependencies": {
48
- "@huggingface/transformers": "^3.8.1",
49
- "@modelcontextprotocol/sdk": "^1.27.1",
50
- "ajv": "^8.18.0",
51
- "ansis": "^4.2.0",
52
- "commander": "^14.0.3",
53
- "nanospinner": "^1.2.2",
54
- "picomatch": "^4.0.3"
55
- },
56
- "devDependencies": {
57
- "@types/bun": "latest",
58
- "@types/picomatch": "^4.0.2",
59
- "prettier": "^3.8.1"
60
- },
61
- "peerDependencies": {
62
- "typescript": "^5"
63
- }
2
+ "name": "@evantahler/mcpx",
3
+ "version": "0.18.5",
4
+ "description": "A command-line interface for MCP servers. curl for MCP.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/sdk.ts",
8
+ "./cli": "./src/cli.ts"
9
+ },
10
+ "main": "./src/sdk.ts",
11
+ "types": "./src/sdk.ts",
12
+ "bin": {
13
+ "mcpx": "./src/cli.ts"
14
+ },
15
+ "files": [
16
+ "src",
17
+ ".claude",
18
+ ".cursor",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "dev": "bun run src/cli.ts",
24
+ "test": "bun test",
25
+ "test:e2e": "bun test test/integration/remote-server.test.ts",
26
+ "lint": "biome ci . && tsc --noEmit",
27
+ "format": "biome check --write .",
28
+ "build": "bun build --compile --minify --sourcemap ./src/cli.ts --outfile dist/mcpx"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/evantahler/mcpx.git"
36
+ },
37
+ "keywords": [
38
+ "mcp",
39
+ "cli",
40
+ "model-context-protocol",
41
+ "ai",
42
+ "llm",
43
+ "tools"
44
+ ],
45
+ "author": "Evan Tahler",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "@huggingface/transformers": "^3.8.1",
49
+ "@modelcontextprotocol/sdk": "^1.27.1",
50
+ "ajv": "^8.18.0",
51
+ "ansis": "^4.2.0",
52
+ "commander": "^14.0.3",
53
+ "nanospinner": "^1.2.2",
54
+ "picomatch": "^4.0.3",
55
+ "@types/picomatch": "^4.0.2"
56
+ },
57
+ "devDependencies": {
58
+ "@biomejs/biome": "^2.4.11",
59
+ "@types/bun": "latest",
60
+ "typescript": "^5"
61
+ },
62
+ "peerDependencies": {
63
+ "typescript": "^5"
64
+ }
64
65
  }
package/src/cli.ts CHANGED
@@ -1,53 +1,52 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { bold, cyan, dim, green, yellow } from "ansis";
3
4
  import { program } from "commander";
4
- import { bold, cyan, yellow, dim, green } from "ansis";
5
- import { registerListCommand } from "./commands/list.ts";
6
- import { registerInfoCommand } from "./commands/info.ts";
7
- import { registerSearchCommand } from "./commands/search.ts";
8
- import { registerExecCommand } from "./commands/exec.ts";
5
+ import pkg from "../package.json";
6
+ import { registerAddCommand } from "./commands/add.ts";
7
+ import { registerAllowCommand } from "./commands/allow.ts";
9
8
  import { registerAuthCommand, registerDeauthCommand } from "./commands/auth.ts";
9
+ import { registerCheckUpdateCommand } from "./commands/check-update.ts";
10
+ import { registerDenyCommand } from "./commands/deny.ts";
11
+ import { registerExecCommand } from "./commands/exec.ts";
10
12
  import { registerIndexCommand } from "./commands/index.ts";
11
- import { registerAddCommand } from "./commands/add.ts";
12
- import { registerRemoveCommand } from "./commands/remove.ts";
13
- import { registerSkillCommand } from "./commands/skill.ts";
13
+ import { registerInfoCommand } from "./commands/info.ts";
14
+ import { registerListCommand } from "./commands/list.ts";
14
15
  import { registerPingCommand } from "./commands/ping.ts";
15
- import { registerResourceCommand } from "./commands/resource.ts";
16
16
  import { registerPromptCommand } from "./commands/prompt.ts";
17
+ import { registerRemoveCommand } from "./commands/remove.ts";
18
+ import { registerResourceCommand } from "./commands/resource.ts";
19
+ import { registerSearchCommand } from "./commands/search.ts";
17
20
  import { registerServersCommand } from "./commands/servers.ts";
21
+ import { registerSkillCommand } from "./commands/skill.ts";
18
22
  import { registerTaskCommand } from "./commands/task.ts";
19
- import { registerAllowCommand } from "./commands/allow.ts";
20
- import { registerDenyCommand } from "./commands/deny.ts";
21
- import { registerCheckUpdateCommand } from "./commands/check-update.ts";
22
23
  import { registerUpgradeCommand } from "./commands/upgrade.ts";
23
24
  import { maybeCheckForUpdate } from "./update/background.ts";
24
25
 
25
- import pkg from "../package.json";
26
-
27
26
  program
28
- .name("mcpx")
29
- .description("A command-line interface for MCP servers. curl for MCP.")
30
- .version(pkg.version)
31
- .option("-c, --config <path>", "config directory path")
32
- .option("-d, --with-descriptions", "include tool descriptions in output")
33
- .option("-j, --json", "force JSON output")
34
- .option("-F, --format <format>", "output format (json, markdown)")
35
- .option("-v, --verbose", "show HTTP details and JSON-RPC protocol messages")
36
- .option("-S, --show-secrets", "show full auth tokens in verbose output")
37
- .option("-N, --no-interactive", "decline server elicitation requests")
38
- .option(
39
- "-l, --log-level <level>",
40
- "minimum server log level (debug|info|notice|warning|error|critical|alert|emergency)",
41
- "warning",
42
- );
27
+ .name("mcpx")
28
+ .description("A command-line interface for MCP servers. curl for MCP.")
29
+ .version(pkg.version)
30
+ .option("-c, --config <path>", "config directory path")
31
+ .option("-d, --with-descriptions", "include tool descriptions in output")
32
+ .option("-j, --json", "force JSON output")
33
+ .option("-F, --format <format>", "output format (json, markdown)")
34
+ .option("-v, --verbose", "show HTTP details and JSON-RPC protocol messages")
35
+ .option("-S, --show-secrets", "show full auth tokens in verbose output")
36
+ .option("-N, --no-interactive", "decline server elicitation requests")
37
+ .option(
38
+ "-l, --log-level <level>",
39
+ "minimum server log level (debug|info|notice|warning|error|critical|alert|emergency)",
40
+ "warning",
41
+ );
43
42
 
44
43
  program.configureHelp({
45
- styleTitle: (str) => bold(str),
46
- styleCommandText: (str) => cyan(str),
47
- styleSubcommandText: (str) => cyan(str),
48
- styleOptionText: (str) => yellow(str),
49
- styleArgumentText: (str) => green(str),
50
- styleDescriptionText: (str) => dim(str),
44
+ styleTitle: (str) => bold(str),
45
+ styleCommandText: (str) => cyan(str),
46
+ styleSubcommandText: (str) => cyan(str),
47
+ styleOptionText: (str) => yellow(str),
48
+ styleArgumentText: (str) => green(str),
49
+ styleDescriptionText: (str) => dim(str),
51
50
  });
52
51
 
53
52
  registerListCommand(program);
@@ -75,25 +74,18 @@ const knownCommands = new Set(program.commands.map((c) => c.name()));
75
74
  const cliArgs = process.argv.slice(2);
76
75
  let firstCommand: string | undefined;
77
76
  for (let i = 0; i < cliArgs.length; i++) {
78
- const a = cliArgs[i];
79
- if (
80
- a === "-c" ||
81
- a === "--config" ||
82
- a === "-l" ||
83
- a === "--log-level" ||
84
- a === "-F" ||
85
- a === "--format"
86
- ) {
87
- i++; // skip the option's value argument
88
- continue;
89
- }
90
- if (a.startsWith("-")) continue;
91
- firstCommand = a;
92
- break;
77
+ const a = cliArgs[i]!;
78
+ if (a === "-c" || a === "--config" || a === "-l" || a === "--log-level" || a === "-F" || a === "--format") {
79
+ i++; // skip the option's value argument
80
+ continue;
81
+ }
82
+ if (a.startsWith("-")) continue;
83
+ firstCommand = a;
84
+ break;
93
85
  }
94
86
  if (firstCommand && !knownCommands.has(firstCommand)) {
95
- console.error(`error: unknown command '${firstCommand}'. See 'mcpx --help'.`);
96
- process.exit(1);
87
+ console.error(`error: unknown command '${firstCommand}'. See 'mcpx --help'.`);
88
+ process.exit(1);
97
89
  }
98
90
 
99
91
  // Fire-and-forget background update check
@@ -103,6 +95,6 @@ program.parse();
103
95
 
104
96
  // Print update notice after command output completes
105
97
  process.on("beforeExit", async () => {
106
- const notice = await updateNotice;
107
- if (notice) process.stderr.write(notice);
98
+ const notice = await updateNotice;
99
+ if (notice) process.stderr.write(notice);
108
100
  });
@@ -1,4 +1,4 @@
1
- import { exec } from "child_process";
1
+ import { exec } from "node:child_process";
2
2
 
3
3
  /**
4
4
  * Open a URL in the default browser (macOS/Windows/Linux).
@@ -6,19 +6,19 @@ import { exec } from "child_process";
6
6
  * (e.g., headless servers, Docker containers).
7
7
  */
8
8
  export function openBrowser(url: string): Promise<void> {
9
- const cmd =
10
- process.platform === "darwin"
11
- ? `open "${url}"`
12
- : process.platform === "win32"
13
- ? `start "${url}"`
14
- : `xdg-open "${url}"`;
9
+ const cmd =
10
+ process.platform === "darwin"
11
+ ? `open "${url}"`
12
+ : process.platform === "win32"
13
+ ? `start "${url}"`
14
+ : `xdg-open "${url}"`;
15
15
 
16
- return new Promise((resolve) => {
17
- exec(cmd, (err) => {
18
- if (err) {
19
- process.stderr.write(`Could not open browser. Please visit:\n ${url}\n`);
20
- }
21
- resolve();
22
- });
23
- });
16
+ return new Promise((resolve) => {
17
+ exec(cmd, (err) => {
18
+ if (err) {
19
+ process.stderr.write(`Could not open browser. Please visit:\n ${url}\n`);
20
+ }
21
+ resolve();
22
+ });
23
+ });
24
24
  }
@@ -4,78 +4,75 @@ import { logger } from "../output/logger.ts";
4
4
  export type FetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;
5
5
 
6
6
  export function createDebugFetch(showSecrets: boolean): FetchLike {
7
- const isTTY = process.stderr.isTTY ?? false;
8
- const fmt = (s: string) => (isTTY ? dim(s) : s);
7
+ const isTTY = process.stderr.isTTY ?? false;
8
+ const fmt = (s: string) => (isTTY ? dim(s) : s);
9
9
 
10
- return async (url, init) => {
11
- const start = performance.now();
10
+ return async (url, init) => {
11
+ const start = performance.now();
12
12
 
13
- // Request
14
- log("");
15
- log(fmt(`> ${init?.method ?? "GET"} ${url}`));
16
- logHeaders(">", init?.headers, fmt, showSecrets);
17
- log(fmt(">"));
18
- if (init?.body) {
19
- logBody(String(init.body), fmt);
20
- }
13
+ // Request
14
+ log("");
15
+ log(fmt(`> ${init?.method ?? "GET"} ${url}`));
16
+ logHeaders(">", init?.headers, fmt, showSecrets);
17
+ log(fmt(">"));
18
+ if (init?.body) {
19
+ logBody(String(init.body), fmt);
20
+ }
21
21
 
22
- const response = await fetch(url, init);
23
- const elapsed = Math.round(performance.now() - start);
22
+ const response = await fetch(url, init);
23
+ const elapsed = Math.round(performance.now() - start);
24
24
 
25
- // Response
26
- log(fmt(`< ${response.status} ${response.statusText} (${elapsed}ms)`));
27
- logHeaders("<", response.headers, fmt, showSecrets);
28
- log(fmt("<"));
29
- log("");
25
+ // Response
26
+ log(fmt(`< ${response.status} ${response.statusText} (${elapsed}ms)`));
27
+ logHeaders("<", response.headers, fmt, showSecrets);
28
+ log(fmt("<"));
29
+ log("");
30
30
 
31
- return response;
32
- };
31
+ return response;
32
+ };
33
33
  }
34
34
 
35
35
  function log(line: string) {
36
- logger.writeRaw(line + "\n");
36
+ logger.writeRaw(`${line}\n`);
37
37
  }
38
38
 
39
- function logHeaders(
40
- prefix: string,
41
- headers: HeadersInit | Headers | undefined,
42
- fmt: (s: string) => string,
43
- showSecrets: boolean,
44
- ) {
45
- if (!headers) return;
39
+ function logHeaders(prefix: string, headers: RequestInit["headers"], fmt: (s: string) => string, showSecrets: boolean) {
40
+ if (!headers) return;
46
41
 
47
- const format = (key: string, value: string) =>
48
- fmt(`${prefix} ${key}: ${showSecrets ? value : maskSensitive(key, value)}`);
42
+ const format = (key: string, value: string) =>
43
+ fmt(`${prefix} ${key}: ${showSecrets ? value : maskSensitive(key, value)}`);
49
44
 
50
- if (headers instanceof Headers) {
51
- headers.forEach((value, key) => log(format(key, value)));
52
- } else if (Array.isArray(headers)) {
53
- for (const [key, value] of headers) {
54
- log(format(key, value));
55
- }
56
- } else {
57
- for (const [key, value] of Object.entries(headers)) {
58
- log(format(key, value));
59
- }
60
- }
45
+ if (headers instanceof Headers) {
46
+ headers.forEach((value, key) => {
47
+ log(format(key, value));
48
+ });
49
+ } else if (Array.isArray(headers)) {
50
+ for (const pair of headers) {
51
+ log(format(String(pair[0]), String(pair[1])));
52
+ }
53
+ } else {
54
+ for (const [key, value] of Object.entries(headers as Record<string, string>)) {
55
+ log(format(key, value));
56
+ }
57
+ }
61
58
  }
62
59
 
63
60
  function logBody(body: string, fmt: (s: string) => string) {
64
- try {
65
- const formatted = JSON.stringify(JSON.parse(body), null, 2);
66
- for (const line of formatted.split("\n")) {
67
- log(fmt(line));
68
- }
69
- } catch {
70
- log(fmt(body));
71
- }
61
+ try {
62
+ const formatted = JSON.stringify(JSON.parse(body), null, 2);
63
+ for (const line of formatted.split("\n")) {
64
+ log(fmt(line));
65
+ }
66
+ } catch {
67
+ log(fmt(body));
68
+ }
72
69
  }
73
70
 
74
71
  export function maskSensitive(key: string, value: string): string {
75
- const lower = key.toLowerCase();
76
- if (lower === "authorization" || lower === "cookie" || lower === "set-cookie") {
77
- if (value.length <= 12) return value;
78
- return value.slice(0, 12) + "...";
79
- }
80
- return value;
72
+ const lower = key.toLowerCase();
73
+ if (lower === "authorization" || lower === "cookie" || lower === "set-cookie") {
74
+ if (value.length <= 12) return value;
75
+ return `${value.slice(0, 12)}...`;
76
+ }
77
+ return value;
81
78
  }