@evantahler/mcpx 0.18.3 → 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 -63
  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
@@ -1,50 +1,46 @@
1
1
  import type { Command } from "commander";
2
+ import { DEFAULTS } from "../constants.ts";
2
3
  import { getContext } from "../context.ts";
3
- import { search } from "../search/index.ts";
4
- import { getStaleServers } from "../search/staleness.ts";
5
4
  import { formatError, formatSearchResults } from "../output/formatter.ts";
6
5
  import { logger } from "../output/logger.ts";
7
- import { DEFAULTS } from "../constants.ts";
6
+ import { search } from "../search/index.ts";
7
+ import { getStaleServers } from "../search/staleness.ts";
8
8
 
9
9
  export function registerSearchCommand(program: Command) {
10
- program
11
- .command("search <terms...>")
12
- .description("search tools by keyword and/or semantic similarity")
13
- .option("-k, --keyword", "keyword/glob search only")
14
- .option("-q, --query", "semantic search only")
15
- .option("-n, --limit <number>", "max results to return", String(DEFAULTS.SEARCH_TOP_K))
16
- .action(
17
- async (terms: string[], options: { keyword?: boolean; query?: boolean; limit: string }) => {
18
- const query = terms.join(" ");
19
- const { config, formatOptions } = await getContext(program);
10
+ program
11
+ .command("search <terms...>")
12
+ .description("search tools by keyword and/or semantic similarity")
13
+ .option("-k, --keyword", "keyword/glob search only")
14
+ .option("-q, --query", "semantic search only")
15
+ .option("-n, --limit <number>", "max results to return", String(DEFAULTS.SEARCH_TOP_K))
16
+ .action(async (terms: string[], options: { keyword?: boolean; query?: boolean; limit: string }) => {
17
+ const query = terms.join(" ");
18
+ const { config, formatOptions } = await getContext(program);
20
19
 
21
- if (config.searchIndex.tools.length === 0) {
22
- console.error(formatError("No search index found. Run: mcpx index", formatOptions));
23
- process.exit(1);
24
- }
20
+ if (config.searchIndex.tools.length === 0) {
21
+ console.error(formatError("No search index found. Run: mcpx index", formatOptions));
22
+ process.exit(1);
23
+ }
25
24
 
26
- const stale = getStaleServers(config.searchIndex, config.servers);
27
- if (stale.length > 0) {
28
- logger.warn(
29
- `Warning: index has tools for removed servers: ${stale.join(", ")}. Run: mcpx index`,
30
- );
31
- }
25
+ const stale = getStaleServers(config.searchIndex, config.servers);
26
+ if (stale.length > 0) {
27
+ logger.warn(`Warning: index has tools for removed servers: ${stale.join(", ")}. Run: mcpx index`);
28
+ }
32
29
 
33
- const spinner = logger.startSpinner("Searching...", formatOptions);
30
+ const spinner = logger.startSpinner("Searching...", formatOptions);
34
31
 
35
- try {
36
- const results = await search(query, config.searchIndex, {
37
- keywordOnly: options.keyword,
38
- semanticOnly: options.query,
39
- topK: parseInt(options.limit, 10),
40
- });
41
- spinner.stop();
42
- console.log(formatSearchResults(results, formatOptions));
43
- } catch (err) {
44
- spinner.stop();
45
- console.error(formatError(String(err), formatOptions));
46
- process.exit(1);
47
- }
48
- },
49
- );
32
+ try {
33
+ const results = await search(query, config.searchIndex, {
34
+ keywordOnly: options.keyword,
35
+ semanticOnly: options.query,
36
+ topK: parseInt(options.limit, 10),
37
+ });
38
+ spinner.stop();
39
+ console.log(formatSearchResults(results, formatOptions));
40
+ } catch (err) {
41
+ spinner.stop();
42
+ console.error(formatError(String(err), formatOptions));
43
+ process.exit(1);
44
+ }
45
+ });
50
46
  }
@@ -1,60 +1,56 @@
1
1
  import { cyan, dim, green, yellow } from "ansis";
