@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/.claude/skills/mcpx.md
CHANGED
|
@@ -29,12 +29,15 @@ mcpx exec <server> <tool> '<json args>'
|
|
|
29
29
|
mcpx exec <server> <tool> -f params.json
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Output is JSON when piped. Use `--json` to force JSON output in any context — prefer this when you need to parse results programmatically.
|
|
33
|
+
|
|
32
34
|
## Rules
|
|
33
35
|
|
|
34
36
|
- Always search before executing — don't assume tool names exist
|
|
35
37
|
- Always inspect the schema before executing — validate you have the right arguments
|
|
36
38
|
- Use `mcpx search -k` for exact name matching
|
|
37
39
|
- Pipe results through `jq` when you need to extract specific fields
|
|
40
|
+
- Use `--json` when parsing output programmatically (automatic when piped, but explicit is safer)
|
|
38
41
|
- Use `-v` for verbose debugging (HTTP details + JSON-RPC protocol messages) if an exec fails unexpectedly
|
|
39
42
|
- Use `-l debug` to see all server log messages, or `-l error` for errors only
|
|
40
43
|
|
|
@@ -58,16 +61,22 @@ mcpx exec github search_repositories '{"query":"mcp"}' \
|
|
|
58
61
|
# Read args from stdin
|
|
59
62
|
echo '{"path":"./README.md"}' | mcpx exec filesystem read_file
|
|
60
63
|
|
|
61
|
-
# Pipe from a file
|
|
62
|
-
cat params.json | mcpx exec server tool
|
|
63
|
-
|
|
64
64
|
# Read args from a file with --file flag
|
|
65
65
|
mcpx exec filesystem read_file -f params.json
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
##
|
|
68
|
+
## Troubleshooting
|
|
69
|
+
|
|
70
|
+
- **"Not authenticated" / 401 error** → Run `mcpx auth <server>` to start the OAuth flow
|
|
71
|
+
- **Exec timeout** → Use `-v` to see where it stalls; set `MCP_TIMEOUT=<seconds>` to increase the timeout (default: 1800)
|
|
72
|
+
- **Search returns no results** → Try `mcpx search -k "*keyword*"` for glob matching, or `mcpx index` to rebuild the search index
|
|
73
|
+
- **Missing or stale tools** → Run `mcpx index` to rebuild; any command that connects to a server also auto-updates the index
|
|
74
|
+
- **Server won't connect** → Run `mcpx ping <server>` to check connectivity; use `-v` for protocol-level details
|
|
75
|
+
- **Auth token expired** → Run `mcpx auth <server> -r` to force a token refresh
|
|
76
|
+
|
|
77
|
+
## Long-running tools (Tasks)
|
|
69
78
|
|
|
70
|
-
Some tools support async execution via MCP Tasks. mcpx auto-detects this
|
|
79
|
+
Some tools support async execution via MCP Tasks. mcpx auto-detects this.
|
|
71
80
|
|
|
72
81
|
```bash
|
|
73
82
|
# Default: waits for the task to complete, showing progress
|
|
@@ -76,90 +85,121 @@ mcpx exec my-server long_running_tool '{"input": "data"}'
|
|
|
76
85
|
# Return immediately with a task handle (for scripting/polling)
|
|
77
86
|
mcpx exec my-server long_running_tool '{"input": "data"}' --no-wait
|
|
78
87
|
|
|
79
|
-
# Check task status
|
|
88
|
+
# Check task status / retrieve result / cancel
|
|
80
89
|
mcpx task get my-server <taskId>
|
|
81
|
-
|
|
82
|
-
# Retrieve the result once complete
|
|
83
90
|
mcpx task result my-server <taskId>
|
|
84
|
-
|
|
85
|
-
# List all tasks on a server
|
|
86
|
-
mcpx task list my-server
|
|
87
|
-
|
|
88
|
-
# Cancel a running task
|
|
89
91
|
mcpx task cancel my-server <taskId>
|
|
92
|
+
mcpx task list my-server
|
|
90
93
|
```
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
## 5. Elicitation (Server-Requested Input)
|
|
95
|
-
|
|
96
|
-
Some servers request user input mid-operation (e.g., confirmations, auth flows). mcpx handles this automatically:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# Interactive — prompts appear in the terminal
|
|
100
|
-
mcpx exec my-server deploy_tool '{"target": "staging"}'
|
|
101
|
-
# Server requests input: Confirm deployment
|
|
102
|
-
# *Confirm [y/n]: y
|
|
103
|
-
|
|
104
|
-
# Non-interactive — decline all elicitation (for scripts/CI)
|
|
105
|
-
mcpx exec my-server deploy_tool '{"target": "staging"}' --no-interactive
|
|
95
|
+
## Elicitation (Server-Requested Input)
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
echo '{"action":"accept","content":{"confirm":true}}' | \
|
|
109
|
-
mcpx exec my-server deploy_tool '{"target": "staging"}' --json
|
|
110
|
-
```
|
|
97
|
+
Some servers request user input mid-operation. mcpx handles this automatically in interactive mode. Use `-N` / `--no-interactive` to decline all elicitation (for scripts/CI), or `--json` to handle elicitation programmatically via stdin/stdout.
|
|
111
98
|
|
|
112
99
|
## Authentication
|
|
113
100
|
|
|
114
|
-
Some HTTP servers require OAuth. If you see an "Not authenticated" error:
|
|
115
|
-
|
|
116
101
|
```bash
|
|
117
|
-
mcpx auth <server>
|
|
118
|
-
mcpx auth <server> -s
|
|
119
|
-
mcpx auth <server> -r
|
|
120
|
-
mcpx
|
|
102
|
+
mcpx auth <server> # authenticate via browser
|
|
103
|
+
mcpx auth <server> -s # check token status and TTL
|
|
104
|
+
mcpx auth <server> -r # force token refresh
|
|
105
|
+
mcpx auth <server> --no-index # authenticate without rebuilding search index
|
|
106
|
+
mcpx deauth <server> # remove stored auth
|
|
121
107
|
```
|
|
122
108
|
|
|
123
109
|
## Available commands
|
|
124
110
|
|
|
125
111
|
| Command | Purpose |
|
|
126
112
|
| -------------------------------------- | --------------------------------- |
|
|
127
|
-
| `mcpx`
|
|
128
|
-
| `mcpx servers`
|
|
129
|
-
| `mcpx -d`
|
|
130
|
-
| `mcpx info <server>`
|
|
131
|
-
| `mcpx info <server> <tool>`
|
|
132
|
-
| `mcpx exec <server>`
|
|
133
|
-
| `mcpx exec <server> <tool> '<json>'`
|
|
134
|
-
| `mcpx exec <server> <tool> -f file`
|
|
135
|
-
| `mcpx search "<query>"`
|
|
136
|
-
| `mcpx search -k "<pattern>"`
|
|
137
|
-
| `mcpx search -q "<query>"`
|
|
138
|
-
| `mcpx search -n <number> "<query>"`
|
|
139
|
-
| `mcpx index`
|
|
140
|
-
| `mcpx index -i`
|
|
141
|
-
| `mcpx auth <server>`
|
|
142
|
-
| `mcpx auth <server> -s`
|
|
113
|
+
| `mcpx` | List all servers and tools |
|
|
114
|
+
| `mcpx servers` | List servers (name, type, detail) |
|
|
115
|
+
| `mcpx -d` | List with descriptions |
|
|
116
|
+
| `mcpx info <server>` | Server overview (version, capabilities, tools) |
|
|
117
|
+
| `mcpx info <server> <tool>` | Show tool schema |
|
|
118
|
+
| `mcpx exec <server>` | List tools for a server |
|
|
119
|
+
| `mcpx exec <server> <tool> '<json>'` | Execute a tool |
|
|
120
|
+
| `mcpx exec <server> <tool> -f file` | Execute with args from file |
|
|
121
|
+
| `mcpx search "<query>"` | Search tools (keyword + semantic) |
|
|
122
|
+
| `mcpx search -k "<pattern>"` | Keyword/glob search only |
|
|
123
|
+
| `mcpx search -q "<query>"` | Semantic search only |
|
|
124
|
+
| `mcpx search -n <number> "<query>"` | Limit number of results (default: 10) |
|
|
125
|
+
| `mcpx index` | Build/rebuild search index |
|
|
126
|
+
| `mcpx index -i` | Show index status |
|
|
127
|
+
| `mcpx auth <server>` | Authenticate with OAuth |
|
|
128
|
+
| `mcpx auth <server> -s` | Check token status and TTL |
|
|
143
129
|
| `mcpx auth <server> -r` | Force token refresh |
|
|
144
|
-
| `mcpx
|
|
145
|
-
| `mcpx
|
|
146
|
-
| `mcpx ping
|
|
147
|
-
| `mcpx
|
|
148
|
-
| `mcpx add <name> --
|
|
149
|
-
| `mcpx add <name> --url <url
|
|
150
|
-
| `mcpx remove <name>`
|
|
151
|
-
| `mcpx skill install --claude`
|
|
152
|
-
| `mcpx skill install --cursor`
|
|
153
|
-
| `mcpx resource`
|
|
154
|
-
| `mcpx resource <server>`
|
|
155
|
-
| `mcpx resource <server> <uri>`
|
|
156
|
-
| `mcpx prompt`
|
|
157
|
-
| `mcpx prompt <server>`
|
|
158
|
-
| `mcpx prompt <server> <name> '<json>'` | Get a specific prompt
|
|
159
|
-
| `mcpx
|
|
160
|
-
| `mcpx
|
|
161
|
-
| `mcpx
|
|
162
|
-
| `mcpx task
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
130
|
+
| `mcpx auth <server> --no-index` | Authenticate without rebuilding index |
|
|
131
|
+
| `mcpx deauth <server>` | Remove stored authentication |
|
|
132
|
+
| `mcpx ping` | Check connectivity to all servers |
|
|
133
|
+
| `mcpx ping <server> [server2...]` | Check specific server(s) |
|
|
134
|
+
| `mcpx add <name> --command <cmd>` | Add a stdio MCP server |
|
|
135
|
+
| `mcpx add <name> --url <url>` | Add an HTTP MCP server |
|
|
136
|
+
| `mcpx remove <name>` | Remove an MCP server |
|
|
137
|
+
| `mcpx skill install --claude` | Install mcpx skill for Claude |
|
|
138
|
+
| `mcpx skill install --cursor` | Install mcpx rule for Cursor |
|
|
139
|
+
| `mcpx resource` | List all resources across servers |
|
|
140
|
+
| `mcpx resource <server>` | List resources for a server |
|
|
141
|
+
| `mcpx resource <server> <uri>` | Read a specific resource |
|
|
142
|
+
| `mcpx prompt` | List all prompts across servers |
|
|
143
|
+
| `mcpx prompt <server>` | List prompts for a server |
|
|
144
|
+
| `mcpx prompt <server> <name> '<json>'` | Get a specific prompt |
|
|
145
|
+
| `mcpx task list <server>` | List tasks on a server |
|
|
146
|
+
| `mcpx task get <server> <taskId>` | Get task status |
|
|
147
|
+
| `mcpx task result <server> <taskId>` | Retrieve completed task result |
|
|
148
|
+
| `mcpx task cancel <server> <taskId>` | Cancel a running task |
|
|
149
|
+
|
|
150
|
+
## Global flags
|
|
151
|
+
|
|
152
|
+
| Flag | Purpose |
|
|
153
|
+
| ------------------------- | -------------------------------------------------------- |
|
|
154
|
+
| `-j, --json` | Force JSON output (default when piped) |
|
|
155
|
+
| `-v, --verbose` | Show HTTP details and JSON-RPC protocol messages |
|
|
156
|
+
| `-d, --with-descriptions` | Include tool descriptions in list output |
|
|
157
|
+
| `-c, --config <path>` | Specify config file location |
|
|
158
|
+
| `-N, --no-interactive` | Decline server elicitation requests (for scripted usage) |
|
|
159
|
+
| `-S, --show-secrets` | Show full auth tokens in verbose output (unmasked) |
|
|
160
|
+
| `-l, --log-level <level>` | Minimum server log level to display (default: `warning`) |
|
|
161
|
+
|
|
162
|
+
## `add` options
|
|
163
|
+
|
|
164
|
+
| Flag | Purpose |
|
|
165
|
+
| -------------------------- | -------------------------------------- |
|
|
166
|
+
| `--command <cmd>` | Command to run (stdio server) |
|
|
167
|
+
| `--args <a1,a2,...>` | Comma-separated arguments |
|
|
168
|
+
| `--env <KEY=VAL,...>` | Comma-separated environment variables |
|
|
169
|
+
| `--cwd <dir>` | Working directory for the command |
|
|
170
|
+
| `--url <url>` | Server URL (HTTP server) |
|
|
171
|
+
| `--header <Key:Value>` | HTTP header (repeatable) |
|
|
172
|
+
| `--transport <type>` | Transport: `sse` or `streamable-http` |
|
|
173
|
+
| `--allowed-tools <t1,t2>` | Comma-separated allowed tool patterns |
|
|
174
|
+
| `--disabled-tools <t1,t2>` | Comma-separated disabled tool patterns |
|
|
175
|
+
| `-f, --force` | Overwrite if server already exists |
|
|
176
|
+
| `--no-auth` | Skip automatic OAuth after adding |
|
|
177
|
+
| `--no-index` | Skip rebuilding the search index |
|
|
178
|
+
|
|
179
|
+
## `remove` options
|
|
180
|
+
|
|
181
|
+
| Flag | Purpose |
|
|
182
|
+
| ------------- | ------------------------------------------------ |
|
|
183
|
+
| `--keep-auth` | Don't remove stored auth credentials |
|
|
184
|
+
| `--dry-run` | Show what would be removed without changing files |
|
|
185
|
+
|
|
186
|
+
## `skill install` options
|
|
187
|
+
|
|
188
|
+
| Flag | Purpose |
|
|
189
|
+
| ----------- | ------------------------------------------ |
|
|
190
|
+
| `--claude` | Install skill for Claude Code |
|
|
191
|
+
| `--cursor` | Install rule for Cursor |
|
|
192
|
+
| `--global` | Install to global location (`~/`) |
|
|
193
|
+
| `--project` | Install to project location (default) |
|
|
194
|
+
| `-f, --force` | Overwrite if file already exists |
|
|
195
|
+
|
|
196
|
+
## Environment variables
|
|
197
|
+
|
|
198
|
+
| Variable | Purpose | Default |
|
|
199
|
+
| ----------------- | --------------------------- | ---------- |
|
|
200
|
+
| `MCP_CONFIG_PATH` | Config directory path | `~/.mcpx/` |
|
|
201
|
+
| `MCP_TIMEOUT` | Request timeout (seconds) | `1800` |
|
|
202
|
+
| `MCP_CONCURRENCY` | Parallel server connections | `5` |
|
|
203
|
+
| `MCP_MAX_RETRIES` | Retry attempts | `3` |
|
|
204
|
+
| `MCP_STRICT_ENV` | Error on missing `${VAR}` | `true` |
|
|
205
|
+
| `MCP_DEBUG` | Enable debug output | `false` |
|
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) {
|