@evantahler/mcpx 0.15.3 → 0.15.8
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/README.md +2 -0
- 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/README.md
CHANGED
|
@@ -626,6 +626,8 @@ Inspired by [mcp-cli](https://github.com/philschmid/mcp-cli) by Phil Schmid, whi
|
|
|
626
626
|
|
|
627
627
|
## Why mcpx?
|
|
628
628
|
|
|
629
|
+
mcpx is the client. If you need the server side — auth, governance, and production tools at scale — check out [Arcade](https://arcade.dev).
|
|
630
|
+
|
|
629
631
|
The full story: [curl for MCP: Why Coding Agents Are Happier Using the CLI](https://arcade.dev/blog/curl-for-mcp)
|
|
630
632
|
|
|
631
633
|
## License
|
package/package.json
CHANGED
package/src/client/http.ts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
2
|
-
import type
|
|
3
|
-
import type { HttpServerConfig } from "../config/schemas.ts";
|
|
4
|
-
import pkg from "../../package.json";
|
|
5
|
-
import { createDebugFetch } from "./debug-fetch.ts";
|
|
2
|
+
import { buildTransportInit, type TransportDeps } from "./transport-options.ts";
|
|
6
3
|
|
|
7
|
-
export function createHttpTransport(
|
|
8
|
-
config
|
|
9
|
-
authProvider?: OAuthClientProvider,
|
|
10
|
-
verbose = false,
|
|
11
|
-
showSecrets = false,
|
|
12
|
-
): StreamableHTTPClientTransport {
|
|
13
|
-
const requestInit: RequestInit = {};
|
|
14
|
-
const userAgent = `${pkg.name}/${pkg.version}`;
|
|
15
|
-
requestInit.headers = {
|
|
16
|
-
"User-Agent": userAgent,
|
|
17
|
-
...config.headers,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
21
|
-
authProvider,
|
|
22
|
-
requestInit,
|
|
23
|
-
fetch: verbose ? createDebugFetch(showSecrets) : undefined,
|
|
24
|
-
});
|
|
4
|
+
export function createHttpTransport(deps: TransportDeps): StreamableHTTPClientTransport {
|
|
5
|
+
return new StreamableHTTPClientTransport(new URL(deps.config.url), buildTransportInit(deps));
|
|
25
6
|
}
|
package/src/client/manager.ts
CHANGED
|
@@ -34,18 +34,19 @@ import { McpOAuthProvider } from "./oauth.ts";
|
|
|
34
34
|
import { logger } from "../output/logger.ts";
|
|
35
35
|
import { wrapTransportWithTrace } from "./trace.ts";
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
interface WithServer {
|
|
38
38
|
server: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ToolWithServer extends WithServer {
|
|
39
42
|
tool: Tool;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
export interface ResourceWithServer {
|
|
43
|
-
server: string;
|
|
45
|
+
export interface ResourceWithServer extends WithServer {
|
|
44
46
|
resource: Resource;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
export interface PromptWithServer {
|
|
48
|
-
server: string;
|
|
49
|
+
export interface PromptWithServer extends WithServer {
|
|
49
50
|
prompt: Prompt;
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -160,12 +161,12 @@ export class ServerManager {
|
|
|
160
161
|
// ignore close errors
|
|
161
162
|
}
|
|
162
163
|
const provider = this.getOrCreateOAuthProvider(serverName);
|
|
163
|
-
const rawSseTransport = createSseTransport(
|
|
164
|
+
const rawSseTransport = createSseTransport({
|
|
164
165
|
config,
|
|
165
|
-
provider.isComplete() ? provider : undefined,
|
|
166
|
-
this.verbose,
|
|
167
|
-
this.showSecrets,
|
|
168
|
-
);
|
|
166
|
+
authProvider: provider.isComplete() ? provider : undefined,
|
|
167
|
+
verbose: this.verbose,
|
|
168
|
+
showSecrets: this.showSecrets,
|
|
169
|
+
});
|
|
169
170
|
const sseTransport = this.verbose
|
|
170
171
|
? wrapTransportWithTrace(rawSseTransport, { json: this.json, serverName })
|
|
171
172
|
: rawSseTransport;
|
|
@@ -245,11 +246,21 @@ export class ServerManager {
|
|
|
245
246
|
const authProvider = provider.isComplete() ? provider : undefined;
|
|
246
247
|
|
|
247
248
|
if (config.transport === "sse") {
|
|
248
|
-
return createSseTransport(
|
|
249
|
+
return createSseTransport({
|
|
250
|
+
config,
|
|
251
|
+
authProvider,
|
|
252
|
+
verbose: this.verbose,
|
|
253
|
+
showSecrets: this.showSecrets,
|
|
254
|
+
});
|
|
249
255
|
}
|
|
250
256
|
// Default (including explicit "streamable-http") uses Streamable HTTP.
|
|
251
257
|
// When no transport is set, getClient() will auto-fallback to SSE on failure.
|
|
252
|
-
return createHttpTransport(
|
|
258
|
+
return createHttpTransport({
|
|
259
|
+
config,
|
|
260
|
+
authProvider,
|
|
261
|
+
verbose: this.verbose,
|
|
262
|
+
showSecrets: this.showSecrets,
|
|
263
|
+
});
|
|
253
264
|
}
|
|
254
265
|
throw new Error("Invalid server config");
|
|
255
266
|
}
|
|
@@ -322,20 +333,31 @@ export class ServerManager {
|
|
|
322
333
|
throw lastError;
|
|
323
334
|
}
|
|
324
335
|
|
|
325
|
-
/**
|
|
326
|
-
async
|
|
336
|
+
/** Get client, call method with timeout, wrapped in retry logic */
|
|
337
|
+
private async callWithResilience<T>(
|
|
338
|
+
serverName: string,
|
|
339
|
+
label: string,
|
|
340
|
+
fn: (client: Client) => Promise<T>,
|
|
341
|
+
): Promise<T> {
|
|
327
342
|
return this.withRetry(
|
|
328
343
|
async () => {
|
|
329
344
|
const client = await this.getClient(serverName);
|
|
330
|
-
|
|
331
|
-
const config = this.servers.mcpServers[serverName]!;
|
|
332
|
-
return filterTools(result.tools, config.allowedTools, config.disabledTools);
|
|
345
|
+
return this.withTimeout(fn(client), label);
|
|
333
346
|
},
|
|
334
|
-
|
|
347
|
+
label,
|
|
335
348
|
serverName,
|
|
336
349
|
);
|
|
337
350
|
}
|
|
338
351
|
|
|
352
|
+
/** List tools for a single server, applying allowedTools/disabledTools filters */
|
|
353
|
+
async listTools(serverName: string): Promise<Tool[]> {
|
|
354
|
+
return this.callWithResilience(serverName, `listTools(${serverName})`, async (client) => {
|
|
355
|
+
const result = await client.listTools();
|
|
356
|
+
const config = this.servers.mcpServers[serverName]!;
|
|
357
|
+
return filterTools(result.tools, config.allowedTools, config.disabledTools);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
339
361
|
/** List tools across all configured servers */
|
|
340
362
|
async getAllTools(): Promise<{ tools: ToolWithServer[]; errors: ServerError[] }> {
|
|
341
363
|
const { items: tools, errors } = await this.gatherFromServers(async (name) => {
|
|
@@ -351,16 +373,8 @@ export class ServerManager {
|
|
|
351
373
|
toolName: string,
|
|
352
374
|
args: Record<string, unknown> = {},
|
|
353
375
|
): Promise<unknown> {
|
|
354
|
-
return this.
|
|
355
|
-
|
|
356
|
-
const client = await this.getClient(serverName);
|
|
357
|
-
return this.withTimeout(
|
|
358
|
-
client.callTool({ name: toolName, arguments: args }),
|
|
359
|
-
`callTool(${serverName}/${toolName})`,
|
|
360
|
-
);
|
|
361
|
-
},
|
|
362
|
-
`callTool(${serverName}/${toolName})`,
|
|
363
|
-
serverName,
|
|
376
|
+
return this.callWithResilience(serverName, `callTool(${serverName}/${toolName})`, (client) =>
|
|
377
|
+
client.callTool({ name: toolName, arguments: args }),
|
|
364
378
|
);
|
|
365
379
|
}
|
|
366
380
|
|
|
@@ -382,18 +396,10 @@ export class ServerManager {
|
|
|
382
396
|
|
|
383
397
|
/** List resources for a single server */
|
|
384
398
|
async listResources(serverName: string): Promise<Resource[]> {
|
|
385
|
-
return this.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
client.listResources(),
|
|
390
|
-
`listResources(${serverName})`,
|
|
391
|
-
);
|
|
392
|
-
return result.resources;
|
|
393
|
-
},
|
|
394
|
-
`listResources(${serverName})`,
|
|
395
|
-
serverName,
|
|
396
|
-
);
|
|
399
|
+
return this.callWithResilience(serverName, `listResources(${serverName})`, async (client) => {
|
|
400
|
+
const result = await client.listResources();
|
|
401
|
+
return result.resources;
|
|
402
|
+
});
|
|
397
403
|
}
|
|
398
404
|
|
|
399
405
|
/** List resources across all configured servers (skips servers without resources capability) */
|
|
@@ -409,27 +415,17 @@ export class ServerManager {
|
|
|
409
415
|
|
|
410
416
|
/** Read a specific resource by URI */
|
|
411
417
|
async readResource(serverName: string, uri: string): Promise<unknown> {
|
|
412
|
-
return this.
|
|
413
|
-
|
|
414
|
-
const client = await this.getClient(serverName);
|
|
415
|
-
return this.withTimeout(client.readResource({ uri }), `readResource(${serverName}/${uri})`);
|
|
416
|
-
},
|
|
417
|
-
`readResource(${serverName}/${uri})`,
|
|
418
|
-
serverName,
|
|
418
|
+
return this.callWithResilience(serverName, `readResource(${serverName}/${uri})`, (client) =>
|
|
419
|
+
client.readResource({ uri }),
|
|
419
420
|
);
|
|
420
421
|
}
|
|
421
422
|
|
|
422
423
|
/** List prompts for a single server */
|
|
423
424
|
async listPrompts(serverName: string): Promise<Prompt[]> {
|
|
424
|
-
return this.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return result.prompts;
|
|
429
|
-
},
|
|
430
|
-
`listPrompts(${serverName})`,
|
|
431
|
-
serverName,
|
|
432
|
-
);
|
|
425
|
+
return this.callWithResilience(serverName, `listPrompts(${serverName})`, async (client) => {
|
|
426
|
+
const result = await client.listPrompts();
|
|
427
|
+
return result.prompts;
|
|
428
|
+
});
|
|
433
429
|
}
|
|
434
430
|
|
|
435
431
|
/** List prompts across all configured servers (skips servers without prompts capability) */
|
|
@@ -449,16 +445,8 @@ export class ServerManager {
|
|
|
449
445
|
name: string,
|
|
450
446
|
args?: Record<string, string>,
|
|
451
447
|
): Promise<unknown> {
|
|
452
|
-
return this.
|
|
453
|
-
|
|
454
|
-
const client = await this.getClient(serverName);
|
|
455
|
-
return this.withTimeout(
|
|
456
|
-
client.getPrompt({ name, arguments: args }),
|
|
457
|
-
`getPrompt(${serverName}/${name})`,
|
|
458
|
-
);
|
|
459
|
-
},
|
|
460
|
-
`getPrompt(${serverName}/${name})`,
|
|
461
|
-
serverName,
|
|
448
|
+
return this.callWithResilience(serverName, `getPrompt(${serverName}/${name})`, (client) =>
|
|
449
|
+
client.getPrompt({ name, arguments: args }),
|
|
462
450
|
);
|
|
463
451
|
}
|
|
464
452
|
|
package/src/client/sse.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
2
|
-
import type
|
|
3
|
-
import type { HttpServerConfig } from "../config/schemas.ts";
|
|
4
|
-
import { createDebugFetch } from "./debug-fetch.ts";
|
|
2
|
+
import { buildTransportInit, type TransportDeps } from "./transport-options.ts";
|
|
5
3
|
|
|
6
|
-
export function createSseTransport(
|
|
7
|
-
config
|
|
8
|
-
authProvider?: OAuthClientProvider,
|
|
9
|
-
verbose = false,
|
|
10
|
-
showSecrets = false,
|
|
11
|
-
): SSEClientTransport {
|
|
12
|
-
return new SSEClientTransport(new URL(config.url), {
|
|
13
|
-
authProvider,
|
|
14
|
-
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
15
|
-
fetch: verbose ? createDebugFetch(showSecrets) : undefined,
|
|
16
|
-
});
|
|
4
|
+
export function createSseTransport(deps: TransportDeps): SSEClientTransport {
|
|
5
|
+
return new SSEClientTransport(new URL(deps.config.url), buildTransportInit(deps));
|
|
17
6
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
+
import type { HttpServerConfig } from "../config/schemas.ts";
|
|
3
|
+
import { createDebugFetch, type FetchLike } from "./debug-fetch.ts";
|
|
4
|
+
import pkg from "../../package.json";
|
|
5
|
+
|
|
6
|
+
export interface TransportDeps {
|
|
7
|
+
config: HttpServerConfig;
|
|
8
|
+
authProvider?: OAuthClientProvider;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
showSecrets?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Build shared transport init options (auth, headers, User-Agent, debug fetch) */
|
|
14
|
+
export function buildTransportInit(deps: TransportDeps): {
|
|
15
|
+
authProvider?: OAuthClientProvider;
|
|
16
|
+
requestInit: RequestInit;
|
|
17
|
+
fetch?: FetchLike;
|
|
18
|
+
} {
|
|
19
|
+
const { config, authProvider, verbose = false, showSecrets = false } = deps;
|
|
20
|
+
const userAgent = `${pkg.name}/${pkg.version}`;
|
|
21
|
+
return {
|
|
22
|
+
authProvider,
|
|
23
|
+
requestInit: {
|
|
24
|
+
headers: {
|
|
25
|
+
"User-Agent": userAgent,
|
|
26
|
+
...config.headers,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
fetch: verbose ? createDebugFetch(showSecrets) : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/commands/exec.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { logger } from "../output/logger.ts";
|
|
11
11
|
import { validateToolInput } from "../validation/schema.ts";
|
|
12
12
|
import { parseJsonArgs, readStdin } from "../lib/input.ts";
|
|
13
|
+
import { DEFAULTS } from "../constants.ts";
|
|
13
14
|
|
|
14
15
|
export function registerExecCommand(program: Command) {
|
|
15
16
|
program
|
|
@@ -17,7 +18,7 @@ export function registerExecCommand(program: Command) {
|
|
|
17
18
|
.description("execute a tool (omit tool name to list available tools)")
|
|
18
19
|
.option("-f, --file <path>", "read JSON args from a file")
|
|
19
20
|
.option("--no-wait", "return task handle immediately without waiting for completion")
|
|
20
|
-
.option("--ttl <ms>", "task TTL in milliseconds",
|
|
21
|
+
.option("--ttl <ms>", "task TTL in milliseconds", String(DEFAULTS.TASK_TTL_MS))
|
|
21
22
|
.action(
|
|
22
23
|
async (
|
|
23
24
|
server: string,
|
package/src/commands/index.ts
CHANGED
|
@@ -4,32 +4,27 @@ import { getContext } from "../context.ts";
|
|
|
4
4
|
import { buildSearchIndex } from "../search/indexer.ts";
|
|
5
5
|
import { getStaleServers } from "../search/staleness.ts";
|
|
6
6
|
import { saveSearchIndex } from "../config/loader.ts";
|
|
7
|
-
import { formatError } from "../output/formatter.ts";
|
|
8
7
|
import { logger } from "../output/logger.ts";
|
|
8
|
+
import { withCommand } from "./with-command.ts";
|
|
9
9
|
|
|
10
10
|
/** Run the search index build. Reusable from other commands (e.g. add). */
|
|
11
11
|
export async function runIndex(program: Command): Promise<void> {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
await withCommand(
|
|
13
|
+
program,
|
|
14
|
+
{ spinnerText: "Connecting to servers...", errorLabel: "Indexing failed" },
|
|
15
|
+
async ({ config, manager, spinner }) => {
|
|
16
|
+
const start = performance.now();
|
|
17
|
+
const index = await buildSearchIndex(manager, (progress) => {
|
|
18
|
+
spinner.update(`Indexing ${progress.current}/${progress.total}: ${progress.tool}`);
|
|
19
|
+
});
|
|
20
|
+
const elapsed = ((performance.now() - start) / 1000).toFixed(1);
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const index = await buildSearchIndex(manager, (progress) => {
|
|
18
|
-
spinner.update(`Indexing ${progress.current}/${progress.total}: ${progress.tool}`);
|
|
19
|
-
});
|
|
20
|
-
const elapsed = ((performance.now() - start) / 1000).toFixed(1);
|
|
21
|
-
|
|
22
|
-
await saveSearchIndex(config.configDir, index);
|
|
23
|
-
spinner.success(`Indexed ${index.tools.length} tools in ${elapsed}s`);
|
|
22
|
+
await saveSearchIndex(config.configDir, index);
|
|
23
|
+
spinner.success(`Indexed ${index.tools.length} tools in ${elapsed}s`);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.error(formatError(String(err), formatOptions));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
} finally {
|
|
31
|
-
await manager.close();
|
|
32
|
-
}
|
|
25
|
+
logger.info(`Saved to ${config.configDir}/search.json`);
|
|
26
|
+
},
|
|
27
|
+
)();
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
export function registerIndexCommand(program: Command) {
|
package/src/commands/info.ts
CHANGED
|
@@ -1,63 +1,58 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { Tool, Resource, Prompt } from "../config/schemas.ts";
|
|
3
|
-
import { getContext } from "../context.ts";
|
|
4
3
|
import { formatServerOverview, formatToolSchema, formatError } from "../output/formatter.ts";
|
|
5
|
-
import {
|
|
4
|
+
import { withCommand } from "./with-command.ts";
|
|
6
5
|
|
|
7
6
|
export function registerInfoCommand(program: Command) {
|
|
8
7
|
program
|
|
9
8
|
.command("info <server> [tool]")
|
|
10
9
|
.description("show server overview, or schema for a specific tool")
|
|
11
|
-
.action(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
.action(
|
|
11
|
+
withCommand(
|
|
12
|
+
program,
|
|
13
|
+
{ spinnerText: "Connecting..." },
|
|
14
|
+
async ({ manager, formatOptions, spinner }, server: string, tool?: string) => {
|
|
15
|
+
const target = tool ? `${server}/${tool}` : server;
|
|
16
|
+
spinner.update(`Connecting to ${target}...`);
|
|
17
|
+
|
|
18
|
+
if (tool) {
|
|
19
|
+
const toolSchema = await manager.getToolSchema(server, tool);
|
|
20
|
+
spinner.stop();
|
|
21
|
+
if (!toolSchema) {
|
|
22
|
+
console.error(
|
|
23
|
+
formatError(`Tool "${tool}" not found on server "${server}"`, formatOptions),
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
console.log(formatToolSchema(server, toolSchema, formatOptions));
|
|
28
|
+
} else {
|
|
29
|
+
const serverInfo = await manager.getServerInfo(server);
|
|
30
|
+
const caps = serverInfo.capabilities as Record<string, unknown> | undefined;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const [tools, resources, prompts] = await Promise.all(fetches);
|
|
32
|
+
const fetches: [Promise<Tool[]>, Promise<Resource[]>, Promise<Prompt[]>] = [
|
|
33
|
+
caps?.tools !== undefined ? manager.listTools(server) : Promise.resolve([]),
|
|
34
|
+
caps?.resources !== undefined ? manager.listResources(server) : Promise.resolve([]),
|
|
35
|
+
caps?.prompts !== undefined ? manager.listPrompts(server) : Promise.resolve([]),
|
|
36
|
+
];
|
|
37
|
+
const [tools, resources, prompts] = await Promise.all(fetches);
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
process.exit(1);
|
|
59
|
-
} finally {
|
|
60
|
-
await manager.close();
|
|
61
|
-
}
|
|
62
|
-
});
|
|
39
|
+
spinner.stop();
|
|
40
|
+
console.log(
|
|
41
|
+
formatServerOverview(
|
|
42
|
+
{
|
|
43
|
+
serverName: server,
|
|
44
|
+
version: serverInfo.version,
|
|
45
|
+
capabilities: caps,
|
|
46
|
+
instructions: serverInfo.instructions,
|
|
47
|
+
tools,
|
|
48
|
+
resourceCount: resources.length,
|
|
49
|
+
promptCount: prompts.length,
|
|
50
|
+
},
|
|
51
|
+
formatOptions,
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
),
|
|
57
|
+
);
|
|
63
58
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,64 +1,59 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import { formatUnifiedList, formatError } from "../output/formatter.ts";
|
|
2
|
+
import { formatUnifiedList } from "../output/formatter.ts";
|
|
4
3
|
import type { UnifiedItem } from "../output/formatter.ts";
|
|
5
|
-
import {
|
|
4
|
+
import { withCommand } from "./with-command.ts";
|
|
6
5
|
|
|
7
6
|
export function registerListCommand(program: Command) {
|
|
8
|
-
program.action(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
program.action(
|
|
8
|
+
withCommand(
|
|
9
|
+
program,
|
|
10
|
+
{ spinnerText: "Connecting to servers...", errorLabel: "Failed to list servers" },
|
|
11
|
+
async ({ manager, formatOptions, spinner }) => {
|
|
12
|
+
const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
|
|
13
|
+
manager.getAllTools(),
|
|
14
|
+
manager.getAllResources(),
|
|
15
|
+
manager.getAllPrompts(),
|
|
16
|
+
]);
|
|
17
|
+
spinner.stop();
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
const items: UnifiedItem[] = [
|
|
20
|
+
...toolsResult.tools.map((t) => ({
|
|
21
|
+
server: t.server,
|
|
22
|
+
type: "tool" as const,
|
|
23
|
+
name: t.tool.name,
|
|
24
|
+
description: t.tool.description,
|
|
25
|
+
})),
|
|
26
|
+
...resourcesResult.resources.map((r) => ({
|
|
27
|
+
server: r.server,
|
|
28
|
+
type: "resource" as const,
|
|
29
|
+
name: r.resource.uri,
|
|
30
|
+
description: r.resource.description,
|
|
31
|
+
})),
|
|
32
|
+
...promptsResult.prompts.map((p) => ({
|
|
33
|
+
server: p.server,
|
|
34
|
+
type: "prompt" as const,
|
|
35
|
+
name: p.prompt.name,
|
|
36
|
+
description: p.prompt.description,
|
|
37
|
+
})),
|
|
38
|
+
];
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const typeOrder = { tool: 0, resource: 1, prompt: 2 };
|
|
41
|
+
items.sort((a, b) => {
|
|
42
|
+
if (a.server !== b.server) return a.server.localeCompare(b.server);
|
|
43
|
+
if (a.type !== b.type) return typeOrder[a.type] - typeOrder[b.type];
|
|
44
|
+
return a.name.localeCompare(b.name);
|
|
45
|
+
});
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const errors = [...toolsResult.errors, ...resourcesResult.errors, ...promptsResult.errors];
|
|
48
|
+
if (errors.length > 0) {
|
|
49
|
+
for (const err of errors) {
|
|
50
|
+
console.error(`"${err.server}": ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
if (items.length > 0) console.log("");
|
|
51
53
|
}
|
|
52
|
-
if (items.length > 0) console.log("");
|
|
53
|
-
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
process.exit(1);
|
|
60
|
-
} finally {
|
|
61
|
-
await manager.close();
|
|
62
|
-
}
|
|
63
|
-
});
|
|
55
|
+
console.log(formatUnifiedList(items, formatOptions));
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
);
|
|
64
59
|
}
|
package/src/commands/prompt.ts
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { getContext } from "../context.ts";
|
|
3
2
|
import {
|
|
4
3
|
formatPromptList,
|
|
5
4
|
formatServerPrompts,
|
|
6
5
|
formatPromptMessages,
|
|
7
6
|
formatError,
|
|
8
7
|
} from "../output/formatter.ts";
|
|
9
|
-
import { logger } from "../output/logger.ts";
|
|
10
8
|
import { parseJsonArgs, readStdin } from "../lib/input.ts";
|
|
9
|
+
import { withCommand } from "./with-command.ts";
|
|
11
10
|
|
|
12
11
|
export function registerPromptCommand(program: Command) {
|
|
13
12
|
program
|
|
14
13
|
.command("prompt [server] [name] [args]")
|
|
15
14
|
.description("list prompts for a server, or get a specific prompt")
|
|
16
15
|
.action(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
formatOptions,
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
withCommand(
|
|
17
|
+
program,
|
|
18
|
+
{ spinnerText: "Connecting to servers..." },
|
|
19
|
+
async (
|
|
20
|
+
{ manager, formatOptions, spinner },
|
|
21
|
+
server?: string,
|
|
22
|
+
name?: string,
|
|
23
|
+
argsStr?: string,
|
|
24
|
+
) => {
|
|
25
|
+
if (server) {
|
|
26
|
+
spinner.update(`Connecting to ${server}...`);
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
if (server && name) {
|
|
25
30
|
let args: Record<string, string> | undefined;
|
|
26
31
|
|
|
@@ -48,13 +53,7 @@ export function registerPromptCommand(program: Command) {
|
|
|
48
53
|
console.error(formatError(`${err.server}: ${err.message}`, formatOptions));
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.error(formatError(String(err), formatOptions));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
} finally {
|
|
56
|
-
await manager.close();
|
|
57
|
-
}
|
|
58
|
-
},
|
|
56
|
+
},
|
|
57
|
+
),
|
|
59
58
|
);
|
|
60
59
|
}
|