2
2
  import type { Command } from "commander";
3
- import { isStdioServer, isHttpServer } from "../config/schemas.ts";
3
+ import { isHttpServer, isStdioServer } from "../config/schemas.ts";
4
4
  import { isInteractive } from "../output/formatter.ts";
5
5
  import { withCommand } from "./with-command.ts";
6
6
 
7
7
  export function registerServersCommand(program: Command) {
8
- program
9
- .command("servers")
10
- .description("List configured MCP servers")
11
- .action(
12
- withCommand(program, {}, async ({ config, formatOptions }) => {
13
- const servers = Object.entries(config.servers.mcpServers);
8
+ program
9
+ .command("servers")
10
+ .description("List configured MCP servers")
11
+ .action(
12
+ withCommand(program, {}, async ({ config, formatOptions }) => {
13
+ const servers = Object.entries(config.servers.mcpServers);
14
14
 
15
- if (!isInteractive(formatOptions)) {
16
- console.log(
17
- JSON.stringify(
18
- servers.map(([name, cfg]) => ({
19
- name,
20
- type: isStdioServer(cfg) ? "stdio" : "http",
21
- ...(isHttpServer(cfg) ? { transport: cfg.transport ?? "http" } : {}),
22
- ...(isStdioServer(cfg)
23
- ? { command: cfg.command, args: cfg.args ?? [] }
24
- : { url: cfg.url }),
25
- })),
26
- null,
27
- 2,
28
- ),
29
- );
30
- return;
31
- }
15
+ if (!isInteractive(formatOptions)) {
16
+ console.log(
17
+ JSON.stringify(
18
+ servers.map(([name, cfg]) => ({
19
+ name,
20
+ type: isStdioServer(cfg) ? "stdio" : "http",
21
+ ...(isHttpServer(cfg) ? { transport: cfg.transport ?? "http" } : {}),
22
+ ...(isStdioServer(cfg) ? { command: cfg.command, args: cfg.args ?? [] } : { url: cfg.url }),
23
+ })),
24
+ null,
25
+ 2,
26
+ ),
27
+ );
28
+ return;
29
+ }
32
30
 
33
- if (servers.length === 0) {
34
- console.log(dim("No servers configured"));
35
- return;
36
- }
31
+ if (servers.length === 0) {
32
+ console.log(dim("No servers configured"));
33
+ return;
34
+ }
37
35
 
38
- const maxName = Math.max(...servers.map(([n]) => n.length));
36
+ const maxName = Math.max(...servers.map(([n]) => n.length));
39
37
 
40
- function typeLabel(cfg: (typeof servers)[number][1]): string {
41
- if (isStdioServer(cfg)) return "stdio";
42
- if (isHttpServer(cfg) && cfg.transport === "sse") return "http/sse";
43
- if (isHttpServer(cfg) && cfg.transport === "streamable-http") return "http/streamable";
44
- return "http";
45
- }
46
- const maxType = Math.max(...servers.map(([, cfg]) => typeLabel(cfg).length));
38
+ function typeLabel(cfg: (typeof servers)[number][1]): string {
39
+ if (isStdioServer(cfg)) return "stdio";
40
+ if (isHttpServer(cfg) && cfg.transport === "sse") return "http/sse";
41
+ if (isHttpServer(cfg) && cfg.transport === "streamable-http") return "http/streamable";
42
+ return "http";
43
+ }
44
+ const maxType = Math.max(...servers.map(([, cfg]) => typeLabel(cfg).length));
47
45
 
48
- for (const [name, cfg] of servers) {
49
- const n = cyan(name.padEnd(maxName));
50
- const type = isStdioServer(cfg)
51
- ? green(typeLabel(cfg).padEnd(maxType))
52
- : yellow(typeLabel(cfg).padEnd(maxType));
53
- const detail = isStdioServer(cfg)
54
- ? dim([cfg.command, ...(cfg.args ?? [])].join(" "))
55
- : dim(cfg.url);
56
- console.log(`${n} ${type} ${detail}`);
57
- }
58
- }),
59
- );
46
+ for (const [name, cfg] of servers) {
47
+ const n = cyan(name.padEnd(maxName));
48
+ const type = isStdioServer(cfg)
49
+ ? green(typeLabel(cfg).padEnd(maxType))
50
+ : yellow(typeLabel(cfg).padEnd(maxType));
51
+ const detail = isStdioServer(cfg) ? dim([cfg.command, ...(cfg.args ?? [])].join(" ")) : dim(cfg.url);
52
+ console.log(`${n} ${type} ${detail}`);
53
+ }
54
+ }),
55
+ );
60
56
  }
