@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
package/src/sdk.ts CHANGED
@@ -1,310 +1,302 @@
1
1
  import type {
2
- CallToolResult,
3
- GetTaskResult,
4
- ListTasksResult,
5
- CancelTaskResult,
2
+ CallToolResult,
3
+ CancelTaskResult,
4
+ GetTaskResult,
5
+ ListTasksResult,
6
6
  } from "@modelcontextprotocol/sdk/types.js";
7
- import { ServerManager } from "./client/manager.ts";
8
7
  import type {
9
- ServerManagerOptions,
10
- ToolWithServer,
11
- ResourceWithServer,
12
- PromptWithServer,
13
- ServerInfo,
14
- ServerError,
8
+ PromptWithServer,
9
+ ResourceWithServer,
10
+ ServerError,
11
+ ServerInfo,
12
+ ServerManagerOptions,
13
+ ToolWithServer,
15
14
  } from "./client/manager.ts";
15
+ import { ServerManager } from "./client/manager.ts";
16
16
  import { loadConfig } from "./config/loader.ts";
17
17
  import type {
18
- Tool,
19
- Resource,
20
- Prompt,
21
- ServerConfig,
22
- StdioServerConfig,
23
- HttpServerConfig,
24
- ServersFile,
25
- AuthFile,
26
- AuthEntry,
27
- SearchIndex,
28
- Config,
18
+ AuthEntry,
19
+ AuthFile,
20
+ Config,
21
+ HttpServerConfig,
22
+ Prompt,
23
+ Resource,
24
+ SearchIndex,
25
+ ServerConfig,
26
+ ServersFile,
27
+ StdioServerConfig,
28
+ Tool,
29
29
  } from "./config/schemas.ts";
30
+ import type { SearchOptions, SearchResult } from "./search/index.ts";
30
31
  import { search } from "./search/index.ts";
31
- import type { SearchResult, SearchOptions } from "./search/index.ts";
32
+ import type { ValidationError, ValidationResult } from "./validation/schema.ts";
32
33
  import { validateToolInput } from "./validation/schema.ts";
33
- import type { ValidationResult, ValidationError } from "./validation/schema.ts";
34
34
 
35
35
  // Re-export types for SDK consumers
36
36
  export type {
37
- // MCP SDK types
38
- CallToolResult,
39
- GetTaskResult,
40
- ListTasksResult,
41
- CancelTaskResult,
42
- // Config types
43
- Tool,
44
- Resource,
45
- Prompt,
46
- ServerConfig,
47
- StdioServerConfig,
48
- HttpServerConfig,
49
- ServersFile,
50
- AuthFile,
51
- AuthEntry,
52
- SearchIndex,
53
- Config,
54
- // Manager types
55
- ToolWithServer,
56
- ResourceWithServer,
57
- PromptWithServer,
58
- ServerInfo,
59
- ServerError,
60
- // Search types
61
- SearchResult,
62
- SearchOptions,
63
- // Validation types
64
- ValidationResult,
65
- ValidationError,
37
+ AuthEntry,
38
+ AuthFile,
39
+ // MCP SDK types
40
+ CallToolResult,
41
+ CancelTaskResult,
42
+ Config,
43
+ GetTaskResult,
44
+ HttpServerConfig,
45
+ ListTasksResult,
46
+ Prompt,
47
+ PromptWithServer,
48
+ Resource,
49
+ ResourceWithServer,
50
+ SearchIndex,
51
+ SearchOptions,
52
+ // Search types
53
+ SearchResult,
54
+ ServerConfig,
55
+ ServerError,
56
+ ServerInfo,
57
+ ServersFile,
58
+ StdioServerConfig,
59
+ // Config types
60
+ Tool,
61
+ // Manager types
62
+ ToolWithServer,
63
+ ValidationError,
64
+ // Validation types
65
+ ValidationResult,
66
66
  };
67
67
 
