@evantahler/mcpx 0.18.2 → 0.18.5
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/package.json +63 -62
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +15 -15
- package/src/client/debug-fetch.ts +53 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +99 -102
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +790 -814
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +52 -58
package/src/commands/exec.ts
CHANGED
|
@@ -1,241 +1,237 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import type { ServerManager } from "../client/manager.ts";
|
|
3
|
+
import { DEFAULTS } from "../constants.ts";
|
|
2
4
|
import { getContext } from "../context.ts";
|
|
5
|
+
import { parseJsonArgs, readStdin } from "../lib/input.ts";
|
|
3
6
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
formatCallResult,
|
|
8
|
+
formatError,
|
|
9
|
+
formatServerTools,
|
|
10
|
+
formatTaskCreated,
|
|
11
|
+
formatValidationErrors,
|
|
9
12
|
} from "../output/formatter.ts";
|
|
10
13
|
import { logger } from "../output/logger.ts";
|
|
11
14
|
import { validateToolInput } from "../validation/schema.ts";
|
|
12
|
-
import { parseJsonArgs, readStdin } from "../lib/input.ts";
|
|
13
|
-
import { DEFAULTS } from "../constants.ts";
|
|
14
|
-
import type { ServerManager } from "../client/manager.ts";
|
|
15
15
|
|
|
16
16
|
type ResolvedArgs =
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
| { mode: "list-tools"; server: string }
|
|
18
|
+
| { mode: "call-tool"; server: string; tool: string; argsStr: string | undefined };
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Resolve the positional args into either list-tools or call-tool mode.
|
|
22
22
|
* Supports both `exec <server> <tool> [args]` and `exec <tool> [args]`.
|
|
23
23
|
*/
|
|
24
24
|
async function resolveExecArgs(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
manager: ServerManager,
|
|
26
|
+
first: string,
|
|
27
|
+
second: string | undefined,
|
|
28
|
+
third: string | undefined,
|
|
29
29
|
): Promise<ResolvedArgs> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return { mode: "call-tool", server: matches[0]!.server, tool: toolName, argsStr: second };
|
|
30
|
+
const serverNames = manager.getServerNames();
|
|
31
|
+
const isServer = serverNames.includes(first);
|
|
32
|
+
|
|
33
|
+
if (isServer) {
|
|
34
|
+
// Traditional form: exec <server> [tool] [args]
|
|
35
|
+
if (!second) {
|
|
36
|
+
return { mode: "list-tools", server: first };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate the tool exists on the specified server
|
|
40
|
+
const serverTools = await manager.listTools(first);
|
|
41
|
+
const toolExists = serverTools.some((t) => t.name === second);
|
|
42
|
+
|
|
43
|
+
if (!toolExists) {
|
|
44
|
+
const { tools } = await manager.getAllTools();
|
|
45
|
+
const matches = tools.filter((t) => t.tool.name === second);
|
|
46
|
+
|
|
47
|
+
if (matches.length === 1) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Tool "${second}" not found on server "${first}". Did you mean:\n mcpx exec ${matches[0]?.server} ${second}`,
|
|
50
|
+
);
|
|
51
|
+
} else if (matches.length > 1) {
|
|
52
|
+
const servers = matches.map((m) => m.server).join(", ");
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Tool "${second}" not found on server "${first}". Found on: ${servers}\nUsage: mcpx exec <server> ${second} [args]`,
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Tool "${second}" not found on server "${first}". Run "mcpx search ${second}" to find similar tools.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { mode: "call-tool", server: first, tool: second, argsStr: third };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Not a server name — treat first as a tool name
|
|
67
|
+
const toolName = first;
|
|
68
|
+
const { tools } = await manager.getAllTools();
|
|
69
|
+
const matches = tools.filter((t) => t.tool.name === toolName);
|
|
70
|
+
|
|
71
|
+
if (matches.length === 0) {
|
|
72
|
+
throw new Error(`Unknown server or tool "${first}". Run "mcpx search ${first}" to find similar tools.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (matches.length > 1) {
|
|
76
|
+
const servers = matches.map((m) => m.server).join(", ");
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Ambiguous tool "${toolName}" — found on multiple servers: ${servers}\nSpecify the server: mcpx exec <server> ${toolName} [args]`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { mode: "call-tool", server: matches[0]!.server, tool: toolName, argsStr: second };
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
export function registerExecCommand(program: Command) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
},
|
|
240
|
-
);
|
|
86
|
+
program
|
|
87
|
+
.command("exec <first> [second] [third]")
|
|
88
|
+
.description("execute a tool (server is optional if tool name is unambiguous)")
|
|
89
|
+
.option("-f, --file <path>", "read JSON args from a file")
|
|
90
|
+
.option("--no-wait", "return task handle immediately without waiting for completion")
|
|
91
|
+
.option("--ttl <ms>", "task TTL in milliseconds", String(DEFAULTS.TASK_TTL_MS))
|
|
92
|
+
.action(
|
|
93
|
+
async (
|
|
94
|
+
first: string,
|
|
95
|
+
second: string | undefined,
|
|
96
|
+
third: string | undefined,
|
|
97
|
+
options: { file?: string; wait: boolean; ttl: string },
|
|
98
|
+
) => {
|
|
99
|
+
const { manager, formatOptions } = await getContext(program);
|
|
100
|
+
|
|
101
|
+
let resolved: ResolvedArgs;
|
|
102
|
+
try {
|
|
103
|
+
resolved = await resolveExecArgs(manager, first, second, third);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error(formatError(String(err), formatOptions));
|
|
106
|
+
await manager.close();
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (resolved.mode === "list-tools") {
|
|
111
|
+
try {
|
|
112
|
+
const tools = await manager.listTools(resolved.server);
|
|
113
|
+
console.log(formatServerTools(resolved.server, tools, formatOptions));
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(formatError(String(err), formatOptions));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
} finally {
|
|
118
|
+
await manager.close();
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { server, tool, argsStr } = resolved;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Error if both --file and positional arg provided
|
|
127
|
+
if (options.file && argsStr) {
|
|
128
|
+
throw new Error("Cannot specify both --file and inline JSON args");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Parse args from: --file > positional arg > stdin > empty
|
|
132
|
+
let args: Record<string, unknown> = {};
|
|
133
|
+
|
|
134
|
+
if (options.file) {
|
|
135
|
+
const file = Bun.file(options.file);
|
|
136
|
+
if (!(await file.exists())) {
|
|
137
|
+
throw new Error(`File not found: ${options.file}`);
|
|
138
|
+
}
|
|
139
|
+
const content = await file.text();
|
|
140
|
+
args = parseJsonArgs(content);
|
|
141
|
+
} else if (argsStr) {
|
|
142
|
+
args = parseJsonArgs(argsStr);
|
|
143
|
+
} else if (!process.stdin.isTTY) {
|
|
144
|
+
// Read from stdin
|
|
145
|
+
const stdin = await readStdin();
|
|
146
|
+
if (stdin.trim()) {
|
|
147
|
+
args = parseJsonArgs(stdin);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate args against tool inputSchema before calling
|
|
152
|
+
const toolSchema = await manager.getToolSchema(server, tool);
|
|
153
|
+
if (toolSchema) {
|
|
154
|
+
const validation = validateToolInput(server, toolSchema, args);
|
|
155
|
+
if (!validation.valid) {
|
|
156
|
+
console.error(formatValidationErrors(server, tool, validation.errors, formatOptions));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check if tool supports task-augmented execution
|
|
162
|
+
const taskSupport = (toolSchema as Record<string, unknown> | undefined)?.execution as
|
|
163
|
+
| { taskSupport?: string }
|
|
164
|
+
| undefined;
|
|
165
|
+
const supportsTask = await manager.serverSupportsTask(server);
|
|
166
|
+
const useTask =
|
|
167
|
+
supportsTask && taskSupport?.taskSupport !== undefined && taskSupport.taskSupport !== "forbidden";
|
|
168
|
+
|
|
169
|
+
if (useTask) {
|
|
170
|
+
const abortController = new AbortController();
|
|
171
|
+
let currentTaskId: string | undefined;
|
|
172
|
+
|
|
173
|
+
// Graceful Ctrl+C: cancel the task before exiting
|
|
174
|
+
const sigintHandler = async () => {
|
|
175
|
+
abortController.abort();
|
|
176
|
+
if (currentTaskId) {
|
|
177
|
+
try {
|
|
178
|
+
await manager.cancelTask(server, currentTaskId);
|
|
179
|
+
} catch {
|
|
180
|
+
// best effort
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await manager.close();
|
|
184
|
+
process.exit(130);
|
|
185
|
+
};
|
|
186
|
+
process.on("SIGINT", sigintHandler);
|
|
187
|
+
|
|
188
|
+
const spinner = logger.startSpinner(`Executing ${server}/${tool}...`, formatOptions);
|
|
189
|
+
try {
|
|
190
|
+
const stream = manager.callToolStream(server, tool, args, {
|
|
191
|
+
ttl: parseInt(options.ttl, 10),
|
|
192
|
+
signal: abortController.signal,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
for await (const message of stream) {
|
|
196
|
+
switch (message.type) {
|
|
197
|
+
case "taskCreated":
|
|
198
|
+
currentTaskId = message.task.taskId;
|
|
199
|
+
if (!options.wait) {
|
|
200
|
+
// --no-wait: output the task handle and exit
|
|
201
|
+
spinner.stop();
|
|
202
|
+
console.log(formatTaskCreated(message.task, formatOptions));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
spinner.update(`Task ${message.task.taskId} (${message.task.status})...`);
|
|
206
|
+
break;
|
|
207
|
+
case "taskStatus":
|
|
208
|
+
spinner.update(`Task ${message.task.taskId} (${message.task.status})...`);
|
|
209
|
+
break;
|
|
210
|
+
case "result":
|
|
211
|
+
spinner.stop();
|
|
212
|
+
console.log(formatCallResult(message.result, formatOptions));
|
|
213
|
+
return;
|
|
214
|
+
case "error":
|
|
215
|
+
spinner.error("Task failed");
|
|
216
|
+
throw message.error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// Standard synchronous tool call
|
|
224
|
+
const spinner = logger.startSpinner(`Executing ${server}/${tool}...`, formatOptions);
|
|
225
|
+
const result = await manager.callTool(server, tool, args);
|
|
226
|
+
spinner.stop();
|
|
227
|
+
console.log(formatCallResult(result, formatOptions));
|
|
228
|
+
}
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error(formatError(String(err), formatOptions));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
} finally {
|
|
233
|
+
await manager.close();
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
);
|
|
241
237
|
}
|
package/src/commands/index.ts
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
1
|
import { yellow } from "ansis";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { saveSearchIndex } from "../config/loader.ts";
|
|
3
4
|
import { getContext } from "../context.ts";
|
|
5
|
+
import { logger } from "../output/logger.ts";
|
|
4
6
|
import { buildSearchIndex } from "../search/indexer.ts";
|
|
5
7
|
import { getStaleServers } from "../search/staleness.ts";
|
|
6
|
-
import { saveSearchIndex } from "../config/loader.ts";
|
|
7
|
-
import { logger } from "../output/logger.ts";
|
|
8
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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);
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
await saveSearchIndex(config.configDir, index);
|
|
23
|
+
spinner.success(`Indexed ${index.tools.length} tools in ${elapsed}s`);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
logger.info(`Saved to ${config.configDir}/search.json`);
|
|
26
|
+
},
|
|
27
|
+
)();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function registerIndexCommand(program: Command) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
program
|
|
32
|
+
.command("index")
|
|
33
|
+
.description("build the search index from all configured servers")
|
|
34
|
+
.option("-i, --status", "show index status")
|
|
35
|
+
.action(async (options: { status?: boolean }) => {
|
|
36
|
+
if (options.status) {
|
|
37
|
+
const { config, manager } = await getContext(program);
|
|
38
|
+
const idx = config.searchIndex;
|
|
39
|
+
if (idx.tools.length === 0) {
|
|
40
|
+
console.log("No search index. Run: mcpx index");
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`Tools: ${idx.tools.length}`);
|
|
43
|
+
console.log(`Model: ${idx.embedding_model}`);
|
|
44
|
+
console.log(`Indexed: ${idx.indexed_at}`);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
const stale = getStaleServers(idx, config.servers);
|
|
47
|
+
if (stale.length > 0) {
|
|
48
|
+
console.log(yellow(`Stale: ${stale.join(", ")} (run mcpx index to refresh)`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await manager.close();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
await runIndex(program);
|
|
56
|
+
});
|
|
57
57
|
}
|