@@ -1,112 +1,106 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
1
4
  import type { Command } from "commander";
2
- import { resolve, dirname, join } from "path";
3
- import { readFile, mkdir, writeFile, access } from "fs/promises";
4
- import { homedir } from "os";
5
5
 
6
6
  interface SkillTarget {
7
- label: string;
8
- dir: string;
9
- filename: string;
7
+ label: string;
8
+ dir: string;
9
+ filename: string;
10
10
  }
11
11
 
12
12
  export function registerSkillCommand(program: Command) {
13
- const skill = program.command("skill").description("manage mcpx skills");
13
+ const skill = program.command("skill").description("manage mcpx skills");
14
14
 
15
- skill
16
- .command("install")
17
- .description("install the mcpx skill for an AI agent")
18
- .option("--claude", "install for Claude Code")
19
- .option("--cursor", "install for Cursor")
20
- .option("--global", "install to global location (e.g. ~/.claude/skills/)")
21
- .option("--project", "install to project location (default)")
22
- .option("-f, --force", "overwrite if file already exists")
23
- .action(
24
- async (options: {
25
- claude?: boolean;
26
- cursor?: boolean;
27
- global?: boolean;
28
- project?: boolean;
29
- force?: boolean;
30
- }) => {
31
- if (!options.claude && !options.cursor) {
32
- console.error("error: specify at least one agent target: --claude, --cursor");
33
- process.exit(1);
34
- }
15
+ skill
16
+ .command("install")
17
+ .description("install the mcpx skill for an AI agent")
18
+ .option("--claude", "install for Claude Code")
19
+ .option("--cursor", "install for Cursor")
20
+ .option("--global", "install to global location (e.g. ~/.claude/skills/)")
21
+ .option("--project", "install to project location (default)")
22
+ .option("-f, --force", "overwrite if file already exists")
23
+ .action(
24
+ async (options: { claude?: boolean; cursor?: boolean; global?: boolean; project?: boolean; force?: boolean }) => {
25
+ if (!options.claude && !options.cursor) {
26
+ console.error("error: specify at least one agent target: --claude, --cursor");
27
+ process.exit(1);
28
+ }
35
29
 
36
- const agents: {
37
- name: string;
38
- sourcePath: string;
39
- globalDir: string;
40
- projectDir: string;
41
- filename: string;
42
- }[] = [];
30
+ const agents: {
31
+ name: string;
32
+ sourcePath: string;
33
+ globalDir: string;
34
+ projectDir: string;
35
+ filename: string;
36
+ }[] = [];
43
37
 
44
- if (options.claude) {
45
- agents.push({
46
- name: "Claude Code",
47
- sourcePath: resolve(dirname(Bun.main), "..", ".claude", "skills", "mcpx.md"),
48
- globalDir: join(homedir(), ".claude", "skills"),
49
- projectDir: resolve(".claude", "skills"),
50
- filename: "mcpx.md",
51
- });
52
- }
38
+ if (options.claude) {
39
+ agents.push({
40
+ name: "Claude Code",
41
+ sourcePath: resolve(dirname(Bun.main), "..", ".claude", "skills", "mcpx.md"),
42
+ globalDir: join(homedir(), ".claude", "skills"),
43
+ projectDir: resolve(".claude", "skills"),
44
+ filename: "mcpx.md",
45
+ });
46
+ }
53
47
 
54
- if (options.cursor) {
55
- agents.push({
56
- name: "Cursor",
57
- sourcePath: resolve(dirname(Bun.main), "..", ".cursor", "rules", "mcpx.mdc"),
58
- globalDir: join(homedir(), ".cursor", "rules"),
59
- projectDir: resolve(".cursor", "rules"),
60
- filename: "mcpx.mdc",
61
- });
62
- }
48
+ if (options.cursor) {
49
+ agents.push({
50
+ name: "Cursor",
51
+ sourcePath: resolve(dirname(Bun.main), "..", ".cursor", "rules", "mcpx.mdc"),
52
+ globalDir: join(homedir(), ".cursor", "rules"),
53
+ projectDir: resolve(".cursor", "rules"),
54
+ filename: "mcpx.mdc",
55
+ });
56
+ }
63
57
 
64
- for (const agent of agents) {
65
- let content: string;
66
- try {
67
- content = await readFile(agent.sourcePath, "utf-8");
68
- } catch {
69
- console.error(`Could not read skill file: ${agent.sourcePath}`);
70
- process.exit(1);
71
- }
58
+ for (const agent of agents) {
59
+ let content: string;
60
+ try {
61
+ content = await readFile(agent.sourcePath, "utf-8");
62
+ } catch {
63
+ console.error(`Could not read skill file: ${agent.sourcePath}`);
64
+ process.exit(1);
65
+ }
72
66
 
73
- // Determine targets — default to project if neither flag is set
74
- const targets: SkillTarget[] = [];
67
+ // Determine targets — default to project if neither flag is set
68
+ const targets: SkillTarget[] = [];
75
69
 
76
- if (options.global) {
77
- targets.push({
78
- label: "global",
79
- dir: agent.globalDir,
80
- filename: agent.filename,
81
- });
82
- }
83
- if (options.project || !options.global) {
84
- targets.push({
85
- label: "project",
86
- dir: agent.projectDir,
87
- filename: agent.filename,
88
- });
89
- }
70
+ if (options.global) {
71
+ targets.push({
72
+ label: "global",
73
+ dir: agent.globalDir,
74
+ filename: agent.filename,
75
+ });
76
+ }
77
+ if (options.project || !options.global) {
78
+ targets.push({
79
+ label: "project",
80
+ dir: agent.projectDir,
81
+ filename: agent.filename,
82
+ });
83
+ }
90
84
 
91
- for (const target of targets) {
92
- const dest = join(target.dir, target.filename);
85
+ for (const target of targets) {
86
+ const dest = join(target.dir, target.filename);
93
87
 
94
- // Check if file already exists
95
- if (!options.force) {
96
- try {
97
- await access(dest);
98
- console.error(`${dest} already exists (use --force to overwrite)`);
99
- process.exit(1);
100
- } catch {
101
- // File doesn't exist — good
102
- }
103
- }
88
+ // Check if file already exists
89
+ if (!options.force) {
90
+ try {
91
+ await access(dest);
92
+ console.error(`${dest} already exists (use --force to overwrite)`);
93
+ process.exit(1);
94
+ } catch {
95
+ // File doesn't exist — good
96
+ }
97
+ }
104
98
 
105
- await mkdir(target.dir, { recursive: true });
106
- await writeFile(dest, content, "utf-8");
107
- console.log(`Installed mcpx skill for ${agent.name} (${target.label}): ${dest}`);
108
- }
109
- }
110
- },
111
- );
99
+ await mkdir(target.dir, { recursive: true });
100
+ await writeFile(dest, content, "utf-8");
101
+ console.log(`Installed mcpx skill for ${agent.name} (${target.label}): ${dest}`);
102
+ }
103
+ }
104
+ },
105
+ );
112
106
  }