68
68
  export interface McpxClientOptions {
69
- /** Path to config directory. Defaults to ~/.mcpx or MCP_CONFIG_PATH env var. */
70
- configDir?: string;
71
- /** Inline server config — bypasses file loading when provided. */
72
- servers?: ServersFile;
73
- /** Inline auth config — bypasses file loading when provided. */
74
- auth?: AuthFile;
75
- /** Inline search index — bypasses file loading when provided. */
76
- searchIndex?: SearchIndex;
77
- /** Max concurrent server connections. Default: 5 */
78
- concurrency?: number;
79
- /** Request timeout in ms. Default: 1_800_000 (30 min) */
80
- timeout?: number;
81
- /** Max retries per operation. Default: 3 */
82
- maxRetries?: number;
83
- /** Enable verbose/trace logging. Default: false */
84
- verbose?: boolean;
69
+ /** Path to config directory. Defaults to ~/.mcpx or MCP_CONFIG_PATH env var. */
70
+ configDir?: string;
71
+ /** Inline server config — bypasses file loading when provided. */
72
+ servers?: ServersFile;
73
+ /** Inline auth config — bypasses file loading when provided. */
74
+ auth?: AuthFile;
75
+ /** Inline search index — bypasses file loading when provided. */
76
+ searchIndex?: SearchIndex;
77
+ /** Max concurrent server connections. Default: 5 */
78
+ concurrency?: number;
79
+ /** Request timeout in ms. Default: 1_800_000 (30 min) */
80
+ timeout?: number;
81
+ /** Max retries per operation. Default: 3 */
82
+ maxRetries?: number;
83
+ /** Enable verbose/trace logging. Default: false */
84
+ verbose?: boolean;
85
85
  }
86
86
 
