@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.
- package/package.json +63 -63
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +15 -15
- package/src/client/debug-fetch.ts +53 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +99 -102
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +790 -814
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +52 -58
package/package.json
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
}
|
|
65
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
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 {
|
|
12
|
-
import {
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
107
|
-
|
|
98
|
+
const notice = await updateNotice;
|
|
99
|
+
if (notice) process.stderr.write(notice);
|
|
108
100
|
});
|
package/src/client/browser.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const cmd =
|
|
10
|
+
process.platform === "darwin"
|
|
11
|
+
? `open "${url}"`
|
|
12
|
+
: process.platform === "win32"
|
|
13
|
+
? `start "${url}"`
|
|
14
|
+
: `xdg-open "${url}"`;
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
8
|
-
|
|
7
|
+
const isTTY = process.stderr.isTTY ?? false;
|
|
8
|
+
const fmt = (s: string) => (isTTY ? dim(s) : s);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
return async (url, init) => {
|
|
11
|
+
const start = performance.now();
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
22
|
+
const response = await fetch(url, init);
|
|
23
|
+
const elapsed = Math.round(performance.now() - start);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
31
|
+
return response;
|
|
32
|
+
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function log(line: string) {
|
|
36
|
-
|
|
36
|
+
logger.writeRaw(`${line}\n`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function logHeaders(
|
|
40
|
-
|
|
41
|
-
headers: RequestInit["headers"],
|
|
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
|
-
|
|
48
|
-
|
|
42
|
+
const format = (key: string, value: string) =>
|
|
43
|
+
fmt(`${prefix} ${key}: ${showSecrets ? value : maskSensitive(key, value)}`);
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
}
|