@@ -1,66 +1,56 @@
1
1
  import type { Command } from "commander";
2
- import {
3
- formatCallResult,
4
- formatError,
5
- formatTaskStatus,
6
- formatTasksList,
7
- } from "../output/formatter.ts";
2
+ import { formatCallResult, formatTaskStatus, formatTasksList } from "../output/formatter.ts";
8
3
  import { withCommand } from "./with-command.ts";
9
4
 
10
5
  export function registerTaskCommand(program: Command) {
11
- program
12
- .command("task <action> <server> [taskId]")
13
- .description("manage tasks (actions: get, list, result, cancel)")
14
- .action(
15
- withCommand(
16
- program,
17
- { spinnerText: "Connecting..." },
18
- async (
19
- { manager, formatOptions, spinner },
20
- action: string,
21
- server: string,
22
- taskId?: string,
23
- ) => {
24
- spinner.update(`Connecting to ${server}...`);
6
+ program
7
+ .command("task <action> <server> [taskId]")
8
+ .description("manage tasks (actions: get, list, result, cancel)")
9
+ .action(
10
+ withCommand(
11
+ program,
12
+ { spinnerText: "Connecting..." },
13
+ async ({ manager, formatOptions, spinner }, action: string, server: string, taskId?: string) => {
14
+ spinner.update(`Connecting to ${server}...`);
25
15
 
26
- switch (action) {
27
- case "list": {
28
- const result = await manager.listTasks(server);
29
- spinner.stop();
30
- console.log(formatTasksList(result.tasks, result.nextCursor, formatOptions));
31
- break;
32
- }
33
- case "get": {
34
- if (!taskId) {
35
- throw new Error("Usage: mcpx task get <server> <taskId>");
36
- }
37
- const task = await manager.getTask(server, taskId);
38
- spinner.stop();
39
- console.log(formatTaskStatus(task, formatOptions));
40
- break;
41
- }
42
- case "result": {
43
- if (!taskId) {
44
- throw new Error("Usage: mcpx task result <server> <taskId>");
45
- }
46
- const result = await manager.getTaskResult(server, taskId);
47
- spinner.stop();
48
- console.log(formatCallResult(result, formatOptions));
49
- break;
50
- }
51
- case "cancel": {
52
- if (!taskId) {
53
- throw new Error("Usage: mcpx task cancel <server> <taskId>");
54
- }
55
- const cancelled = await manager.cancelTask(server, taskId);
56
- spinner.stop();
57
- console.log(formatTaskStatus(cancelled, formatOptions));
58
- break;
59
- }
60
- default:
61
- throw new Error(`Unknown task action: "${action}". Use: get, list, result, cancel`);
62
- }
63
- },
64
- ),
65
- );
16
+ switch (action) {
17
+ case "list": {
18
+ const result = await manager.listTasks(server);
19
+ spinner.stop();
20
+ console.log(formatTasksList(result.tasks, result.nextCursor, formatOptions));
21
+ break;
22
+ }
23
+ case "get": {
24
+ if (!taskId) {
25
+ throw new Error("Usage: mcpx task get <server> <taskId>");
26
+ }
27
+ const task = await manager.getTask(server, taskId);
28
+ spinner.stop();
29
+ console.log(formatTaskStatus(task, formatOptions));
30
+ break;
31
+ }
32
+ case "result": {
33
+ if (!taskId) {
34
+ throw new Error("Usage: mcpx task result <server> <taskId>");
35
+ }
36
+ const result = await manager.getTaskResult(server, taskId);
37
+ spinner.stop();
38
+ console.log(formatCallResult(result, formatOptions));
39
+ break;
40
+ }
41
+ case "cancel": {
42
+ if (!taskId) {
43
+ throw new Error("Usage: mcpx task cancel <server> <taskId>");
44
+ }
45
+ const cancelled = await manager.cancelTask(server, taskId);
46
+ spinner.stop();
47
+ console.log(formatTaskStatus(cancelled, formatOptions));
48
+ break;
49
+ }
50
+ default:
51
+ throw new Error(`Unknown task action: "${action}". Use: get, list, result, cancel`);
52
+ }
53
+ },
54
+ ),
55
+ );
66
56
  }