87
87
  export class McpxClient {
88
- private options: McpxClientOptions;
89
- private manager: ServerManager | undefined;
90
- private searchIndex: SearchIndex | undefined;
91
- private connectPromise: Promise<void> | undefined;
92
-
93
- constructor(options: McpxClientOptions = {}) {
94
- this.options = options;
95
- }
96
-
97
- /** Ensure config is loaded and ServerManager is ready. Idempotent. */
98
- private async ensureConnected(): Promise<ServerManager> {
99
- if (this.manager) return this.manager;
100
-
101
- if (!this.connectPromise) {
102
- this.connectPromise = this.init();
103
- }
104
- await this.connectPromise;
105
- return this.manager!;
106
- }
107
-
108
- private async init(): Promise<void> {
109
- let servers: ServersFile;
110
- let auth: AuthFile;
111
- let configDir: string;
112
- let searchIndex: SearchIndex;
113
-
114
- if (this.options.servers) {
115
- // Inline config — no file loading
116
- servers = this.options.servers;
117
- auth = this.options.auth ?? {};
118
- configDir = this.options.configDir ?? "/tmp";
119
- searchIndex = this.options.searchIndex ?? {
120
- version: 1,
121
- indexed_at: "",
122
- embedding_model: "",
123
- tools: [],
124
- };
125
- } else {
126
- // Load from disk
127
- const config = await loadConfig({ configFlag: this.options.configDir });
128
- servers = config.servers;
129
- auth = config.auth;
130
- configDir = config.configDir;
131
- searchIndex = config.searchIndex;
132
- }
133
-
134
- this.searchIndex = searchIndex;
135
-
136
- const managerOpts: ServerManagerOptions = {
137
- servers,
138
- configDir,
139
- auth,
140
- concurrency: this.options.concurrency,
141
- verbose: this.options.verbose,
142
- timeout: this.options.timeout,
143
- maxRetries: this.options.maxRetries,
144
- logLevel: "emergency", // suppress server log messages from writing to stderr
145
- noInteractive: true, // agents can't fill elicitation forms
146
- };
147
-
148
- this.manager = new ServerManager(managerOpts);
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Core workflow: search → info → exec
153
- // ---------------------------------------------------------------------------
154
-
155
- /** Search for tools by keyword and/or semantic similarity. Requires a pre-built index (run `mcpx index` via CLI). */
156
- async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
157
- await this.ensureConnected();
158
- if (!this.searchIndex || this.searchIndex.tools.length === 0) {
159
- throw new Error("No search index found. Build one with: mcpx index");
160
- }
161
- return search(query, this.searchIndex, options);
162
- }
163
-
164
- /** Get a tool's schema (name, description, inputSchema). */
165
- async info(server: string, tool: string): Promise<Tool | undefined> {
166
- const manager = await this.ensureConnected();
167
- return manager.getToolSchema(server, tool);
168
- }
169
-
170
- /** Execute a tool and return the result. */
171
- async exec(
172
- server: string,
173
- tool: string,
174
- args?: Record<string, unknown>,
175
- ): Promise<CallToolResult> {
176
- const manager = await this.ensureConnected();
177
- return manager.callTool(server, tool, args ?? {}) as Promise<CallToolResult>;
178
- }
179
-
180
- // ---------------------------------------------------------------------------
181
- // Tools
182
- // ---------------------------------------------------------------------------
183
-
184
- /** List tools, optionally filtered to a single server. */
185
- async listTools(server?: string): Promise<ToolWithServer[]> {
186
- const manager = await this.ensureConnected();
187
- if (server) {
188
- const tools = await manager.listTools(server);
189
- return tools.map((tool) => ({ server, tool }));
190
- }
191
- const { tools } = await manager.getAllTools();
192
- return tools;
193
- }
194
-
195
- // ---------------------------------------------------------------------------
196
- // Validation
197
- // ---------------------------------------------------------------------------
198
-
199
- /** Validate arguments against a tool's inputSchema. */
200
- async validateToolInput(
201
- server: string,
202
- toolName: string,
203
- args: Record<string, unknown>,
204
- ): Promise<ValidationResult> {
205
- const tool = await this.info(server, toolName);
206
- if (!tool) {
207
- return { valid: false, errors: [{ path: "(root)", message: `Tool not found: ${toolName}` }] };
208
- }
209
- return validateToolInput(server, tool, args);
210
- }
211
-
212
- // ---------------------------------------------------------------------------
213
- // Resources
214
- // ---------------------------------------------------------------------------
215
-
216
- /** List resources, optionally filtered to a single server. */
217
- async listResources(server?: string): Promise<ResourceWithServer[]> {
218
- const manager = await this.ensureConnected();
219
- if (server) {
220
- const resources = await manager.listResources(server);
221
- return resources.map((resource) => ({ server, resource }));
222
- }
223
- const { resources } = await manager.getAllResources();
224
- return resources;
225
- }
226
-
227
- /** Read a specific resource by URI. */
228
- async readResource(server: string, uri: string): Promise<unknown> {
229
- const manager = await this.ensureConnected();
230
- return manager.readResource(server, uri);
231
- }
232
-
233
- // ---------------------------------------------------------------------------
234
- // Prompts
235
- // ---------------------------------------------------------------------------
236
-
237
- /** List prompts, optionally filtered to a single server. */
238
- async listPrompts(server?: string): Promise<PromptWithServer[]> {
239
- const manager = await this.ensureConnected();
240
- if (server) {
241
- const prompts = await manager.listPrompts(server);
242
- return prompts.map((prompt) => ({ server, prompt }));
243
- }
244
- const { prompts } = await manager.getAllPrompts();
245
- return prompts;
246
- }
247
-
248
- /** Get a specific prompt by name, optionally with arguments. */
249
- async getPrompt(server: string, name: string, args?: Record<string, string>): Promise<unknown> {
250
- const manager = await this.ensureConnected();
251
- return manager.getPrompt(server, name, args);
252
- }
253
-
254
- // ---------------------------------------------------------------------------
255
- // Tasks
256
- // ---------------------------------------------------------------------------
257
-
258
- /** List tasks on a server. */
259
- async listTasks(server: string, cursor?: string): Promise<ListTasksResult> {
260
- const manager = await this.ensureConnected();
261
- return manager.listTasks(server, cursor);
262
- }
263
-
264
- /** Get the status of a task. */
265
- async getTask(server: string, taskId: string): Promise<GetTaskResult> {
266
- const manager = await this.ensureConnected();
267
- return manager.getTask(server, taskId);
268
- }
269
-
270
- /** Retrieve the result of a completed task. */
271
- async getTaskResult(server: string, taskId: string): Promise<CallToolResult> {
272
- const manager = await this.ensureConnected();
273
- return manager.getTaskResult(server, taskId);
274
- }
275
-
276
- /** Cancel a running task. */
277
- async cancelTask(server: string, taskId: string): Promise<CancelTaskResult> {
278
- const manager = await this.ensureConnected();
279
- return manager.cancelTask(server, taskId);
280
- }
281
-
282
- // ---------------------------------------------------------------------------
283
- // Server info
284
- // ---------------------------------------------------------------------------
285
-
286
- /** Get server info (version, capabilities, instructions). */
287
- async getServerInfo(server: string): Promise<ServerInfo> {
288
- const manager = await this.ensureConnected();
289
- return manager.getServerInfo(server);
290
- }
291
-
292
- /** Get all configured server names. */
293
- async getServerNames(): Promise<string[]> {
294
- const manager = await this.ensureConnected();
295
- return manager.getServerNames();
296
- }
297
-
298
- // ---------------------------------------------------------------------------
299
- // Lifecycle
300
- // ---------------------------------------------------------------------------
301
-
302
- /** Disconnect all servers and clean up. */
303
- async close(): Promise<void> {
304
- if (this.manager) {
305
- await this.manager.close();
306
- this.manager = undefined;
307
- this.connectPromise = undefined;
308
- }
309
- }
88
+ private options: McpxClientOptions;
89
+ private manager: ServerManager | undefined;
90
+ private searchIndex: SearchIndex | undefined;
91
+ private connectPromise: Promise<void> | undefined;
92
+
93
+ constructor(options: McpxClientOptions = {}) {
94
+ this.options = options;
95
+ }
96
+
97
+ /** Ensure config is loaded and ServerManager is ready. Idempotent. */
98
+ private async ensureConnected(): Promise<ServerManager> {
99
+ if (this.manager) return this.manager;
100
+
101
+ if (!this.connectPromise) {
102
+ this.connectPromise = this.init();
103
+ }
104
+ await this.connectPromise;
105
+ return this.manager!;
106
+ }
107
+
108
+ private async init(): Promise<void> {
109
+ let servers: ServersFile;
110
+ let auth: AuthFile;
111
+ let configDir: string;
112
+ let searchIndex: SearchIndex;
113
+
114
+ if (this.options.servers) {
115
+ // Inline config — no file loading
116
+ servers = this.options.servers;
117
+ auth = this.options.auth ?? {};
118
+ configDir = this.options.configDir ?? "/tmp";
119
+ searchIndex = this.options.searchIndex ?? {
120
+ version: 1,
121
+ indexed_at: "",
122
+ embedding_model: "",
123
+ tools: [],
124
+ };
125
+ } else {
126
+ // Load from disk
127
+ const config = await loadConfig({ configFlag: this.options.configDir });
128
+ servers = config.servers;
129
+ auth = config.auth;
130
+ configDir = config.configDir;
131
+ searchIndex = config.searchIndex;
132
+ }
133
+
134
+ this.searchIndex = searchIndex;
135
+
136
+ const managerOpts: ServerManagerOptions = {
137
+ servers,
138
+ configDir,
139
+ auth,
140
+ concurrency: this.options.concurrency,
141
+ verbose: this.options.verbose,
142
+ timeout: this.options.timeout,
143
+ maxRetries: this.options.maxRetries,
144
+ logLevel: "emergency", // suppress server log messages from writing to stderr
145
+ noInteractive: true, // agents can't fill elicitation forms
146
+ };
147
+
148
+ this.manager = new ServerManager(managerOpts);
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Core workflow: search → info → exec
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /** Search for tools by keyword and/or semantic similarity. Requires a pre-built index (run `mcpx index` via CLI). */
156
+ async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
157
+ await this.ensureConnected();
158
+ if (!this.searchIndex || this.searchIndex.tools.length === 0) {
159
+ throw new Error("No search index found. Build one with: mcpx index");
160
+ }
161
+ return search(query, this.searchIndex, options);
162
+ }
163
+
164
+ /** Get a tool's schema (name, description, inputSchema). */
165
+ async info(server: string, tool: string): Promise<Tool | undefined> {
166
+ const manager = await this.ensureConnected();
167
+ return manager.getToolSchema(server, tool);
168
+ }
169
+
170
+ /** Execute a tool and return the result. */
171
+ async exec(server: string, tool: string, args?: Record<string, unknown>): Promise<CallToolResult> {
172
+ const manager = await this.ensureConnected();
173
+ return manager.callTool(server, tool, args ?? {}) as Promise<CallToolResult>;
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Tools
178
+ // ---------------------------------------------------------------------------
179
+
180
+ /** List tools, optionally filtered to a single server. */
181
+ async listTools(server?: string): Promise<ToolWithServer[]> {
182
+ const manager = await this.ensureConnected();
183
+ if (server) {
184
+ const tools = await manager.listTools(server);
185
+ return tools.map((tool) => ({ server, tool }));
186
+ }
187
+ const { tools } = await manager.getAllTools();
188
+ return tools;
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Validation
193
+ // ---------------------------------------------------------------------------
194
+
195
+ /** Validate arguments against a tool's inputSchema. */
196
+ async validateToolInput(server: string, toolName: string, args: Record<string, unknown>): Promise<ValidationResult> {
197
+ const tool = await this.info(server, toolName);
198
+ if (!tool) {
199
+ return { valid: false, errors: [{ path: "(root)", message: `Tool not found: ${toolName}` }] };
200
+ }
201
+ return validateToolInput(server, tool, args);
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Resources
206
+ // ---------------------------------------------------------------------------
207
+
208
+ /** List resources, optionally filtered to a single server. */
209
+ async listResources(server?: string): Promise<ResourceWithServer[]> {
210
+ const manager = await this.ensureConnected();
211
+ if (server) {
212
+ const resources = await manager.listResources(server);
213
+ return resources.map((resource) => ({ server, resource }));
214
+ }
215
+ const { resources } = await manager.getAllResources();
216
+ return resources;
217
+ }
218
+
219
+ /** Read a specific resource by URI. */
220
+ async readResource(server: string, uri: string): Promise<unknown> {
221
+ const manager = await this.ensureConnected();
222
+ return manager.readResource(server, uri);
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Prompts
227
+ // ---------------------------------------------------------------------------
228
+
229
+ /** List prompts, optionally filtered to a single server. */
230
+ async listPrompts(server?: string): Promise<PromptWithServer[]> {
231
+ const manager = await this.ensureConnected();
232
+ if (server) {
233
+ const prompts = await manager.listPrompts(server);
234
+ return prompts.map((prompt) => ({ server, prompt }));
235
+ }
236
+ const { prompts } = await manager.getAllPrompts();
237
+ return prompts;
238
+ }
239
+
240
+ /** Get a specific prompt by name, optionally with arguments. */
241
+ async getPrompt(server: string, name: string, args?: Record<string, string>): Promise<unknown> {
242
+ const manager = await this.ensureConnected();
243
+ return manager.getPrompt(server, name, args);
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // Tasks
248
+ // ---------------------------------------------------------------------------
249
+
250
+ /** List tasks on a server. */
251
+ async listTasks(server: string, cursor?: string): Promise<ListTasksResult> {
252
+ const manager = await this.ensureConnected();
253
+ return manager.listTasks(server, cursor);
254
+ }
255
+
256
+ /** Get the status of a task. */
257
+ async getTask(server: string, taskId: string): Promise<GetTaskResult> {
258
+ const manager = await this.ensureConnected();
259
+ return manager.getTask(server, taskId);
260
+ }
261
+
262
+ /** Retrieve the result of a completed task. */
263
+ async getTaskResult(server: string, taskId: string): Promise<CallToolResult> {
264
+ const manager = await this.ensureConnected();
265
+ return manager.getTaskResult(server, taskId);
266
+ }
267
+
268
+ /** Cancel a running task. */
269
+ async cancelTask(server: string, taskId: string): Promise<CancelTaskResult> {
270
+ const manager = await this.ensureConnected();
271
+ return manager.cancelTask(server, taskId);
272
+ }
273
+
274
+ // ---------------------------------------------------------------------------
275
+ // Server info
276
+ // ---------------------------------------------------------------------------
277
+
278
+ /** Get server info (version, capabilities, instructions). */
279
+ async getServerInfo(server: string): Promise<ServerInfo> {
280
+ const manager = await this.ensureConnected();
281
+ return manager.getServerInfo(server);
282
+ }
283
+
284
+ /** Get all configured server names. */
285
+ async getServerNames(): Promise<string[]> {
286
+ const manager = await this.ensureConnected();
287
+ return manager.getServerNames();
288
+ }
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // Lifecycle
292
+ // ---------------------------------------------------------------------------
293
+
294
+ /** Disconnect all servers and clean up. */
295
+ async close(): Promise<void> {
296
+ if (this.manager) {
297
+ await this.manager.close();
298
+ this.manager = undefined;
299
+ this.connectPromise = undefined;
300
+ }
301
+ }
310
302
  }