@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evantahler/mcpx",
3
- "version": "0.15.3",
3
+ "version": "0.15.8",
4
4
  "description": "A command-line interface for MCP servers. curl for MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,25 +1,6 @@
1
1
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
2
- import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
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: HttpServerConfig,
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
  }
@@ -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
- export interface ToolWithServer {
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(config, authProvider, this.verbose, this.showSecrets);
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(config, authProvider, this.verbose, this.showSecrets);
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
- /** List tools for a single server, applying allowedTools/disabledTools filters */
326
- async listTools(serverName: string): Promise<Tool[]> {
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
- const result = await this.withTimeout(client.listTools(), `listTools(${serverName})`);
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
- `listTools(${serverName})`,
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.withRetry(
355
- async () => {
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.withRetry(
386
- async () => {
387
- const client = await this.getClient(serverName);
388
- const result = await this.withTimeout(
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.withRetry(
413
- async () => {
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.withRetry(
425
- async () => {
426
- const client = await this.getClient(serverName);
427
- const result = await this.withTimeout(client.listPrompts(), `listPrompts(${serverName})`);
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.withRetry(
453
- async () => {
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 { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
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: HttpServerConfig,
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
+ }
@@ -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", "60000")
21
+ .option("--ttl <ms>", "task TTL in milliseconds", String(DEFAULTS.TASK_TTL_MS))
21
22
  .action(
22
23
  async (
23
24
  server: string,
@@ -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
- const { config, manager, formatOptions } = await getContext(program);
13
- const spinner = logger.startSpinner("Connecting to servers...", formatOptions);
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
- try {
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);
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
- logger.info(`Saved to ${config.configDir}/search.json`);
26
- } catch (err) {
27
- spinner.error("Indexing failed");
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) {
@@ -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 { logger } from "../output/logger.ts";
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(async (server: string, tool: string | undefined) => {
12
- const { manager, formatOptions } = await getContext(program);
13
- const target = tool ? `${server}/${tool}` : server;
14
- const spinner = logger.startSpinner(`Connecting to ${target}...`, formatOptions);
15
- try {
16
- if (tool) {
17
- const toolSchema = await manager.getToolSchema(server, tool);
18
- spinner.stop();
19
- if (!toolSchema) {
20
- console.error(
21
- formatError(`Tool "${tool}" not found on server "${server}"`, formatOptions),
22
- );
23
- process.exit(1);
24
- }
25
- console.log(formatToolSchema(server, toolSchema, formatOptions));
26
- } else {
27
- // Get server info first to check capabilities
28
- const serverInfo = await manager.getServerInfo(server);
29
- const caps = serverInfo.capabilities as Record<string, unknown> | undefined;
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
- // Only fetch what the server supports
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);
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
- 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
- } catch (err) {
56
- spinner.error(`Failed to connect to ${target}`);
57
- console.error(formatError(String(err), formatOptions));
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
  }
@@ -1,64 +1,59 @@
1
1
  import type { Command } from "commander";
2
- import { getContext } from "../context.ts";
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 { logger } from "../output/logger.ts";
4
+ import { withCommand } from "./with-command.ts";
6
5
 
7
6
  export function registerListCommand(program: Command) {
8
- program.action(async () => {
9
- const { manager, formatOptions } = await getContext(program);
10
- const spinner = logger.startSpinner("Connecting to servers...", formatOptions);
11
- try {
12
- const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
13
- manager.getAllTools(),
14
- manager.getAllResources(),
15
- manager.getAllPrompts(),
16
- ]);
17
- spinner.stop();
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
- 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
- ];
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
- 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
- });
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
- 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}`);
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
- console.log(formatUnifiedList(items, formatOptions));
56
- } catch (err) {
57
- spinner.error("Failed to list servers");
58
- console.error(formatError(String(err), formatOptions));
59
- process.exit(1);
60
- } finally {
61
- await manager.close();
62
- }
63
- });
55
+ console.log(formatUnifiedList(items, formatOptions));
56
+ },
57
+ ),
58
+ );
64
59
  }
@@ -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
- async (server: string | undefined, name: string | undefined, argsStr: string | undefined) => {
18
- const { manager, formatOptions } = await getContext(program);
19
- const spinner = logger.startSpinner(
20
- server ? `Connecting to ${server}...` : "Connecting to servers...",
21
- formatOptions,
22
- );
23
- try {
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
- } catch (err) {
52
- spinner.error("Failed");
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
  }