@evantahler/mcpx 0.15.4 → 0.15.9
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/.claude/skills/mcpx.md +115 -75
- package/package.json +1 -1
- package/src/client/http.ts +3 -22
- package/src/client/manager.ts +54 -66
- package/src/client/sse.ts +3 -14
- package/src/client/transport-options.ts +31 -0
- package/src/commands/exec.ts +2 -1
- package/src/commands/index.ts +15 -20
- package/src/commands/info.ts +47 -52
- package/src/commands/list.ts +49 -54
- package/src/commands/prompt.ts +16 -17
- package/src/commands/resource.ts +28 -32
- package/src/commands/search.ts +2 -1
- package/src/commands/servers.ts +6 -12
- package/src/commands/task.ts +48 -64
- package/src/commands/with-command.ts +59 -0
- package/src/config/env.ts +4 -2
- package/src/config/loader.ts +10 -4
- package/src/constants.ts +19 -0
- package/src/context.ts +7 -6
- package/src/output/format-output.ts +18 -0
- package/src/output/format-table.ts +63 -0
- package/src/output/formatter.ts +424 -570
- package/src/search/index.ts +2 -1
- package/src/search/keyword.ts +2 -5
- package/src/search/semantic.ts +4 -7
- package/src/search/types.ts +7 -0
- package/src/validation/schema.ts +18 -30
package/src/config/loader.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, resolve } from "path";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { interpolateEnv } from "./env.ts";
|
|
4
|
+
import { ENV } from "../constants.ts";
|
|
4
5
|
import {
|
|
5
6
|
type Config,
|
|
6
7
|
type ServersFile,
|
|
@@ -36,7 +37,7 @@ function resolveConfigDir(configFlag?: string): string {
|
|
|
36
37
|
if (configFlag) return resolve(configFlag);
|
|
37
38
|
|
|
38
39
|
// 2. MCP_CONFIG_PATH env var
|
|
39
|
-
const envPath = process.env.
|
|
40
|
+
const envPath = process.env[ENV.CONFIG_PATH];
|
|
40
41
|
if (envPath) return resolve(envPath);
|
|
41
42
|
|
|
42
43
|
// 3. ./servers.json exists in cwd → use cwd
|
|
@@ -107,9 +108,14 @@ export async function loadConfig(options: LoadConfigOptions = {}): Promise<Confi
|
|
|
107
108
|
return { configDir, servers, auth, searchIndex };
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
/** Write a JSON file to the config directory */
|
|
112
|
+
async function saveJsonFile(configDir: string, filename: string, data: unknown): Promise<void> {
|
|
113
|
+
await Bun.write(join(configDir, filename), JSON.stringify(data, null, 2) + "\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
/** Save auth.json to the config directory */
|
|
111
117
|
export async function saveAuth(configDir: string, auth: AuthFile): Promise<void> {
|
|
112
|
-
|
|
118
|
+
return saveJsonFile(configDir, "auth.json", auth);
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
/** Load search.json from the config directory */
|
|
@@ -120,12 +126,12 @@ export async function loadSearchIndex(configDir: string): Promise<SearchIndex> {
|
|
|
120
126
|
|
|
121
127
|
/** Save search.json to the config directory */
|
|
122
128
|
export async function saveSearchIndex(configDir: string, index: SearchIndex): Promise<void> {
|
|
123
|
-
|
|
129
|
+
return saveJsonFile(configDir, "search.json", index);
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
/** Save servers.json to the config directory */
|
|
127
133
|
export async function saveServers(configDir: string, servers: ServersFile): Promise<void> {
|
|
128
|
-
|
|
134
|
+
return saveJsonFile(configDir, "servers.json", servers);
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/** Load servers.json without env interpolation (preserves ${VAR} placeholders) */
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Environment variable names used by mcpx */
|
|
2
|
+
export const ENV = {
|
|
3
|
+
DEBUG: "MCP_DEBUG",
|
|
4
|
+
CONCURRENCY: "MCP_CONCURRENCY",
|
|
5
|
+
TIMEOUT: "MCP_TIMEOUT",
|
|
6
|
+
MAX_RETRIES: "MCP_MAX_RETRIES",
|
|
7
|
+
STRICT_ENV: "MCP_STRICT_ENV",
|
|
8
|
+
CONFIG_PATH: "MCP_CONFIG_PATH",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
/** Default values for configurable options */
|
|
12
|
+
export const DEFAULTS = {
|
|
13
|
+
CONCURRENCY: 5,
|
|
14
|
+
TIMEOUT_SECONDS: 1800,
|
|
15
|
+
MAX_RETRIES: 3,
|
|
16
|
+
TASK_TTL_MS: 60_000,
|
|
17
|
+
SEARCH_TOP_K: 10,
|
|
18
|
+
LOG_LEVEL: "warning",
|
|
19
|
+
} as const;
|
package/src/context.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ServerManager } from "./client/manager.ts";
|
|
|
4
4
|
import type { Config } from "./config/schemas.ts";
|
|
5
5
|
import type { FormatOptions } from "./output/formatter.ts";
|
|
6
6
|
import { logger } from "./output/logger.ts";
|
|
7
|
+
import { ENV, DEFAULTS } from "./constants.ts";
|
|
7
8
|
|
|
8
9
|
export interface AppContext {
|
|
9
10
|
config: Config;
|
|
@@ -21,14 +22,14 @@ export async function getContext(program: Command): Promise<AppContext> {
|
|
|
21
22
|
|
|
22
23
|
const verbose = !!(
|
|
23
24
|
(opts.verbose as boolean | undefined) ||
|
|
24
|
-
process.env.
|
|
25
|
-
process.env.
|
|
25
|
+
process.env[ENV.DEBUG] === "1" ||
|
|
26
|
+
process.env[ENV.DEBUG] === "true"
|
|
26
27
|
);
|
|
27
28
|
const showSecrets = !!(opts.showSecrets as boolean | undefined);
|
|
28
|
-
const concurrency = Number(process.env.
|
|
29
|
-
const timeout = Number(process.env.
|
|
30
|
-
const maxRetries = Number(process.env.
|
|
31
|
-
const logLevel = (opts.logLevel as string | undefined) ??
|
|
29
|
+
const concurrency = Number(process.env[ENV.CONCURRENCY] ?? DEFAULTS.CONCURRENCY);
|
|
30
|
+
const timeout = Number(process.env[ENV.TIMEOUT] ?? DEFAULTS.TIMEOUT_SECONDS) * 1000;
|
|
31
|
+
const maxRetries = Number(process.env[ENV.MAX_RETRIES] ?? DEFAULTS.MAX_RETRIES);
|
|
32
|
+
const logLevel = (opts.logLevel as string | undefined) ?? DEFAULTS.LOG_LEVEL;
|
|
32
33
|
|
|
33
34
|
const json = !!(opts.json as boolean | undefined);
|
|
34
35
|
// Commander's --no-interactive sets opts.interactive = false (default true)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FormatOptions } from "./formatter.ts";
|
|
2
|
+
import { isInteractive } from "./formatter.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format output with automatic JSON/interactive branching.
|
|
6
|
+
* In non-interactive mode, returns JSON.stringify of jsonData.
|
|
7
|
+
* In interactive mode, calls interactiveFn() for formatted output.
|
|
8
|
+
*/
|
|
9
|
+
export function formatOutput(
|
|
10
|
+
jsonData: unknown,
|
|
11
|
+
interactiveFn: () => string,
|
|
12
|
+
options: FormatOptions,
|
|
13
|
+
): string {
|
|
14
|
+
if (!isInteractive(options)) {
|
|
15
|
+
return JSON.stringify(jsonData, null, 2);
|
|
16
|
+
}
|
|
17
|
+
return interactiveFn();
|
|
18
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
import { dim } from "ansis";
|
|
3
|
+
import { wrapDescription } from "./formatter.ts";
|
|
4
|
+
|
|
5
|
+
export interface Column<T> {
|
|
6
|
+
value: (item: T) => string;
|
|
7
|
+
style: (text: string) => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TableOptions<T> {
|
|
11
|
+
columns: Column<T>[];
|
|
12
|
+
description?: (item: T) => string | undefined;
|
|
13
|
+
separator?: string;
|
|
14
|
+
emptyMessage?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Measure visible length of a string (excluding ANSI escape codes) */
|
|
18
|
+
function visibleLength(s: string): number {
|
|
19
|
+
return ansis.strip(s).length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Get terminal width, or undefined if not a TTY */
|
|
23
|
+
function getTerminalWidth(): number | undefined {
|
|
24
|
+
if (process.stdout.isTTY) return Math.max(process.stdout.columns - 1, 40);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format a list of items as an aligned table with optional description wrapping.
|
|
30
|
+
*/
|
|
31
|
+
export function formatTable<T>(items: T[], options: TableOptions<T>): string {
|
|
32
|
+
if (items.length === 0) {
|
|
33
|
+
return dim(options.emptyMessage ?? "No items found");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sep = options.separator ?? " ";
|
|
37
|
+
const termWidth = getTerminalWidth();
|
|
38
|
+
|
|
39
|
+
// Calculate max width for each column
|
|
40
|
+
const maxWidths = options.columns.map((col) =>
|
|
41
|
+
Math.max(...items.map((item) => col.value(item).length)),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return items
|
|
45
|
+
.map((item) => {
|
|
46
|
+
const parts = options.columns.map((col, i) => {
|
|
47
|
+
const raw = col.value(item);
|
|
48
|
+
const pad = maxWidths[i]! - raw.length;
|
|
49
|
+
return col.style(raw) + " ".repeat(Math.max(0, pad));
|
|
50
|
+
});
|
|
51
|
+
const prefix = parts.join(sep);
|
|
52
|
+
|
|
53
|
+
const desc = options.description?.(item);
|
|
54
|
+
if (desc) {
|
|
55
|
+
const pw = visibleLength(prefix) + sep.length;
|
|
56
|
+
const formatted = termWidth != null ? wrapDescription(desc, pw, termWidth) : dim(desc);
|
|
57
|
+
return `${prefix}${sep}${formatted}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return prefix;
|
|
61
|
+
})
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|