@evantahler/mcpx 0.18.3 → 0.18.6

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 +36 -15
  4. package/src/client/debug-fetch.ts +64 -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 +103 -103
  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 +794 -815
  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 +53 -58
@@ -1,226 +1,209 @@
1
+ import { tmpdir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { dim, green, red, yellow } from "ansis";
1
4
  import { $ } from "bun";
2
- import { green, yellow, red, cyan, dim } from "ansis";
3
5
  import type { Command } from "commander";
4
6
  import { createSpinner } from "nanospinner";
5
- import { tmpdir } from "os";
6
- import { join } from "path";
7
7
  import pkg from "../../package.json";
8
- import {
9
- checkForUpdate,
10
- detectInstallMethod,
11
- needsCheck,
12
- type InstallMethod,
13
- } from "../update/checker.ts";
14
- import { loadUpdateCache, saveUpdateCache, clearUpdateCache } from "../update/cache.ts";
15
- import type { UpdateCache } from "../update/checker.ts";
16
8
  import pkgMeta from "../../package.json";
9
+ import { clearUpdateCache, loadUpdateCache, saveUpdateCache } from "../update/cache.ts";
10
+ import type { UpdateCache } from "../update/checker.ts";
11
+ import { checkForUpdate, detectInstallMethod, type InstallMethod, needsCheck } from "../update/checker.ts";
17
12
 
18
- const GITHUB_REPO = pkgMeta.repository.url
19
- .replace(/^https:\/\/github\.com\//, "")
20
- .replace(/\.git$/, "");
13
+ const GITHUB_REPO = pkgMeta.repository.url.replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
21
14
 
22
15
  function platformArtifactName(): string {
23
- let os: string;
24
- let ext = "";
25
- switch (process.platform) {
26
- case "darwin":
27
- os = "darwin";
28
- break;
29
- case "win32":
30
- os = "windows";
31
- ext = ".exe";
32
- break;
33
- default:
34
- os = "linux";
35
- break;
36
- }
37
- const arch = process.arch === "arm64" ? "arm64" : "x64";
38
- return `mcpx-${os}-${arch}${ext}`;
16
+ let os: string;
17
+ let ext = "";
18
+ switch (process.platform) {
19
+ case "darwin":
20
+ os = "darwin";
21
+ break;
22
+ case "win32":
23
+ os = "windows";
24
+ ext = ".exe";
25
+ break;
26
+ default:
27
+ os = "linux";
28
+ break;
29
+ }
30
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
31
+ return `mcpx-${os}-${arch}${ext}`;
39
32
  }
40
33
 
41
34
  async function upgradeWithPackageManager(command: string, args: string[]): Promise<boolean> {
42
- const result = await $`${command} ${args}`.nothrow();
43
- return result.exitCode === 0;
35
+ const result = await $`${command} ${args}`.nothrow();
36
+ return result.exitCode === 0;
44
37
  }
45
38
 
46
39
  async function upgradeFromBinary(latestVersion: string): Promise<boolean> {
47
- const artifact = platformArtifactName();
48
- const tag = `v${latestVersion}`;
49
- const url = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${artifact}`;
50
-
51
- const tmpPath = join(tmpdir(), `mcpx-upgrade-${Date.now()}`);
52
- const targetPath = process.execPath;
53
-
54
- try {
55
- const res = await fetch(url);
56
- if (!res.ok) {
57
- console.error(red(`Failed to download binary: HTTP ${res.status}`));
58
- return false;
59
- }
60
-
61
- const bytes = await res.arrayBuffer();
62
- await Bun.write(tmpPath, bytes);
63
-
64
- await $`chmod +x ${tmpPath}`.quiet();
65
-
66
- // Try to move into place
67
- const mv = await $`mv ${tmpPath} ${targetPath}`.quiet().nothrow();
68
-
69
- if (mv.exitCode !== 0) {
70
- // Try with sudo
71
- console.log(dim("Requires elevated permissions..."));
72
- const sudo = await $`sudo mv ${tmpPath} ${targetPath}`.nothrow();
73
- if (sudo.exitCode !== 0) {
74
- console.error(red("Failed to install binary. Try running with sudo."));
75
- return false;
76
- }
77
- }
78
-
79
- return true;
80
- } catch (err) {
81
- console.error(red(`Failed to upgrade binary: ${err}`));
82
- // Clean up temp file
83
- await $`rm -f ${tmpPath}`.quiet().nothrow();
84
- return false;
85
- }
40
+ const artifact = platformArtifactName();
41
+ const tag = `v${latestVersion}`;
42
+ const url = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${artifact}`;
43
+
44
+ const tmpPath = join(tmpdir(), `mcpx-upgrade-${Date.now()}`);
45
+ const targetPath = process.execPath;
46
+
47
+ try {
48
+ const res = await fetch(url);
49
+ if (!res.ok) {
50
+ console.error(red(`Failed to download binary: HTTP ${res.status}`));
51
+ return false;
52
+ }
53
+
54
+ const bytes = await res.arrayBuffer();
55
+ await Bun.write(tmpPath, bytes);
56
+
57
+ await $`chmod +x ${tmpPath}`.quiet();
58
+
59
+ // Try to move into place
60
+ const mv = await $`mv ${tmpPath} ${targetPath}`.quiet().nothrow();
61
+
62
+ if (mv.exitCode !== 0) {
63
+ // Try with sudo
64
+ console.log(dim("Requires elevated permissions..."));
65
+ const sudo = await $`sudo mv ${tmpPath} ${targetPath}`.nothrow();
66
+ if (sudo.exitCode !== 0) {
67
+ console.error(red("Failed to install binary. Try running with sudo."));
68
+ return false;
69
+ }
70
+ }
71
+
72
+ return true;
73
+ } catch (err) {
74
+ console.error(red(`Failed to upgrade binary: ${err}`));
75
+ // Clean up temp file
76
+ await $`rm -f ${tmpPath}`.quiet().nothrow();
77
+ return false;
78
+ }
86
79
  }
87
80
 
88
81
  export function registerUpgradeCommand(program: Command) {
89
- program
90
- .command("upgrade")
91
- .description("Upgrade mcpx to the latest version")
92
- .action(async () => {
93
- const opts = program.opts();
94
- const json = !!(opts.json as boolean | undefined);
95
- const isTTY = process.stderr.isTTY ?? false;
96
-
97
- const spinner =
98
- !json && isTTY
99
- ? createSpinner("Checking for updates...", { stream: process.stderr }).start()
100
- : null;
101
-
102
- try {
103
- // Check for update (use cache if fresh)
104
- const cache = await loadUpdateCache();
105
- let latestVersion: string;
106
- let hasUpdate: boolean;
107
-
108
- if (!needsCheck(cache) && cache) {
109
- latestVersion = cache.latestVersion;
110
- hasUpdate = cache.hasUpdate;
111
- } else {
112
- const info = await checkForUpdate(pkg.version);
113
- latestVersion = info.latestVersion;
114
- hasUpdate = info.hasUpdate;
115
-
116
- const newCache: UpdateCache = {
117
- lastCheckAt: new Date().toISOString(),
118
- latestVersion,
119
- hasUpdate,
120
- changelog: info.changelog,
121
- };
122
- await saveUpdateCache(newCache);
123
- }
124
-
125
- if (!hasUpdate) {
126
- spinner?.stop();
127
- if (json) {
128
- console.log(
129
- JSON.stringify({
130
- upgraded: false,
131
- currentVersion: pkg.version,
132
- message: "Already up to date",
133
- }),
134
- );
135
- } else {
136
- console.log(green(`mcpx is already up to date (v${pkg.version})`));
137
- }
138
- return;
139
- }
140
-
141
- const method: InstallMethod = detectInstallMethod();
142
- spinner?.update({
143
- text: `Upgrading from v${pkg.version} to v${latestVersion} (${method})...`,
144
- });
145
-
146
- let success = false;
147
-
148
- switch (method) {
149
- case "bun":
150
- spinner?.stop();
151
- success = await upgradeWithPackageManager("bun", [
152
- "install",
153
- "-g",
154
- `@evantahler/mcpx@${latestVersion}`,
155
- ]);
156
- break;
157
-
158
- case "npm":
159
- spinner?.stop();
160
- success = await upgradeWithPackageManager("npm", [
161
- "install",
162
- "-g",
163
- `@evantahler/mcpx@${latestVersion}`,
164
- ]);
165
- break;
166
-
167
- case "binary":
168
- spinner?.stop();
169
- success = await upgradeFromBinary(latestVersion);
170
- break;
171
-
172
- case "local-dev":
173
- spinner?.stop();
174
- if (json) {
175
- console.log(
176
- JSON.stringify({
177
- upgraded: false,
178
- currentVersion: pkg.version,
179
- latestVersion,
180
- installMethod: "local-dev",
181
- message: "Running from source. Use `git pull && bun install` to update.",
182
- }),
183
- );
184
- } else {
185
- console.log(yellow("Running from source. Use `git pull && bun install` to update."));
186
- }
187
- return;
188
- }
189
-
190
- if (success) {
191
- await clearUpdateCache();
192
- if (json) {
193
- console.log(
194
- JSON.stringify({
195
- upgraded: true,
196
- previousVersion: pkg.version,
197
- newVersion: latestVersion,
198
- installMethod: method,
199
- }),
200
- );
201
- } else {
202
- console.log(green(`Successfully upgraded mcpx: v${pkg.version} → v${latestVersion}`));
203
- }
204
- } else {
205
- if (json) {
206
- console.log(
207
- JSON.stringify({
208
- upgraded: false,
209
- currentVersion: pkg.version,
210
- latestVersion,
211
- installMethod: method,
212
- message: "Upgrade failed",
213
- }),
214
- );
215
- } else {
216
- console.error(red("Upgrade failed. See errors above."));
217
- }
218
- process.exit(1);
219
- }
220
- } catch (err) {
221
- spinner?.error({ text: "Upgrade failed" });
222
- console.error(String(err));
223
- process.exit(1);
224
- }
225
- });
82
+ program
83
+ .command("upgrade")
84
+ .description("Upgrade mcpx to the latest version")
85
+ .action(async () => {
86
+ const opts = program.opts();
87
+ const json = !!(opts.json as boolean | undefined);
88
+ const isTTY = process.stderr.isTTY ?? false;
89
+
90
+ const spinner =
91
+ !json && isTTY ? createSpinner("Checking for updates...", { stream: process.stderr }).start() : null;
92
+
93
+ try {
94
+ // Check for update (use cache if fresh)
95
+ const cache = await loadUpdateCache();
96
+ let latestVersion: string;
97
+ let hasUpdate: boolean;
98
+
99
+ if (!needsCheck(cache) && cache) {
100
+ latestVersion = cache.latestVersion;
101
+ hasUpdate = cache.hasUpdate;
102
+ } else {
103
+ const info = await checkForUpdate(pkg.version);
104
+ latestVersion = info.latestVersion;
105
+ hasUpdate = info.hasUpdate;
106
+
107
+ const newCache: UpdateCache = {
108
+ lastCheckAt: new Date().toISOString(),
109
+ latestVersion,
110
+ hasUpdate,
111
+ changelog: info.changelog,
112
+ };
113
+ await saveUpdateCache(newCache);
114
+ }
115
+
116
+ if (!hasUpdate) {
117
+ spinner?.stop();
118
+ if (json) {
119
+ console.log(
120
+ JSON.stringify({
121
+ upgraded: false,
122
+ currentVersion: pkg.version,
123
+ message: "Already up to date",
124
+ }),
125
+ );
126
+ } else {
127
+ console.log(green(`mcpx is already up to date (v${pkg.version})`));
128
+ }
129
+ return;
130
+ }
131
+
132
+ const method: InstallMethod = detectInstallMethod();
133
+ spinner?.update({
134
+ text: `Upgrading from v${pkg.version} to v${latestVersion} (${method})...`,
135
+ });
136
+
137
+ let success = false;
138
+
139
+ switch (method) {
140
+ case "bun":
141
+ spinner?.stop();
142
+ success = await upgradeWithPackageManager("bun", ["install", "-g", `@evantahler/mcpx@${latestVersion}`]);
143
+ break;
144
+
145
+ case "npm":
146
+ spinner?.stop();
147
+ success = await upgradeWithPackageManager("npm", ["install", "-g", `@evantahler/mcpx@${latestVersion}`]);
148
+ break;
149
+
150
+ case "binary":
151
+ spinner?.stop();
152
+ success = await upgradeFromBinary(latestVersion);
153
+ break;
154
+
155
+ case "local-dev":
156
+ spinner?.stop();
157
+ if (json) {
158
+ console.log(
159
+ JSON.stringify({
160
+ upgraded: false,
161
+ currentVersion: pkg.version,
162
+ latestVersion,
163
+ installMethod: "local-dev",
164
+ message: "Running from source. Use `git pull && bun install` to update.",
165
+ }),
166
+ );
167
+ } else {
168
+ console.log(yellow("Running from source. Use `git pull && bun install` to update."));
169
+ }
170
+ return;
171
+ }
172
+
173
+ if (success) {
174
+ await clearUpdateCache();
175
+ if (json) {
176
+ console.log(
177
+ JSON.stringify({
178
+ upgraded: true,
179
+ previousVersion: pkg.version,
180
+ newVersion: latestVersion,
181
+ installMethod: method,
182
+ }),
183
+ );
184
+ } else {
185
+ console.log(green(`Successfully upgraded mcpx: v${pkg.version} → v${latestVersion}`));
186
+ }
187
+ } else {
188
+ if (json) {
189
+ console.log(
190
+ JSON.stringify({
191
+ upgraded: false,
192
+ currentVersion: pkg.version,
193
+ latestVersion,
194
+ installMethod: method,
195
+ message: "Upgrade failed",
196
+ }),
197
+ );
198
+ } else {
199
+ console.error(red("Upgrade failed. See errors above."));
200
+ }
201
+ process.exit(1);
202
+ }
203
+ } catch (err) {
204
+ spinner?.error({ text: "Upgrade failed" });
205
+ console.error(String(err));
206
+ process.exit(1);
207
+ }
208
+ });
226
209
  }
@@ -1,24 +1,24 @@
1
1
  import type { Command } from "commander";
2
- import { getContext, type AppContext } from "../context.ts";
2
+ import { type AppContext, getContext } from "../context.ts";
3
3
  import { formatError } from "../output/formatter.ts";
4
4
  import { logger, type Spinner } from "../output/logger.ts";
5
5
 
6
6
  export interface CommandContext extends AppContext {
7
- spinner: Spinner;
7
+ spinner: Spinner;
8
8
  }
9
9
 
10
10
  interface WithCommandOptions {
11
- /** Spinner text shown during execution. If omitted, no spinner is started. */
12
- spinnerText?: string;
13
- /** Error message for spinner.error(). Defaults to "Failed". */
14
- errorLabel?: string;
11
+ /** Spinner text shown during execution. If omitted, no spinner is started. */
12
+ spinnerText?: string;
13
+ /** Error message for spinner.error(). Defaults to "Failed". */
14
+ errorLabel?: string;
15
15
  }
16
16
 
17
17
  const noopSpinner: Spinner = {
18
- update() {},
19
- success() {},
20
- error() {},
21
- stop() {},
18
+ update() {},
19
+ success() {},
20
+ error() {},
21
+ stop() {},
22
22
  };
23
23
 
24
24
  /**
@@ -34,26 +34,24 @@ const noopSpinner: Spinner = {
34
34
  * manager.close() is always called in finally.
35
35
  */
36
36
  export function withCommand<TArgs extends unknown[]>(
37
- program: Command,
38
- options: WithCommandOptions,
39
- handler: (ctx: CommandContext, ...args: TArgs) => Promise<void>,
37
+ program: Command,
38
+ options: WithCommandOptions,
39
+ handler: (ctx: CommandContext, ...args: TArgs) => Promise<void>,
40
40
  ): (...args: TArgs) => Promise<void> {
41
- return async (...args: TArgs) => {
42
- const appCtx = await getContext(program);
43
- const { manager, formatOptions } = appCtx;
41
+ return async (...args: TArgs) => {
42
+ const appCtx = await getContext(program);
43
+ const { manager, formatOptions } = appCtx;
44
44
 
45
- const spinner = options.spinnerText
46
- ? logger.startSpinner(options.spinnerText, formatOptions)
47
- : noopSpinner;
45
+ const spinner = options.spinnerText ? logger.startSpinner(options.spinnerText, formatOptions) : noopSpinner;
48
46
 
49
- try {
50
- await handler({ ...appCtx, spinner }, ...args);
51
- } catch (err) {
52
- spinner.error(options.errorLabel ?? "Failed");
53
- console.error(formatError(String(err), formatOptions));
54
- process.exit(1);
55
- } finally {
56
- await manager.close();
57
- }
58
- };
47
+ try {
48
+ await handler({ ...appCtx, spinner }, ...args);
49
+ } catch (err) {
50
+ spinner.error(options.errorLabel ?? "Failed");
51
+ console.error(formatError(String(err), formatOptions));
52
+ process.exit(1);
53
+ } finally {
54
+ await manager.close();
55
+ }
56
+ };
59
57
  }
package/src/config/env.ts CHANGED
@@ -4,40 +4,38 @@ const ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
4
4
 
5
5
  /** Whether to throw on missing env vars (default: true) */
6
6
  function isStrictEnv(): boolean {
7
- return process.env[ENV.STRICT_ENV] !== "false";
7
+ return process.env[ENV.STRICT_ENV] !== "false";
8
8
  }
9
9
 
10
10
  /** Replace ${VAR_NAME} in a string with the corresponding env var value */
11
11
  export function interpolateEnvString(value: string): string {
12
- return value.replace(ENV_VAR_PATTERN, (_match, varName: string) => {
13
- const envValue = process.env[varName];
14
- if (envValue === undefined) {
15
- if (isStrictEnv()) {
16
- throw new Error(
17
- `Environment variable "${varName}" is not set (set ${ENV.STRICT_ENV}=false to warn instead)`,
18
- );
19
- }
20
- console.warn(`Warning: environment variable "${varName}" is not set`);
21
- return "";
22
- }
23
- return envValue;
24
- });
12
+ return value.replace(ENV_VAR_PATTERN, (_match, varName: string) => {
13
+ const envValue = process.env[varName];
14
+ if (envValue === undefined) {
15
+ if (isStrictEnv()) {
16
+ throw new Error(`Environment variable "${varName}" is not set (set ${ENV.STRICT_ENV}=false to warn instead)`);
17
+ }
18
+ console.warn(`Warning: environment variable "${varName}" is not set`);
19
+ return "";
20
+ }
21
+ return envValue;
22
+ });
25
23
  }
26
24
 
27
25
  /** Recursively interpolate env vars in all string values of an object */
28
26
  export function interpolateEnv<T>(obj: T): T {
29
- if (typeof obj === "string") {
30
- return interpolateEnvString(obj) as T;
31
- }
32
- if (Array.isArray(obj)) {
33
- return obj.map((item) => interpolateEnv(item)) as T;
34
- }
35
- if (typeof obj === "object" && obj !== null) {
36
- const result: Record<string, unknown> = {};
37
- for (const [key, value] of Object.entries(obj)) {
38
- result[key] = interpolateEnv(value);
39
- }
40
- return result as T;
41
- }
42
- return obj;
27
+ if (typeof obj === "string") {
28
+ return interpolateEnvString(obj) as T;
29
+ }
30
+ if (Array.isArray(obj)) {
31
+ return obj.map((item) => interpolateEnv(item)) as T;
32
+ }
33
+ if (typeof obj === "object" && obj !== null) {
34
+ const result: Record<string, unknown> = {};
35
+ for (const [key, value] of Object.entries(obj)) {
36
+ result[key] = interpolateEnv(value);
37
+ }
38
+ return result as T;
39
+ }
40
+ return obj;
43
41
  }