@mcp-s/cli 0.0.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/dist/index.js ADDED
@@ -0,0 +1,3200 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
5
+ import { homedir as homedir6 } from "os";
6
+ import { join as join7 } from "path";
7
+
8
+ // src/client.ts
9
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
10
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
12
+
13
+ // src/auth.ts
14
+ var tokenStorage = null;
15
+ var clientStorage = null;
16
+ async function getAuthFilePath() {
17
+ const os = await import("os");
18
+ const path = await import("path");
19
+ const fs = await import("fs");
20
+ const dir = path.join(os.homedir(), ".config", "mcp-s-cli");
21
+ fs.mkdirSync(dir, { recursive: true });
22
+ return path.join(dir, "auth.json");
23
+ }
24
+ async function loadAuthData() {
25
+ if (tokenStorage !== null && clientStorage !== null) {
26
+ return;
27
+ }
28
+ tokenStorage = /* @__PURE__ */ new Map();
29
+ clientStorage = /* @__PURE__ */ new Map();
30
+ try {
31
+ const fs = await import("fs/promises");
32
+ const authPath = await getAuthFilePath();
33
+ const data = await fs.readFile(authPath, "utf-8");
34
+ const parsed = JSON.parse(data);
35
+ if (parsed.tokens) {
36
+ for (const [key, value] of Object.entries(parsed.tokens)) {
37
+ tokenStorage.set(key, value);
38
+ }
39
+ }
40
+ if (parsed.clients) {
41
+ for (const [key, value] of Object.entries(parsed.clients)) {
42
+ clientStorage.set(key, value);
43
+ }
44
+ }
45
+ } catch {
46
+ }
47
+ }
48
+ async function saveAuthData() {
49
+ if (!tokenStorage || !clientStorage) {
50
+ return;
51
+ }
52
+ try {
53
+ const fs = await import("fs/promises");
54
+ const authPath = await getAuthFilePath();
55
+ const data = {
56
+ tokens: Object.fromEntries(tokenStorage),
57
+ clients: Object.fromEntries(clientStorage)
58
+ };
59
+ await fs.writeFile(authPath, JSON.stringify(data, null, 2), {
60
+ mode: 384
61
+ });
62
+ } catch (err) {
63
+ if (process.env.MCP_DEBUG) {
64
+ console.error(
65
+ `[mcp-s-cli] Failed to save auth data: ${err.message}`
66
+ );
67
+ }
68
+ }
69
+ }
70
+ async function getTokenStorage() {
71
+ await loadAuthData();
72
+ return tokenStorage ?? /* @__PURE__ */ new Map();
73
+ }
74
+ async function getClientStorage() {
75
+ await loadAuthData();
76
+ return clientStorage ?? /* @__PURE__ */ new Map();
77
+ }
78
+ function generateCodeVerifier() {
79
+ const array = new Uint8Array(32);
80
+ crypto.getRandomValues(array);
81
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
82
+ ""
83
+ );
84
+ }
85
+ async function generateCodeChallenge(verifier) {
86
+ const encoder = new TextEncoder();
87
+ const data = encoder.encode(verifier);
88
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
89
+ const hashArray = new Uint8Array(hashBuffer);
90
+ const base64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
91
+ return base64;
92
+ }
93
+ function parseWWWAuthenticateHeader(header) {
94
+ const challenge = {};
95
+ const bearerMatch = header.match(/Bearer\s+(.*)/i);
96
+ if (!bearerMatch) {
97
+ return challenge;
98
+ }
99
+ const params = bearerMatch[1] ?? "";
100
+ const paramRegex = /(\w+)="([^"]*)"/g;
101
+ for (const match of params.matchAll(paramRegex)) {
102
+ const [, key, value] = match;
103
+ if (key === "error") {
104
+ challenge.error = value;
105
+ } else if (key === "error_description") {
106
+ challenge.error_description = value;
107
+ } else if (key === "resource_metadata") {
108
+ challenge.resource_metadata = value;
109
+ }
110
+ }
111
+ return challenge;
112
+ }
113
+ async function fetchProtectedResourceMetadata(metadataUrl) {
114
+ try {
115
+ const response = await fetch(metadataUrl, {
116
+ signal: AbortSignal.timeout(1e4)
117
+ });
118
+ if (!response.ok) {
119
+ return null;
120
+ }
121
+ return await response.json();
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+ async function fetchAuthServerMetadata(authServerUrl) {
127
+ try {
128
+ const parsed = new URL(authServerUrl);
129
+ const metadataUrl = `${parsed.protocol}//${parsed.host}/.well-known/oauth-authorization-server`;
130
+ const response = await fetch(metadataUrl, {
131
+ signal: AbortSignal.timeout(1e4)
132
+ });
133
+ if (!response.ok) {
134
+ return null;
135
+ }
136
+ return await response.json();
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ async function registerOAuthClient(registrationEndpoint, redirectUri) {
142
+ try {
143
+ const clientMetadata = {
144
+ client_name: "mcp-s-cli",
145
+ redirect_uris: [redirectUri],
146
+ grant_types: ["authorization_code"],
147
+ response_types: ["code"],
148
+ token_endpoint_auth_method: "none"
149
+ };
150
+ const response = await fetch(registrationEndpoint, {
151
+ method: "POST",
152
+ headers: {
153
+ "Content-Type": "application/json"
154
+ },
155
+ body: JSON.stringify(clientMetadata),
156
+ signal: AbortSignal.timeout(1e4)
157
+ });
158
+ if (!response.ok) {
159
+ return null;
160
+ }
161
+ return await response.json();
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ async function exchangeAuthorizationCode(tokenEndpoint, clientId, code, codeVerifier, redirectUri) {
167
+ try {
168
+ const params = new URLSearchParams({
169
+ grant_type: "authorization_code",
170
+ code,
171
+ client_id: clientId,
172
+ code_verifier: codeVerifier,
173
+ redirect_uri: redirectUri
174
+ });
175
+ const response = await fetch(tokenEndpoint, {
176
+ method: "POST",
177
+ headers: {
178
+ "Content-Type": "application/x-www-form-urlencoded"
179
+ },
180
+ body: params.toString(),
181
+ signal: AbortSignal.timeout(1e4)
182
+ });
183
+ if (!response.ok) {
184
+ return null;
185
+ }
186
+ return await response.json();
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ async function startCallbackServer(port) {
192
+ const http = await import("http");
193
+ return new Promise((resolve4, reject) => {
194
+ const server = http.createServer((req, res) => {
195
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
196
+ const code = url.searchParams.get("code");
197
+ const error = url.searchParams.get("error");
198
+ if (error) {
199
+ res.writeHead(400, { "Content-Type": "text/html" });
200
+ res.end(`
201
+ <html>
202
+ <body>
203
+ <h1>Authorization Failed</h1>
204
+ <p>Error: ${error}</p>
205
+ <p>You can close this window.</p>
206
+ </body>
207
+ </html>
208
+ `);
209
+ server.close();
210
+ reject(new Error(`OAuth error: ${error}`));
211
+ return;
212
+ }
213
+ if (code) {
214
+ res.writeHead(200, { "Content-Type": "text/html" });
215
+ res.end(`
216
+ <html>
217
+ <body>
218
+ <h1>Authorization Successful</h1>
219
+ <p>You can close this window and return to the CLI.</p>
220
+ </body>
221
+ </html>
222
+ `);
223
+ server.close();
224
+ resolve4(code);
225
+ return;
226
+ }
227
+ res.writeHead(400, { "Content-Type": "text/html" });
228
+ res.end("<html><body><h1>Invalid Request</h1></body></html>");
229
+ });
230
+ server.listen(port, "127.0.0.1", () => {
231
+ });
232
+ setTimeout(
233
+ () => {
234
+ server.close();
235
+ reject(new Error("OAuth callback timeout"));
236
+ },
237
+ 5 * 60 * 1e3
238
+ );
239
+ });
240
+ }
241
+ async function getOpenCommand() {
242
+ const platform2 = process.platform;
243
+ if (platform2 === "darwin") {
244
+ return "open";
245
+ }
246
+ if (platform2 === "win32") {
247
+ return "start";
248
+ }
249
+ if (platform2 === "linux") {
250
+ return "xdg-open";
251
+ }
252
+ return null;
253
+ }
254
+ async function performOAuthFlow(authServerUrl, resourceUrl) {
255
+ const authMetadata = await fetchAuthServerMetadata(authServerUrl);
256
+ if (!authMetadata) {
257
+ console.error("Failed to fetch authorization server metadata");
258
+ return null;
259
+ }
260
+ if (!authMetadata.registration_endpoint) {
261
+ console.error(
262
+ "Authorization server does not support dynamic client registration"
263
+ );
264
+ return null;
265
+ }
266
+ const callbackPort = 8085;
267
+ const redirectUri = `http://127.0.0.1:${callbackPort}/callback`;
268
+ const serverKey = new URL(authServerUrl).origin;
269
+ const clients = await getClientStorage();
270
+ let clientInfo = clients.get(serverKey);
271
+ if (!clientInfo) {
272
+ const newClientInfo = await registerOAuthClient(
273
+ authMetadata.registration_endpoint,
274
+ redirectUri
275
+ );
276
+ if (!newClientInfo) {
277
+ console.error("Failed to register OAuth client");
278
+ return null;
279
+ }
280
+ clientInfo = newClientInfo;
281
+ clients.set(serverKey, clientInfo);
282
+ await saveAuthData();
283
+ }
284
+ const codeVerifier = generateCodeVerifier();
285
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
286
+ const authUrl = new URL(authMetadata.authorization_endpoint);
287
+ authUrl.searchParams.set("response_type", "code");
288
+ authUrl.searchParams.set("client_id", clientInfo.client_id);
289
+ authUrl.searchParams.set("redirect_uri", redirectUri);
290
+ authUrl.searchParams.set("code_challenge", codeChallenge);
291
+ authUrl.searchParams.set("code_challenge_method", "S256");
292
+ authUrl.searchParams.set("resource", resourceUrl);
293
+ console.error(
294
+ "\nAuthentication required. Opening browser for authorization..."
295
+ );
296
+ console.error(`If the browser doesn't open, visit: ${authUrl.toString()}
297
+ `);
298
+ const open = await getOpenCommand();
299
+ if (open) {
300
+ try {
301
+ const { spawn: spawn2 } = await import("child_process");
302
+ spawn2(open, [authUrl.toString()], {
303
+ detached: true,
304
+ stdio: "ignore"
305
+ }).unref();
306
+ } catch {
307
+ console.error("Could not open browser automatically.");
308
+ }
309
+ }
310
+ try {
311
+ const code = await startCallbackServer(callbackPort);
312
+ const tokens = await exchangeAuthorizationCode(
313
+ authMetadata.token_endpoint,
314
+ clientInfo.client_id,
315
+ code,
316
+ codeVerifier,
317
+ redirectUri
318
+ );
319
+ if (tokens) {
320
+ const resourceOrigin = new URL(resourceUrl).origin;
321
+ const tokenMap = await getTokenStorage();
322
+ tokenMap.set(resourceOrigin, tokens);
323
+ await saveAuthData();
324
+ }
325
+ return tokens;
326
+ } catch (error) {
327
+ console.error("OAuth flow failed:", error);
328
+ return null;
329
+ }
330
+ }
331
+ async function getStoredTokens(url) {
332
+ const origin = new URL(url).origin;
333
+ const tokens = await getTokenStorage();
334
+ return tokens.get(origin);
335
+ }
336
+ async function storeTokens(url, tokens) {
337
+ const origin = new URL(url).origin;
338
+ const tokenMap = await getTokenStorage();
339
+ tokenMap.set(origin, tokens);
340
+ await saveAuthData();
341
+ }
342
+ async function clearTokens(url) {
343
+ const origin = new URL(url).origin;
344
+ const tokens = await getTokenStorage();
345
+ tokens.delete(origin);
346
+ await saveAuthData();
347
+ }
348
+
349
+ // src/config.ts
350
+ import { createHash } from "crypto";
351
+ import { existsSync, readFileSync } from "fs";
352
+ import { homedir } from "os";
353
+ import { join, resolve } from "path";
354
+
355
+ // src/errors.ts
356
+ function formatCliError(error) {
357
+ const lines = [];
358
+ lines.push(`Error [${error.type}]: ${error.message}`);
359
+ if (error.details) {
360
+ lines.push(` Details: ${error.details}`);
361
+ }
362
+ if (error.suggestion) {
363
+ lines.push(` Suggestion: ${error.suggestion}`);
364
+ }
365
+ return lines.join("\n");
366
+ }
367
+ function configNotFoundError(path) {
368
+ return {
369
+ code: 1 /* CLIENT_ERROR */,
370
+ type: "CONFIG_NOT_FOUND",
371
+ message: `Config file not found: ${path}`,
372
+ suggestion: `Create config.json with: { "baseUrl": "https://..." } or run mcp-s-cli init`
373
+ };
374
+ }
375
+ function configSearchError() {
376
+ return {
377
+ code: 1 /* CLIENT_ERROR */,
378
+ type: "CONFIG_NOT_FOUND",
379
+ message: "No config.json config file found",
380
+ details: "Searched: ~/.config/mcp-s-cli/config.json",
381
+ suggestion: "Run mcp-s-cli init or use -c/--config to specify a path"
382
+ };
383
+ }
384
+ function configInvalidJsonError(path, parseError) {
385
+ return {
386
+ code: 1 /* CLIENT_ERROR */,
387
+ type: "CONFIG_INVALID_JSON",
388
+ message: `Invalid JSON in config file: ${path}`,
389
+ details: parseError,
390
+ suggestion: "Check for syntax errors: missing commas, unquoted keys, trailing commas"
391
+ };
392
+ }
393
+ function configMissingFieldError(path) {
394
+ return {
395
+ code: 1 /* CLIENT_ERROR */,
396
+ type: "CONFIG_MISSING_FIELD",
397
+ message: "Config file is missing required server fields",
398
+ details: `File: ${path}`,
399
+ suggestion: 'Config must have "org" or "baseUrl" (HTTP) or "userAccessKey" (stdio). Run mcp-s-cli init to set up.'
400
+ };
401
+ }
402
+ function serverConnectionError(serverLabel, cause) {
403
+ let suggestion = "Check server configuration and ensure the server process can start";
404
+ if (cause.includes("ENOENT") || cause.includes("not found")) {
405
+ suggestion = "Command not found. Install the MCP server: npx -y @modelcontextprotocol/server-<name>";
406
+ } else if (cause.includes("ECONNREFUSED")) {
407
+ suggestion = "Server refused connection. Check if the server is running and URL is correct";
408
+ } else if (cause.includes("ETIMEDOUT") || cause.includes("timeout")) {
409
+ suggestion = "Connection timed out. Check network connectivity and server availability";
410
+ } else if (cause.includes("401") || cause.includes("Unauthorized")) {
411
+ suggestion = "Authentication required. Add Authorization header to config";
412
+ } else if (cause.includes("403") || cause.includes("Forbidden")) {
413
+ suggestion = "Access forbidden. Check credentials and permissions";
414
+ }
415
+ return {
416
+ code: 3 /* NETWORK_ERROR */,
417
+ type: "SERVER_CONNECTION_FAILED",
418
+ message: `Failed to connect to server "${serverLabel}"`,
419
+ details: cause,
420
+ suggestion
421
+ };
422
+ }
423
+ function toolNotFoundError(toolName, serverLabel, availableTools) {
424
+ const toolList = availableTools?.slice(0, 5).join(", ") || "";
425
+ const moreCount = availableTools && availableTools.length > 5 ? ` (+${availableTools.length - 5} more)` : "";
426
+ return {
427
+ code: 1 /* CLIENT_ERROR */,
428
+ type: "TOOL_NOT_FOUND",
429
+ message: `Tool "${toolName}" not found in server "${serverLabel}"`,
430
+ details: availableTools ? `Available tools: ${toolList}${moreCount}` : void 0,
431
+ suggestion: `Run 'mcp-s-cli' to see all available tools`
432
+ };
433
+ }
434
+ function toolExecutionError(toolName, serverLabel, cause) {
435
+ let suggestion = "Check tool arguments match the expected schema";
436
+ if (cause.includes("validation") || cause.includes("invalid_type")) {
437
+ suggestion = `Run 'mcp-s-cli info ${toolName}' to see the input schema, then fix arguments`;
438
+ } else if (cause.includes("required")) {
439
+ suggestion = `Missing required argument. Run 'mcp-s-cli info ${toolName}' to see required fields`;
440
+ } else if (cause.includes("permission") || cause.includes("denied")) {
441
+ suggestion = "Permission denied. Check file/resource permissions";
442
+ } else if (cause.includes("not found") || cause.includes("ENOENT")) {
443
+ suggestion = "Resource not found. Verify the path or identifier exists";
444
+ }
445
+ return {
446
+ code: 2 /* SERVER_ERROR */,
447
+ type: "TOOL_EXECUTION_FAILED",
448
+ message: `Tool "${toolName}" execution failed`,
449
+ details: cause,
450
+ suggestion
451
+ };
452
+ }
453
+ function invalidJsonArgsError(input, parseError) {
454
+ const truncated = input.length > 100 ? `${input.substring(0, 100)}...` : input;
455
+ return {
456
+ code: 1 /* CLIENT_ERROR */,
457
+ type: "INVALID_JSON_ARGUMENTS",
458
+ message: "Invalid JSON in tool arguments",
459
+ details: parseError ? `Parse error: ${parseError}` : `Input: ${truncated}`,
460
+ suggestion: `Use valid JSON: '{"path": "./file.txt"}'. Run 'mcp-s-cli info <tool>' for the schema.`
461
+ };
462
+ }
463
+ function unknownOptionError(option) {
464
+ let suggestion;
465
+ const optionLower = option.toLowerCase().replace(/^-+/, "");
466
+ if (["tool", "t"].includes(optionLower)) {
467
+ suggestion = `Tool is a positional argument. Use 'mcp-s-cli call <tool>'`;
468
+ } else if (["args", "arguments", "a", "input"].includes(optionLower)) {
469
+ suggestion = `Pass JSON directly: 'mcp-s-cli call <tool> '{"key": "value"}''`;
470
+ } else if (["pattern", "p", "search", "query"].includes(optionLower)) {
471
+ suggestion = `Use 'mcp-s-cli grep "*pattern*"'`;
472
+ } else if (["call", "run", "exec"].includes(optionLower)) {
473
+ suggestion = `Use 'call' as a subcommand, not option: 'mcp-s-cli call <tool>'`;
474
+ } else if (["info", "list", "get"].includes(optionLower)) {
475
+ suggestion = `Use 'info' as a subcommand, not option: 'mcp-s-cli info'`;
476
+ } else {
477
+ suggestion = "Valid options: -c/--config, -j/--json, -d/--with-descriptions, -r/--raw";
478
+ }
479
+ return {
480
+ code: 1 /* CLIENT_ERROR */,
481
+ type: "UNKNOWN_OPTION",
482
+ message: `Unknown option: ${option}`,
483
+ suggestion
484
+ };
485
+ }
486
+ function missingArgumentError(command, argument) {
487
+ let suggestion;
488
+ switch (command) {
489
+ case "call":
490
+ suggestion = `Use 'mcp-s-cli call <tool> '{"key": "value"}''`;
491
+ break;
492
+ case "grep":
493
+ suggestion = `Use 'mcp-s-cli grep "*pattern*"'`;
494
+ break;
495
+ case "-c/--config":
496
+ suggestion = `Use 'mcp-s-cli -c /path/to/config.json'`;
497
+ break;
498
+ default:
499
+ suggestion = `Run 'mcp-s-cli --help' for usage examples`;
500
+ }
501
+ return {
502
+ code: 1 /* CLIENT_ERROR */,
503
+ type: "MISSING_ARGUMENT",
504
+ message: `Missing required argument for ${command}: ${argument}`,
505
+ suggestion
506
+ };
507
+ }
508
+ function unknownSubcommandError(subcommand) {
509
+ const suggestions = {
510
+ run: "call",
511
+ execute: "call",
512
+ exec: "call",
513
+ invoke: "call",
514
+ list: "info",
515
+ ls: "info",
516
+ get: "info",
517
+ show: "info",
518
+ describe: "info",
519
+ search: "grep",
520
+ find: "grep",
521
+ query: "grep"
522
+ };
523
+ const suggested = suggestions[subcommand.toLowerCase()];
524
+ const validCommands = "info, grep, call";
525
+ return {
526
+ code: 1 /* CLIENT_ERROR */,
527
+ type: "UNKNOWN_SUBCOMMAND",
528
+ message: `Unknown subcommand: "${subcommand}"`,
529
+ details: `Valid subcommands: ${validCommands}`,
530
+ suggestion: suggested ? `Did you mean 'mcp-s-cli ${suggested}'?` : `Use 'mcp-s-cli --help' to see available commands`
531
+ };
532
+ }
533
+ function tooManyArgumentsError(command, received, max) {
534
+ return {
535
+ code: 1 /* CLIENT_ERROR */,
536
+ type: "TOO_MANY_ARGUMENTS",
537
+ message: `Too many arguments for ${command}`,
538
+ details: `Received ${received} arguments, maximum is ${max}`,
539
+ suggestion: `Run 'mcp-s-cli --help' for correct usage`
540
+ };
541
+ }
542
+
543
+ // src/config.ts
544
+ function matchesPattern(name, pattern) {
545
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
546
+ return new RegExp(`^${regexPattern}$`, "i").test(name);
547
+ }
548
+ function matchesAnyPattern(name, patterns) {
549
+ return patterns.some((pattern) => matchesPattern(name, pattern));
550
+ }
551
+ function filterTools(tools, config) {
552
+ const { allowedTools, disabledTools } = config;
553
+ return tools.filter((tool) => {
554
+ if (disabledTools && disabledTools.length > 0) {
555
+ if (matchesAnyPattern(tool.name, disabledTools)) {
556
+ return false;
557
+ }
558
+ }
559
+ if (allowedTools && allowedTools.length > 0) {
560
+ return matchesAnyPattern(tool.name, allowedTools);
561
+ }
562
+ return true;
563
+ });
564
+ }
565
+ function isToolAllowed(toolName, config) {
566
+ const { allowedTools, disabledTools } = config;
567
+ if (disabledTools && disabledTools.length > 0) {
568
+ if (matchesAnyPattern(toolName, disabledTools)) {
569
+ return false;
570
+ }
571
+ }
572
+ if (allowedTools && allowedTools.length > 0) {
573
+ return matchesAnyPattern(toolName, allowedTools);
574
+ }
575
+ return true;
576
+ }
577
+ function isHttpServer(config) {
578
+ return "url" in config;
579
+ }
580
+ var DEFAULT_TIMEOUT_SECONDS = 1800;
581
+ var DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1e3;
582
+ var DEFAULT_MAX_RETRIES = 3;
583
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
584
+ var DEFAULT_DAEMON_TIMEOUT_SECONDS = 300;
585
+ function debug(message) {
586
+ if (process.env.MCP_DEBUG) {
587
+ console.error(`[mcp-s-cli] ${message}`);
588
+ }
589
+ }
590
+ function getTimeoutMs() {
591
+ const envTimeout = process.env.MCP_TIMEOUT;
592
+ if (envTimeout) {
593
+ const seconds = Number.parseInt(envTimeout, 10);
594
+ if (!Number.isNaN(seconds) && seconds > 0) {
595
+ return seconds * 1e3;
596
+ }
597
+ }
598
+ return DEFAULT_TIMEOUT_MS;
599
+ }
600
+ function getMaxRetries() {
601
+ const envRetries = process.env.MCP_MAX_RETRIES;
602
+ if (envRetries) {
603
+ const retries = Number.parseInt(envRetries, 10);
604
+ if (!Number.isNaN(retries) && retries >= 0) {
605
+ return retries;
606
+ }
607
+ }
608
+ return DEFAULT_MAX_RETRIES;
609
+ }
610
+ function getRetryDelayMs() {
611
+ const envDelay = process.env.MCP_RETRY_DELAY;
612
+ if (envDelay) {
613
+ const delay = Number.parseInt(envDelay, 10);
614
+ if (!Number.isNaN(delay) && delay > 0) {
615
+ return delay;
616
+ }
617
+ }
618
+ return DEFAULT_RETRY_DELAY_MS;
619
+ }
620
+ var DAEMON_SERVER_NAME = "mcp-s-cli";
621
+ function isDaemonEnabled() {
622
+ return process.env.MCP_DAEMON !== "0";
623
+ }
624
+ function getDaemonTimeoutMs() {
625
+ const envTimeout = process.env.MCP_DAEMON_TIMEOUT;
626
+ if (envTimeout) {
627
+ const seconds = Number.parseInt(envTimeout, 10);
628
+ if (!Number.isNaN(seconds) && seconds > 0) {
629
+ return seconds * 1e3;
630
+ }
631
+ }
632
+ return DEFAULT_DAEMON_TIMEOUT_SECONDS * 1e3;
633
+ }
634
+ function getSocketDir() {
635
+ const uid = process.getuid?.() ?? "unknown";
636
+ const base = process.platform === "darwin" ? "/tmp" : "/tmp";
637
+ return join(base, `mcp-s-cli-${uid}`);
638
+ }
639
+ function getSocketPath() {
640
+ return join(getSocketDir(), `${DAEMON_SERVER_NAME}.sock`);
641
+ }
642
+ function getPidPath() {
643
+ return join(getSocketDir(), `${DAEMON_SERVER_NAME}.pid`);
644
+ }
645
+ function getReadyPath() {
646
+ return join(getSocketDir(), `${DAEMON_SERVER_NAME}.ready`);
647
+ }
648
+ function getConfigHash(config) {
649
+ const str = JSON.stringify(config, Object.keys(config).sort());
650
+ return createHash("sha256").update(str).digest("hex").slice(0, 16);
651
+ }
652
+ function isStrictEnvMode() {
653
+ const value = process.env.MCP_STRICT_ENV?.toLowerCase();
654
+ return value !== "false" && value !== "0";
655
+ }
656
+ function substituteEnvVars(value) {
657
+ const missingVars = [];
658
+ const result = value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
659
+ const envValue = process.env[varName];
660
+ if (envValue === void 0) {
661
+ missingVars.push(varName);
662
+ return "";
663
+ }
664
+ return envValue;
665
+ });
666
+ if (missingVars.length > 0) {
667
+ const varList = missingVars.map((v) => `\${${v}}`).join(", ");
668
+ const message = `Missing environment variable${missingVars.length > 1 ? "s" : ""}: ${varList}`;
669
+ if (isStrictEnvMode()) {
670
+ throw new Error(
671
+ formatCliError({
672
+ code: 1 /* CLIENT_ERROR */,
673
+ type: "MISSING_ENV_VAR",
674
+ message,
675
+ details: "Referenced in config but not set in environment",
676
+ suggestion: `Set the variable(s) before running: export ${missingVars[0]}="value" or set MCP_STRICT_ENV=false to use empty values`
677
+ })
678
+ );
679
+ }
680
+ console.error(`[mcp-s-cli] Warning: ${message}`);
681
+ }
682
+ return result;
683
+ }
684
+ function substituteEnvVarsInObject(obj) {
685
+ if (typeof obj === "string") {
686
+ return substituteEnvVars(obj);
687
+ }
688
+ if (Array.isArray(obj)) {
689
+ return obj.map(substituteEnvVarsInObject);
690
+ }
691
+ if (obj && typeof obj === "object") {
692
+ const result = {};
693
+ for (const [key, value] of Object.entries(obj)) {
694
+ result[key] = substituteEnvVarsInObject(value);
695
+ }
696
+ return result;
697
+ }
698
+ return obj;
699
+ }
700
+ function getDefaultConfigPaths() {
701
+ const home2 = homedir();
702
+ return [join(home2, ".config", "mcp-s-cli", "config.json")];
703
+ }
704
+ function deriveServerConfig(raw) {
705
+ const resolvedUrl = raw.org ? `https://${raw.org}.mcp-s.com/mcp` : raw.baseUrl;
706
+ if (raw.userAccessKey) {
707
+ const env = {
708
+ USER_ACCESS_KEY: raw.userAccessKey
709
+ };
710
+ if (raw.mcp) env.MCP = raw.mcp;
711
+ if (raw.toolkit) env.TOOLKIT = raw.toolkit;
712
+ if (resolvedUrl) env.BASE_URL = resolvedUrl;
713
+ return {
714
+ type: "stdio",
715
+ command: "npx",
716
+ args: ["-y", "@mcp-s/mcp"],
717
+ env,
718
+ allowedTools: raw.allowedTools,
719
+ disabledTools: raw.disabledTools
720
+ };
721
+ }
722
+ if (!resolvedUrl) {
723
+ throw new Error(
724
+ formatCliError({
725
+ code: 1 /* CLIENT_ERROR */,
726
+ type: "CONFIG_INVALID",
727
+ message: "Config is missing a server URL",
728
+ details: 'Either "org", "baseUrl" (HTTP mode) or "userAccessKey" (stdio mode) is required',
729
+ suggestion: "Run mcp-s-cli init --org <org> or mcp-s-cli init --base-url <url>"
730
+ })
731
+ );
732
+ }
733
+ const headers = {};
734
+ if (raw.mcp) headers["x-mcp"] = raw.mcp;
735
+ if (raw.toolkit) headers["x-toolkit"] = raw.toolkit;
736
+ if (raw.token) headers.Authorization = `Bearer ${raw.token}`;
737
+ const serverConfig = {
738
+ url: resolvedUrl,
739
+ allowedTools: raw.allowedTools,
740
+ disabledTools: raw.disabledTools
741
+ };
742
+ if (Object.keys(headers).length > 0) {
743
+ serverConfig.headers = headers;
744
+ }
745
+ return serverConfig;
746
+ }
747
+ async function loadConfig(explicitPath) {
748
+ let configPath;
749
+ if (explicitPath) {
750
+ configPath = resolve(explicitPath);
751
+ } else if (process.env.MCP_CONFIG_PATH) {
752
+ configPath = resolve(process.env.MCP_CONFIG_PATH);
753
+ }
754
+ if (configPath) {
755
+ if (!existsSync(configPath)) {
756
+ throw new Error(formatCliError(configNotFoundError(configPath)));
757
+ }
758
+ } else {
759
+ const searchPaths = getDefaultConfigPaths();
760
+ for (const path of searchPaths) {
761
+ if (existsSync(path)) {
762
+ configPath = path;
763
+ break;
764
+ }
765
+ }
766
+ if (!configPath) {
767
+ throw new Error(formatCliError(configSearchError()));
768
+ }
769
+ }
770
+ const content = readFileSync(configPath, "utf-8");
771
+ let raw;
772
+ try {
773
+ raw = JSON.parse(content);
774
+ } catch (e) {
775
+ throw new Error(
776
+ formatCliError(configInvalidJsonError(configPath, e.message))
777
+ );
778
+ }
779
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
780
+ throw new Error(formatCliError(configMissingFieldError(configPath)));
781
+ }
782
+ const knownFields = [
783
+ "org",
784
+ "baseUrl",
785
+ "mcp",
786
+ "toolkit",
787
+ "userAccessKey",
788
+ "token",
789
+ "allowedTools",
790
+ "disabledTools"
791
+ ];
792
+ const hasKnownField = knownFields.some((f) => f in raw);
793
+ if (!hasKnownField) {
794
+ throw new Error(formatCliError(configMissingFieldError(configPath)));
795
+ }
796
+ raw = substituteEnvVarsInObject(raw);
797
+ const serverConfig = deriveServerConfig(raw);
798
+ return { raw, serverConfig };
799
+ }
800
+
801
+ // src/daemon-client.ts
802
+ import { spawn } from "child_process";
803
+ import { existsSync as existsSync3, readdirSync } from "fs";
804
+ import { createConnection } from "net";
805
+ import { join as join2 } from "path";
806
+ import { fileURLToPath } from "url";
807
+
808
+ // src/daemon.ts
809
+ import {
810
+ existsSync as existsSync2,
811
+ mkdirSync,
812
+ readFileSync as readFileSync2,
813
+ unlinkSync,
814
+ writeFileSync
815
+ } from "fs";
816
+ import { createServer } from "net";
817
+ import { dirname } from "path";
818
+ function writePidFile(serverName, configHash) {
819
+ const pidPath = getPidPath();
820
+ const dir = dirname(pidPath);
821
+ if (!existsSync2(dir)) {
822
+ mkdirSync(dir, { recursive: true, mode: 448 });
823
+ }
824
+ const content = {
825
+ pid: process.pid,
826
+ configHash,
827
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
828
+ };
829
+ writeFileSync(pidPath, JSON.stringify(content), { mode: 384 });
830
+ }
831
+ function readPidFile(serverName) {
832
+ const pidPath = getPidPath();
833
+ if (!existsSync2(pidPath)) {
834
+ return null;
835
+ }
836
+ try {
837
+ const content = readFileSync2(pidPath, "utf-8");
838
+ return JSON.parse(content);
839
+ } catch {
840
+ return null;
841
+ }
842
+ }
843
+ function removePidFile(serverName) {
844
+ const pidPath = getPidPath();
845
+ try {
846
+ if (existsSync2(pidPath)) {
847
+ unlinkSync(pidPath);
848
+ }
849
+ } catch {
850
+ }
851
+ }
852
+ function removeSocketFile(serverName) {
853
+ const socketPath = getSocketPath();
854
+ try {
855
+ if (existsSync2(socketPath)) {
856
+ unlinkSync(socketPath);
857
+ }
858
+ } catch {
859
+ }
860
+ }
861
+ function removeReadyFile(serverName) {
862
+ const readyPath = getReadyPath();
863
+ try {
864
+ if (existsSync2(readyPath)) {
865
+ unlinkSync(readyPath);
866
+ }
867
+ } catch {
868
+ }
869
+ }
870
+ function isProcessRunning(pid) {
871
+ try {
872
+ process.kill(pid, 0);
873
+ return true;
874
+ } catch {
875
+ return false;
876
+ }
877
+ }
878
+ function killProcess(pid) {
879
+ try {
880
+ process.kill(pid, "SIGTERM");
881
+ return true;
882
+ } catch {
883
+ return false;
884
+ }
885
+ }
886
+ async function runDaemon(serverName, config) {
887
+ const socketPath = getSocketPath();
888
+ const configHash = getConfigHash(config);
889
+ const timeoutMs = getDaemonTimeoutMs();
890
+ let idleTimer = null;
891
+ let mcpClient = null;
892
+ let server = null;
893
+ const activeConnections = /* @__PURE__ */ new Set();
894
+ const cleanup = async () => {
895
+ debug(`[daemon:${serverName}] Shutting down...`);
896
+ if (idleTimer) {
897
+ clearTimeout(idleTimer);
898
+ idleTimer = null;
899
+ }
900
+ for (const conn of activeConnections) {
901
+ try {
902
+ conn.end();
903
+ } catch {
904
+ }
905
+ }
906
+ activeConnections.clear();
907
+ if (mcpClient) {
908
+ try {
909
+ await mcpClient.close();
910
+ } catch {
911
+ }
912
+ mcpClient = null;
913
+ }
914
+ if (server) {
915
+ try {
916
+ server.close();
917
+ } catch {
918
+ }
919
+ server = null;
920
+ }
921
+ removeSocketFile(serverName);
922
+ removePidFile(serverName);
923
+ removeReadyFile(serverName);
924
+ debug(`[daemon:${serverName}] Cleanup complete`);
925
+ };
926
+ const resetIdleTimer = () => {
927
+ if (idleTimer) {
928
+ clearTimeout(idleTimer);
929
+ }
930
+ idleTimer = setTimeout(async () => {
931
+ debug(`[daemon:${serverName}] Idle timeout reached, shutting down`);
932
+ await cleanup();
933
+ process.exit(0);
934
+ }, timeoutMs);
935
+ };
936
+ process.on("SIGTERM", async () => {
937
+ await cleanup();
938
+ process.exit(0);
939
+ });
940
+ process.on("SIGINT", async () => {
941
+ await cleanup();
942
+ process.exit(0);
943
+ });
944
+ const socketDir = getSocketDir();
945
+ if (!existsSync2(socketDir)) {
946
+ mkdirSync(socketDir, { recursive: true, mode: 448 });
947
+ }
948
+ removeSocketFile(serverName);
949
+ removeReadyFile(serverName);
950
+ writePidFile(serverName, configHash);
951
+ try {
952
+ debug(`[daemon:${serverName}] Connecting to MCP server...`);
953
+ mcpClient = await connectToServer(serverName, config);
954
+ debug(`[daemon:${serverName}] Connected to MCP server`);
955
+ } catch (error) {
956
+ console.error(
957
+ `[daemon:${serverName}] Failed to connect:`,
958
+ error.message
959
+ );
960
+ await cleanup();
961
+ process.exit(1);
962
+ }
963
+ const handleRequest = async (data) => {
964
+ resetIdleTimer();
965
+ let request;
966
+ try {
967
+ request = JSON.parse(data.toString());
968
+ } catch {
969
+ return {
970
+ id: "unknown",
971
+ success: false,
972
+ error: { code: "INVALID_REQUEST", message: "Invalid JSON" }
973
+ };
974
+ }
975
+ debug(`[daemon:${serverName}] Request: ${request.type} (${request.id})`);
976
+ if (!mcpClient) {
977
+ return {
978
+ id: request.id,
979
+ success: false,
980
+ error: { code: "NOT_CONNECTED", message: "MCP client not connected" }
981
+ };
982
+ }
983
+ try {
984
+ switch (request.type) {
985
+ case "ping":
986
+ return { id: request.id, success: true, data: "pong" };
987
+ case "listTools": {
988
+ const tools = await listTools(mcpClient.client);
989
+ return { id: request.id, success: true, data: tools };
990
+ }
991
+ case "callTool": {
992
+ if (!request.toolName) {
993
+ return {
994
+ id: request.id,
995
+ success: false,
996
+ error: { code: "MISSING_TOOL", message: "toolName required" }
997
+ };
998
+ }
999
+ const result = await callTool(
1000
+ mcpClient.client,
1001
+ request.toolName,
1002
+ request.args ?? {}
1003
+ );
1004
+ return { id: request.id, success: true, data: result };
1005
+ }
1006
+ case "getInstructions": {
1007
+ const instructions = mcpClient.client.getInstructions();
1008
+ return { id: request.id, success: true, data: instructions };
1009
+ }
1010
+ case "close":
1011
+ setTimeout(async () => {
1012
+ await cleanup();
1013
+ process.exit(0);
1014
+ }, 100);
1015
+ return { id: request.id, success: true, data: "closing" };
1016
+ default:
1017
+ return {
1018
+ id: request.id,
1019
+ success: false,
1020
+ error: {
1021
+ code: "UNKNOWN_TYPE",
1022
+ message: `Unknown request type: ${request.type}`
1023
+ }
1024
+ };
1025
+ }
1026
+ } catch (error) {
1027
+ const err = error;
1028
+ return {
1029
+ id: request.id,
1030
+ success: false,
1031
+ error: { code: "EXECUTION_ERROR", message: err.message }
1032
+ };
1033
+ }
1034
+ };
1035
+ await new Promise((resolve4, reject) => {
1036
+ server = createServer((socket) => {
1037
+ activeConnections.add(socket);
1038
+ debug(`[daemon:${serverName}] Client connected`);
1039
+ let buffer = "";
1040
+ socket.on("data", async (data) => {
1041
+ buffer += data.toString();
1042
+ const lines = buffer.split("\n");
1043
+ buffer = lines.pop() ?? "";
1044
+ for (const line of lines) {
1045
+ if (!line.trim()) continue;
1046
+ const response = await handleRequest(Buffer.from(line));
1047
+ socket.write(`${JSON.stringify(response)}
1048
+ `);
1049
+ }
1050
+ });
1051
+ socket.on("close", () => {
1052
+ activeConnections.delete(socket);
1053
+ debug(`[daemon:${serverName}] Client disconnected`);
1054
+ });
1055
+ socket.on("error", (error) => {
1056
+ debug(`[daemon:${serverName}] Socket error: ${error.message}`);
1057
+ activeConnections.delete(socket);
1058
+ });
1059
+ });
1060
+ server.on("error", reject);
1061
+ server.listen(socketPath, () => {
1062
+ debug(`[daemon:${serverName}] Listening on ${socketPath}`);
1063
+ resetIdleTimer();
1064
+ writeFileSync(getReadyPath(), String(process.pid), {
1065
+ mode: 384
1066
+ });
1067
+ resolve4();
1068
+ });
1069
+ }).catch(async (error) => {
1070
+ console.error(
1071
+ `[daemon:${serverName}] Failed to start socket server:`,
1072
+ error.message
1073
+ );
1074
+ await cleanup();
1075
+ process.exit(1);
1076
+ });
1077
+ }
1078
+ if (process.argv[2] === "--daemon") {
1079
+ const serverName = process.argv[3];
1080
+ const configJson = process.argv[4];
1081
+ if (!serverName || !configJson) {
1082
+ console.error("Usage: daemon.ts --daemon <serverName> <configJson>");
1083
+ process.exit(1);
1084
+ }
1085
+ let config;
1086
+ try {
1087
+ config = JSON.parse(configJson);
1088
+ } catch {
1089
+ console.error("Invalid config JSON");
1090
+ process.exit(1);
1091
+ }
1092
+ runDaemon(serverName, config).catch((error) => {
1093
+ console.error("Daemon failed:", error);
1094
+ process.exit(1);
1095
+ });
1096
+ }
1097
+
1098
+ // src/daemon-client.ts
1099
+ function generateRequestId() {
1100
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1101
+ }
1102
+ async function sendRequest(socketPath, request) {
1103
+ return new Promise((resolve4, reject) => {
1104
+ const socket = createConnection(socketPath);
1105
+ let settled = false;
1106
+ const settle = (fn) => {
1107
+ if (!settled) {
1108
+ settled = true;
1109
+ fn();
1110
+ }
1111
+ };
1112
+ const timer = setTimeout(() => {
1113
+ socket.destroy();
1114
+ settle(() => reject(new Error("Daemon request timeout")));
1115
+ }, 5e3);
1116
+ socket.on("connect", () => {
1117
+ socket.write(`${JSON.stringify(request)}
1118
+ `);
1119
+ });
1120
+ let buffer = "";
1121
+ socket.on("data", (data) => {
1122
+ buffer += data.toString();
1123
+ const newlineIdx = buffer.indexOf("\n");
1124
+ if (newlineIdx === -1) return;
1125
+ const line = buffer.slice(0, newlineIdx);
1126
+ buffer = buffer.slice(newlineIdx + 1);
1127
+ try {
1128
+ const response = JSON.parse(line);
1129
+ clearTimeout(timer);
1130
+ socket.end();
1131
+ settle(() => resolve4(response));
1132
+ } catch {
1133
+ clearTimeout(timer);
1134
+ socket.end();
1135
+ settle(() => reject(new Error("Invalid response from daemon")));
1136
+ }
1137
+ });
1138
+ socket.on("error", (err) => {
1139
+ clearTimeout(timer);
1140
+ settle(() => reject(err));
1141
+ });
1142
+ socket.on("close", () => {
1143
+ clearTimeout(timer);
1144
+ });
1145
+ });
1146
+ }
1147
+ function isDaemonValid(serverName, config) {
1148
+ const socketPath = getSocketPath();
1149
+ const pidInfo = readPidFile(serverName);
1150
+ if (!pidInfo) {
1151
+ debug(`[daemon-client] No PID file for ${serverName}`);
1152
+ return false;
1153
+ }
1154
+ if (!isProcessRunning(pidInfo.pid)) {
1155
+ debug(`[daemon-client] Process ${pidInfo.pid} not running, cleaning up`);
1156
+ removePidFile(serverName);
1157
+ removeSocketFile(serverName);
1158
+ return false;
1159
+ }
1160
+ const currentHash = getConfigHash(config);
1161
+ if (pidInfo.configHash !== currentHash) {
1162
+ debug(
1163
+ `[daemon-client] Config hash mismatch for ${serverName}, killing old daemon`
1164
+ );
1165
+ killProcess(pidInfo.pid);
1166
+ removePidFile(serverName);
1167
+ removeSocketFile(serverName);
1168
+ return false;
1169
+ }
1170
+ if (!existsSync3(socketPath)) {
1171
+ debug(`[daemon-client] Socket missing for ${serverName}, cleaning up`);
1172
+ killProcess(pidInfo.pid);
1173
+ removePidFile(serverName);
1174
+ return false;
1175
+ }
1176
+ return true;
1177
+ }
1178
+ async function spawnDaemon(serverName, config) {
1179
+ debug(`[daemon-client] Spawning daemon for ${serverName}`);
1180
+ const __dirname = join2(fileURLToPath(import.meta.url), "..");
1181
+ const daemonScript = join2(__dirname, "daemon.js");
1182
+ const configJson = JSON.stringify(config);
1183
+ const proc = spawn(
1184
+ "node",
1185
+ [daemonScript, "--daemon", serverName, configJson],
1186
+ {
1187
+ stdio: "ignore",
1188
+ env: { ...process.env, MCP_NO_OAUTH: "1" },
1189
+ detached: true
1190
+ }
1191
+ );
1192
+ proc.unref();
1193
+ const readyPath = getReadyPath();
1194
+ const pollIntervalMs = 100;
1195
+ const timeoutMs = 1e4;
1196
+ const deadline = Date.now() + timeoutMs;
1197
+ while (Date.now() < deadline) {
1198
+ if (existsSync3(readyPath)) {
1199
+ debug(`[daemon-client] Daemon ready for ${serverName}`);
1200
+ return true;
1201
+ }
1202
+ if (proc.pid === void 0 || !isProcessRunning(proc.pid)) {
1203
+ debug(`[daemon-client] Daemon process exited early for ${serverName}`);
1204
+ return false;
1205
+ }
1206
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1207
+ }
1208
+ debug(`[daemon-client] Daemon spawn timeout for ${serverName}`);
1209
+ return false;
1210
+ }
1211
+ async function getDaemonConnection(serverName, config) {
1212
+ const socketPath = getSocketPath();
1213
+ if (!isDaemonValid(serverName, config)) {
1214
+ const spawned = await spawnDaemon(serverName, config);
1215
+ if (!spawned) {
1216
+ debug(`[daemon-client] Failed to spawn daemon for ${serverName}`);
1217
+ return null;
1218
+ }
1219
+ await new Promise((r) => setTimeout(r, 100));
1220
+ }
1221
+ if (!existsSync3(socketPath)) {
1222
+ debug(`[daemon-client] Socket not found after spawn for ${serverName}`);
1223
+ return null;
1224
+ }
1225
+ try {
1226
+ const pingResponse = await sendRequest(socketPath, {
1227
+ id: generateRequestId(),
1228
+ type: "ping"
1229
+ });
1230
+ if (!pingResponse.success) {
1231
+ debug(`[daemon-client] Ping failed for ${serverName}`);
1232
+ return null;
1233
+ }
1234
+ } catch (error) {
1235
+ debug(
1236
+ `[daemon-client] Connection test failed for ${serverName}: ${error.message}`
1237
+ );
1238
+ return null;
1239
+ }
1240
+ debug(`[daemon-client] Connected to daemon for ${serverName}`);
1241
+ return {
1242
+ serverName,
1243
+ async listTools() {
1244
+ const response = await sendRequest(socketPath, {
1245
+ id: generateRequestId(),
1246
+ type: "listTools"
1247
+ });
1248
+ if (!response.success) {
1249
+ throw new Error(response.error?.message ?? "listTools failed");
1250
+ }
1251
+ return response.data;
1252
+ },
1253
+ async callTool(toolName, args) {
1254
+ const response = await sendRequest(socketPath, {
1255
+ id: generateRequestId(),
1256
+ type: "callTool",
1257
+ toolName,
1258
+ args
1259
+ });
1260
+ if (!response.success) {
1261
+ throw new Error(response.error?.message ?? "callTool failed");
1262
+ }
1263
+ return response.data;
1264
+ },
1265
+ async getInstructions() {
1266
+ const response = await sendRequest(socketPath, {
1267
+ id: generateRequestId(),
1268
+ type: "getInstructions"
1269
+ });
1270
+ if (!response.success) {
1271
+ throw new Error(response.error?.message ?? "getInstructions failed");
1272
+ }
1273
+ return response.data;
1274
+ },
1275
+ async close() {
1276
+ debug(`[daemon-client] Disconnecting from ${serverName} daemon`);
1277
+ }
1278
+ };
1279
+ }
1280
+ async function cleanupOrphanedDaemons() {
1281
+ const socketDir = getSocketDir();
1282
+ if (!existsSync3(socketDir)) {
1283
+ return;
1284
+ }
1285
+ try {
1286
+ const files = readdirSync(socketDir).filter((f) => f.endsWith(".pid"));
1287
+ for (const file of files) {
1288
+ const serverName = file.replace(".pid", "");
1289
+ const pidInfo = readPidFile(serverName);
1290
+ if (pidInfo && !isProcessRunning(pidInfo.pid)) {
1291
+ debug(`[daemon-client] Cleaning up orphaned daemon: ${serverName}`);
1292
+ removePidFile(serverName);
1293
+ removeSocketFile(serverName);
1294
+ removeReadyFile(serverName);
1295
+ }
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ }
1300
+
1301
+ // src/version.ts
1302
+ var VERSION = "0.0.9";
1303
+
1304
+ // src/client.ts
1305
+ function getRetryConfig() {
1306
+ const totalBudgetMs = getTimeoutMs();
1307
+ const maxRetries = getMaxRetries();
1308
+ const baseDelayMs = getRetryDelayMs();
1309
+ const retryBudgetMs = Math.max(0, totalBudgetMs - 5e3);
1310
+ return {
1311
+ maxRetries,
1312
+ baseDelayMs,
1313
+ maxDelayMs: Math.min(1e4, retryBudgetMs / 2),
1314
+ totalBudgetMs
1315
+ };
1316
+ }
1317
+ function isTransientError(error) {
1318
+ const nodeError = error;
1319
+ if (nodeError.code) {
1320
+ const transientCodes = [
1321
+ "ECONNREFUSED",
1322
+ "ECONNRESET",
1323
+ "ETIMEDOUT",
1324
+ "ENOTFOUND",
1325
+ "EPIPE",
1326
+ "ENETUNREACH",
1327
+ "EHOSTUNREACH",
1328
+ "EAI_AGAIN"
1329
+ ];
1330
+ if (transientCodes.includes(nodeError.code)) {
1331
+ return true;
1332
+ }
1333
+ }
1334
+ const message = error.message;
1335
+ if (/^(502|503|504|429)\b/.test(message)) return true;
1336
+ if (/\b(http|status(\s+code)?)\s*(502|503|504|429)\b/i.test(message))
1337
+ return true;
1338
+ if (/\b(502|503|504|429)\s+(bad gateway|service unavailable|gateway timeout|too many requests)/i.test(
1339
+ message
1340
+ ))
1341
+ return true;
1342
+ if (/network\s*(error|fail|unavailable|timeout)/i.test(message)) return true;
1343
+ if (/connection\s*(reset|refused|timeout)/i.test(message)) return true;
1344
+ if (/\btimeout\b/i.test(message)) return true;
1345
+ return false;
1346
+ }
1347
+ function calculateDelay(attempt, config) {
1348
+ const exponentialDelay = config.baseDelayMs * 2 ** attempt;
1349
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
1350
+ const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
1351
+ return Math.round(cappedDelay + jitter);
1352
+ }
1353
+ function sleep(ms) {
1354
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1355
+ }
1356
+ async function withRetry(fn, operationName, config = getRetryConfig()) {
1357
+ let lastError;
1358
+ const startTime = Date.now();
1359
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
1360
+ const elapsed = Date.now() - startTime;
1361
+ if (elapsed >= config.totalBudgetMs) {
1362
+ debug(`${operationName}: timeout budget exhausted after ${elapsed}ms`);
1363
+ break;
1364
+ }
1365
+ try {
1366
+ return await fn();
1367
+ } catch (error) {
1368
+ lastError = error;
1369
+ const remainingBudget = config.totalBudgetMs - (Date.now() - startTime);
1370
+ const shouldRetry = attempt < config.maxRetries && isTransientError(lastError) && remainingBudget > 1e3;
1371
+ if (shouldRetry) {
1372
+ const delay = Math.min(
1373
+ calculateDelay(attempt, config),
1374
+ remainingBudget - 1e3
1375
+ );
1376
+ debug(
1377
+ `${operationName} failed (attempt ${attempt + 1}/${config.maxRetries + 1}): ${lastError.message}. Retrying in ${delay}ms...`
1378
+ );
1379
+ await sleep(delay);
1380
+ } else {
1381
+ throw lastError;
1382
+ }
1383
+ }
1384
+ }
1385
+ throw lastError;
1386
+ }
1387
+ async function safeClose(close) {
1388
+ try {
1389
+ await close();
1390
+ } catch (err) {
1391
+ debug(`Failed to close connection: ${err.message}`);
1392
+ }
1393
+ }
1394
+ async function connectToServer(serverName, config) {
1395
+ if (isHttpServer(config)) {
1396
+ return connectToHttpServer(serverName, config);
1397
+ }
1398
+ const stderrChunks = [];
1399
+ return withRetry(async () => {
1400
+ const client = new Client(
1401
+ {
1402
+ name: "mcp-s-cli",
1403
+ version: VERSION
1404
+ },
1405
+ {
1406
+ capabilities: {}
1407
+ }
1408
+ );
1409
+ const transport = createStdioTransport(config);
1410
+ const stderrStream = transport.stderr;
1411
+ if (stderrStream) {
1412
+ stderrStream.on("data", (chunk) => {
1413
+ const text2 = chunk.toString();
1414
+ stderrChunks.push(text2);
1415
+ process.stderr.write(`[${serverName}] ${text2}`);
1416
+ });
1417
+ }
1418
+ try {
1419
+ await client.connect(transport);
1420
+ } catch (error) {
1421
+ const stderrOutput = stderrChunks.join("").trim();
1422
+ if (stderrOutput) {
1423
+ const err = error;
1424
+ err.message = `${err.message}
1425
+
1426
+ Server stderr:
1427
+ ${stderrOutput}`;
1428
+ }
1429
+ throw error;
1430
+ }
1431
+ const stderrStream2 = transport.stderr;
1432
+ if (stderrStream2) {
1433
+ stderrStream2.on("data", (chunk) => {
1434
+ process.stderr.write(chunk);
1435
+ });
1436
+ }
1437
+ return {
1438
+ client,
1439
+ close: async () => {
1440
+ await client.close();
1441
+ }
1442
+ };
1443
+ }, `connect to ${serverName}`);
1444
+ }
1445
+ async function connectToHttpServer(serverName, config) {
1446
+ const oauthEnabled = process.env.MCP_NO_OAUTH !== "1";
1447
+ return withRetry(async () => {
1448
+ const configuredAuth = config.headers?.Authorization || config.headers?.authorization;
1449
+ let headers = { ...config.headers };
1450
+ if (!configuredAuth) {
1451
+ const stored = await getStoredTokens(config.url);
1452
+ if (stored) {
1453
+ headers = {
1454
+ ...headers,
1455
+ Authorization: `Bearer ${stored.access_token}`
1456
+ };
1457
+ }
1458
+ }
1459
+ return attemptHttpConnect(serverName, config, headers, oauthEnabled);
1460
+ }, `connect to ${serverName}`);
1461
+ }
1462
+ async function attemptHttpConnect(serverName, config, headers, oauthEnabled = true) {
1463
+ const hasAuth = Boolean(headers.Authorization || headers.authorization);
1464
+ let effectiveHeaders = headers;
1465
+ let oauthAlreadyRan = false;
1466
+ if (oauthEnabled && !hasAuth) {
1467
+ const probeTokens = await probeAndOAuth(serverName, config, headers);
1468
+ if (probeTokens) {
1469
+ effectiveHeaders = {
1470
+ ...headers,
1471
+ Authorization: `Bearer ${probeTokens.access_token}`
1472
+ };
1473
+ oauthAlreadyRan = true;
1474
+ }
1475
+ }
1476
+ const client = new Client(
1477
+ { name: "mcp-s-cli", version: VERSION },
1478
+ { capabilities: {} }
1479
+ );
1480
+ const transport = new StreamableHTTPClientTransport(new URL(config.url), {
1481
+ requestInit: { headers: effectiveHeaders }
1482
+ });
1483
+ try {
1484
+ await client.connect(transport);
1485
+ return {
1486
+ client,
1487
+ close: async () => {
1488
+ await client.close();
1489
+ }
1490
+ };
1491
+ } catch (connectError) {
1492
+ if (oauthAlreadyRan) {
1493
+ throw connectError;
1494
+ }
1495
+ const errMsg = connectError.message ?? "";
1496
+ const isAuthError = errMsg.includes("401") || errMsg.toLowerCase().includes("unauthorized") || errMsg.includes("invalid_token") || errMsg.toLowerCase().includes("missing authorization") || errMsg.toLowerCase().includes("authentication") || errMsg.toLowerCase().includes("access denied");
1497
+ if (!isAuthError || !oauthEnabled) {
1498
+ throw connectError;
1499
+ }
1500
+ debug(`Auth error from ${serverName}, probing for OAuth metadata`);
1501
+ const retryTokens = await probeAndOAuth(
1502
+ serverName,
1503
+ config,
1504
+ effectiveHeaders,
1505
+ true
1506
+ );
1507
+ if (!retryTokens) {
1508
+ throw connectError;
1509
+ }
1510
+ const newHeaders = {
1511
+ ...effectiveHeaders,
1512
+ Authorization: `Bearer ${retryTokens.access_token}`
1513
+ };
1514
+ const newClient = new Client(
1515
+ { name: "mcp-s-cli", version: VERSION },
1516
+ { capabilities: {} }
1517
+ );
1518
+ const newTransport = new StreamableHTTPClientTransport(
1519
+ new URL(config.url),
1520
+ { requestInit: { headers: newHeaders } }
1521
+ );
1522
+ await newClient.connect(newTransport);
1523
+ return {
1524
+ client: newClient,
1525
+ close: async () => {
1526
+ await newClient.close();
1527
+ }
1528
+ };
1529
+ }
1530
+ }
1531
+ async function probeAndOAuth(serverName, config, _headers, force = false) {
1532
+ debug(`Probing ${serverName} for OAuth metadata`);
1533
+ let probeResponse;
1534
+ try {
1535
+ probeResponse = await fetch(config.url, {
1536
+ signal: AbortSignal.timeout(1e4)
1537
+ });
1538
+ } catch {
1539
+ return null;
1540
+ }
1541
+ if (!force && probeResponse.status !== 401) {
1542
+ return null;
1543
+ }
1544
+ const wwwAuth = probeResponse.headers.get("WWW-Authenticate");
1545
+ if (!wwwAuth) {
1546
+ return null;
1547
+ }
1548
+ const challenge = parseWWWAuthenticateHeader(wwwAuth);
1549
+ if (!challenge.resource_metadata) {
1550
+ return null;
1551
+ }
1552
+ const resourceMetadata = await fetchProtectedResourceMetadata(
1553
+ challenge.resource_metadata
1554
+ );
1555
+ if (!resourceMetadata?.authorization_servers?.[0]) {
1556
+ return null;
1557
+ }
1558
+ const newTokens = await performOAuthFlow(
1559
+ resourceMetadata.authorization_servers[0],
1560
+ resourceMetadata.resource
1561
+ );
1562
+ if (newTokens) {
1563
+ await storeTokens(config.url, newTokens);
1564
+ }
1565
+ return newTokens;
1566
+ }
1567
+ function createStdioTransport(config) {
1568
+ const mergedEnv = {};
1569
+ for (const [key, value] of Object.entries(process.env)) {
1570
+ if (value !== void 0) {
1571
+ mergedEnv[key] = value;
1572
+ }
1573
+ }
1574
+ if (config.env) {
1575
+ Object.assign(mergedEnv, config.env);
1576
+ }
1577
+ return new StdioClientTransport({
1578
+ command: config.command,
1579
+ args: config.args,
1580
+ env: mergedEnv,
1581
+ cwd: config.cwd,
1582
+ stderr: "pipe"
1583
+ // Capture stderr for better error messages
1584
+ });
1585
+ }
1586
+ async function listTools(client) {
1587
+ return withRetry(async () => {
1588
+ const result = await client.listTools();
1589
+ return result.tools.map((tool) => ({
1590
+ name: tool.name,
1591
+ description: tool.description,
1592
+ inputSchema: tool.inputSchema
1593
+ }));
1594
+ }, "list tools");
1595
+ }
1596
+ async function callTool(client, toolName, args) {
1597
+ return withRetry(async () => {
1598
+ const result = await client.callTool(
1599
+ {
1600
+ name: toolName,
1601
+ arguments: args
1602
+ },
1603
+ void 0,
1604
+ { timeout: getTimeoutMs() }
1605
+ );
1606
+ return result;
1607
+ }, `call tool ${toolName}`);
1608
+ }
1609
+ async function getConnection(serverName, config) {
1610
+ await cleanupOrphanedDaemons();
1611
+ if (isDaemonEnabled()) {
1612
+ try {
1613
+ const daemonConn = await getDaemonConnection(serverName, config);
1614
+ if (daemonConn) {
1615
+ debug(`Using daemon connection for ${serverName}`);
1616
+ return {
1617
+ async listTools() {
1618
+ const data = await daemonConn.listTools();
1619
+ const tools = data;
1620
+ return filterTools(tools, config);
1621
+ },
1622
+ async callTool(toolName, args) {
1623
+ if (!isToolAllowed(toolName, config)) {
1624
+ throw new Error(
1625
+ `Tool "${toolName}" is disabled by configuration`
1626
+ );
1627
+ }
1628
+ return daemonConn.callTool(toolName, args);
1629
+ },
1630
+ async getInstructions() {
1631
+ return daemonConn.getInstructions();
1632
+ },
1633
+ async close() {
1634
+ await daemonConn.close();
1635
+ },
1636
+ isDaemon: true
1637
+ };
1638
+ }
1639
+ } catch (err) {
1640
+ debug(
1641
+ `Daemon connection failed for ${serverName}: ${err.message}, falling back to direct`
1642
+ );
1643
+ }
1644
+ }
1645
+ debug(`Using direct connection for ${serverName}`);
1646
+ const { client, close } = await connectToServer(serverName, config);
1647
+ return {
1648
+ async listTools() {
1649
+ const tools = await listTools(client);
1650
+ return filterTools(tools, config);
1651
+ },
1652
+ async callTool(toolName, args) {
1653
+ if (!isToolAllowed(toolName, config)) {
1654
+ throw new Error(`Tool "${toolName}" is disabled by configuration`);
1655
+ }
1656
+ return callTool(client, toolName, args);
1657
+ },
1658
+ async getInstructions() {
1659
+ return client.getInstructions();
1660
+ },
1661
+ async close() {
1662
+ await close();
1663
+ },
1664
+ isDaemon: false
1665
+ };
1666
+ }
1667
+
1668
+ // src/output.ts
1669
+ var colors = {
1670
+ reset: "\x1B[0m",
1671
+ bold: "\x1B[1m",
1672
+ dim: "\x1B[2m",
1673
+ cyan: "\x1B[36m",
1674
+ green: "\x1B[32m",
1675
+ yellow: "\x1B[33m",
1676
+ blue: "\x1B[34m",
1677
+ magenta: "\x1B[35m"
1678
+ };
1679
+ function shouldColorize() {
1680
+ return process.stdout.isTTY && !process.env.NO_COLOR;
1681
+ }
1682
+ function color(text2, colorCode) {
1683
+ if (!shouldColorize()) return text2;
1684
+ return `${colorCode}${text2}${colors.reset}`;
1685
+ }
1686
+ function formatServerList(servers, withDescriptions) {
1687
+ const lines = [];
1688
+ for (const server of servers) {
1689
+ lines.push(color(server.name, colors.bold + colors.cyan));
1690
+ if (server.instructions) {
1691
+ const instructionLines = server.instructions.split("\n");
1692
+ const firstLine = instructionLines[0].slice(0, 100);
1693
+ const suffix = instructionLines.length > 1 || instructionLines[0].length > 100 ? "..." : "";
1694
+ lines.push(
1695
+ ` ${color(`Instructions: ${firstLine}${suffix}`, colors.dim)}`
1696
+ );
1697
+ }
1698
+ for (const tool of server.tools) {
1699
+ if (withDescriptions && tool.description) {
1700
+ lines.push(` \u2022 ${tool.name} - ${color(tool.description, colors.dim)}`);
1701
+ } else {
1702
+ lines.push(` \u2022 ${tool.name}`);
1703
+ }
1704
+ }
1705
+ lines.push("");
1706
+ }
1707
+ return lines.join("\n").trimEnd();
1708
+ }
1709
+ function formatSearchResults(results, withDescriptions) {
1710
+ const lines = [];
1711
+ for (const result of results) {
1712
+ const server = color(result.server, colors.cyan);
1713
+ const tool = color(result.tool.name, colors.green);
1714
+ if (withDescriptions && result.tool.description) {
1715
+ lines.push(
1716
+ `${server} ${tool} ${color(result.tool.description, colors.dim)}`
1717
+ );
1718
+ } else {
1719
+ lines.push(`${server} ${tool}`);
1720
+ }
1721
+ }
1722
+ return lines.join("\n");
1723
+ }
1724
+ function formatServerDetails(serverName, config, tools, withDescriptions = false, instructions) {
1725
+ const lines = [];
1726
+ lines.push(
1727
+ `${color("Server:", colors.bold)} ${color(serverName, colors.cyan)}`
1728
+ );
1729
+ if (isHttpServer(config)) {
1730
+ lines.push(`${color("Transport:", colors.bold)} HTTP`);
1731
+ lines.push(`${color("URL:", colors.bold)} ${config.url}`);
1732
+ } else {
1733
+ lines.push(`${color("Transport:", colors.bold)} stdio`);
1734
+ lines.push(
1735
+ `${color("Command:", colors.bold)} ${config.command} ${(config.args || []).join(" ")}`
1736
+ );
1737
+ }
1738
+ if (instructions) {
1739
+ lines.push("");
1740
+ lines.push(`${color("Instructions:", colors.bold)}`);
1741
+ const indentedInstructions = instructions.split("\n").map((line) => ` ${line}`).join("\n");
1742
+ lines.push(indentedInstructions);
1743
+ }
1744
+ lines.push("");
1745
+ lines.push(`${color(`Tools (${tools.length}):`, colors.bold)}`);
1746
+ for (const tool of tools) {
1747
+ lines.push(` ${color(tool.name, colors.green)}`);
1748
+ if (withDescriptions && tool.description) {
1749
+ lines.push(` ${color(tool.description, colors.dim)}`);
1750
+ }
1751
+ const schema = tool.inputSchema;
1752
+ if (schema.properties) {
1753
+ lines.push(` ${color("Parameters:", colors.yellow)}`);
1754
+ for (const [name, prop] of Object.entries(schema.properties)) {
1755
+ const required = schema.required?.includes(name) ? "required" : "optional";
1756
+ const type = prop.type || "any";
1757
+ const desc = withDescriptions && prop.description ? ` - ${prop.description}` : "";
1758
+ lines.push(` \u2022 ${name} (${type}, ${required})${desc}`);
1759
+ }
1760
+ }
1761
+ lines.push("");
1762
+ }
1763
+ return lines.join("\n").trimEnd();
1764
+ }
1765
+ function formatToolSchema(serverName, tool) {
1766
+ const lines = [];
1767
+ lines.push(
1768
+ `${color("Tool:", colors.bold)} ${color(tool.name, colors.green)}`
1769
+ );
1770
+ lines.push(
1771
+ `${color("Server:", colors.bold)} ${color(serverName, colors.cyan)}`
1772
+ );
1773
+ lines.push("");
1774
+ if (tool.description) {
1775
+ lines.push(`${color("Description:", colors.bold)}`);
1776
+ lines.push(` ${tool.description}`);
1777
+ lines.push("");
1778
+ }
1779
+ lines.push(`${color("Input Schema:", colors.bold)}`);
1780
+ lines.push(JSON.stringify(tool.inputSchema, null, 2));
1781
+ return lines.join("\n");
1782
+ }
1783
+ function formatToolResult(result) {
1784
+ if (typeof result === "object" && result !== null) {
1785
+ const r = result;
1786
+ if (r.content && Array.isArray(r.content)) {
1787
+ const textParts = r.content.filter((c2) => c2.type === "text" && c2.text).map((c2) => c2.text);
1788
+ if (textParts.length > 0) {
1789
+ return textParts.join("\n");
1790
+ }
1791
+ }
1792
+ }
1793
+ return JSON.stringify(result, null, 2);
1794
+ }
1795
+
1796
+ // src/commands/call.ts
1797
+ async function parseArgs(argsString) {
1798
+ let jsonString;
1799
+ if (argsString) {
1800
+ jsonString = argsString;
1801
+ } else if (!process.stdin.isTTY) {
1802
+ const timeoutMs = getTimeoutMs();
1803
+ const chunks = [];
1804
+ let timeoutId;
1805
+ const readPromise = (async () => {
1806
+ for await (const chunk of process.stdin) {
1807
+ chunks.push(chunk);
1808
+ }
1809
+ return Buffer.concat(chunks).toString("utf-8").trim();
1810
+ })();
1811
+ const timeoutPromise = new Promise((_, reject) => {
1812
+ timeoutId = setTimeout(
1813
+ () => reject(new Error(`stdin read timed out after ${timeoutMs}ms`)),
1814
+ timeoutMs
1815
+ );
1816
+ });
1817
+ try {
1818
+ jsonString = await Promise.race([readPromise, timeoutPromise]);
1819
+ } finally {
1820
+ if (timeoutId) clearTimeout(timeoutId);
1821
+ }
1822
+ } else {
1823
+ return {};
1824
+ }
1825
+ if (!jsonString) {
1826
+ return {};
1827
+ }
1828
+ try {
1829
+ return JSON.parse(jsonString);
1830
+ } catch (e) {
1831
+ throw new Error(
1832
+ formatCliError(invalidJsonArgsError(jsonString, e.message))
1833
+ );
1834
+ }
1835
+ }
1836
+ async function callCommand(options) {
1837
+ let loaded;
1838
+ try {
1839
+ loaded = await loadConfig(options.configPath);
1840
+ } catch (error) {
1841
+ console.error(error.message);
1842
+ process.exit(1 /* CLIENT_ERROR */);
1843
+ }
1844
+ const { serverConfig } = loaded;
1845
+ const serverLabel = "server";
1846
+ if (options.args !== void 0) {
1847
+ try {
1848
+ await parseArgs(options.args);
1849
+ } catch (error) {
1850
+ console.error(error.message);
1851
+ process.exit(1 /* CLIENT_ERROR */);
1852
+ }
1853
+ }
1854
+ let args;
1855
+ try {
1856
+ args = await parseArgs(options.args);
1857
+ } catch (error) {
1858
+ console.error(error.message);
1859
+ process.exit(1 /* CLIENT_ERROR */);
1860
+ }
1861
+ let connection;
1862
+ try {
1863
+ connection = await getConnection(serverLabel, serverConfig);
1864
+ } catch (error) {
1865
+ console.error(
1866
+ formatCliError(
1867
+ serverConnectionError(serverLabel, error.message)
1868
+ )
1869
+ );
1870
+ process.exit(3 /* NETWORK_ERROR */);
1871
+ }
1872
+ try {
1873
+ const result = await connection.callTool(options.tool, args);
1874
+ console.log(formatToolResult(result));
1875
+ } catch (error) {
1876
+ let availableTools;
1877
+ try {
1878
+ const tools = await connection.listTools();
1879
+ availableTools = tools.map((t) => t.name);
1880
+ } catch {
1881
+ }
1882
+ const errMsg = error.message;
1883
+ if (errMsg.includes("not found") || errMsg.includes("unknown tool")) {
1884
+ console.error(
1885
+ formatCliError(
1886
+ toolNotFoundError(options.tool, serverLabel, availableTools)
1887
+ )
1888
+ );
1889
+ } else {
1890
+ console.error(
1891
+ formatCliError(toolExecutionError(options.tool, serverLabel, errMsg))
1892
+ );
1893
+ }
1894
+ process.exit(2 /* SERVER_ERROR */);
1895
+ } finally {
1896
+ await safeClose(connection.close);
1897
+ }
1898
+ }
1899
+
1900
+ // src/commands/clear.ts
1901
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
1902
+ import { homedir as homedir2 } from "os";
1903
+ import { dirname as dirname2, join as join3 } from "path";
1904
+ var GLOBAL_CONFIG_PATH = join3(
1905
+ homedir2(),
1906
+ ".config",
1907
+ "mcp-s-cli",
1908
+ "config.json"
1909
+ );
1910
+ var AUTH_PATH = join3(homedir2(), ".config", "mcp-s-cli", "auth.json");
1911
+ async function clearCommand() {
1912
+ mkdirSync2(dirname2(GLOBAL_CONFIG_PATH), { recursive: true });
1913
+ writeFileSync2(
1914
+ GLOBAL_CONFIG_PATH,
1915
+ `${JSON.stringify({}, null, 2)}
1916
+ `,
1917
+ "utf-8"
1918
+ );
1919
+ console.log(`Cleared server config at ${GLOBAL_CONFIG_PATH}`);
1920
+ }
1921
+ async function clearAuthCommand() {
1922
+ mkdirSync2(dirname2(AUTH_PATH), { recursive: true });
1923
+ writeFileSync2(AUTH_PATH, `${JSON.stringify({}, null, 2)}
1924
+ `, "utf-8");
1925
+ console.log(`Cleared auth data from ${AUTH_PATH}`);
1926
+ }
1927
+ var HISTORY_PATH = join3(homedir2(), ".config", "mcp-s-cli", "history.jsonl");
1928
+ function clearHistoryCommand() {
1929
+ if (!existsSync4(HISTORY_PATH)) {
1930
+ console.log("No history file found.");
1931
+ return;
1932
+ }
1933
+ rmSync(HISTORY_PATH);
1934
+ console.log(`Cleared history from ${HISTORY_PATH}`);
1935
+ }
1936
+
1937
+ // src/commands/grep.ts
1938
+ function globToRegex(pattern) {
1939
+ let escaped = "";
1940
+ let i = 0;
1941
+ while (i < pattern.length) {
1942
+ const char = pattern[i];
1943
+ if (char === "*" && pattern[i + 1] === "*") {
1944
+ escaped += ".*";
1945
+ i += 2;
1946
+ while (pattern[i] === "*") {
1947
+ i++;
1948
+ }
1949
+ } else if (char === "*") {
1950
+ escaped += "[^/]*";
1951
+ i += 1;
1952
+ } else if (char === "?") {
1953
+ escaped += "[^/]";
1954
+ i += 1;
1955
+ } else if ("[.+^${}()|\\]".includes(char)) {
1956
+ escaped += `\\${char}`;
1957
+ i += 1;
1958
+ } else {
1959
+ escaped += char;
1960
+ i += 1;
1961
+ }
1962
+ }
1963
+ return new RegExp(`^${escaped}$`, "i");
1964
+ }
1965
+ async function grepCommand(options) {
1966
+ let loaded;
1967
+ try {
1968
+ loaded = await loadConfig(options.configPath);
1969
+ } catch (error) {
1970
+ console.error(error.message);
1971
+ process.exit(1 /* CLIENT_ERROR */);
1972
+ }
1973
+ const { serverConfig } = loaded;
1974
+ const serverLabel = "server";
1975
+ const pattern = globToRegex(options.pattern);
1976
+ debug(`Searching for pattern "${options.pattern}"`);
1977
+ let connection = null;
1978
+ const allResults = [];
1979
+ let searchError;
1980
+ try {
1981
+ connection = await getConnection(serverLabel, serverConfig);
1982
+ const tools = await connection.listTools();
1983
+ for (const tool of tools) {
1984
+ if (pattern.test(tool.name)) {
1985
+ allResults.push({ server: serverLabel, tool });
1986
+ }
1987
+ }
1988
+ debug(`found ${allResults.length} matches`);
1989
+ } catch (error) {
1990
+ searchError = error.message;
1991
+ debug(`connection failed - ${searchError}`);
1992
+ } finally {
1993
+ if (connection) {
1994
+ await safeClose(connection.close);
1995
+ }
1996
+ }
1997
+ if (searchError) {
1998
+ console.error(`Warning: Failed to connect to server: ${searchError}`);
1999
+ }
2000
+ if (allResults.length === 0) {
2001
+ console.log(`No tools found matching "${options.pattern}"`);
2002
+ console.log(" Tip: Pattern matches tool names only");
2003
+ console.log(` Tip: Use '*' for wildcards, e.g. '*file*' or 'read_*'`);
2004
+ console.log(` Tip: Run 'mcp-s-cli' to list all available tools`);
2005
+ return;
2006
+ }
2007
+ console.log(formatSearchResults(allResults, options.withDescriptions));
2008
+ }
2009
+
2010
+ // src/commands/info.ts
2011
+ async function infoCommand(options) {
2012
+ let loaded;
2013
+ try {
2014
+ loaded = await loadConfig(options.configPath);
2015
+ } catch (error) {
2016
+ console.error(error.message);
2017
+ process.exit(1 /* CLIENT_ERROR */);
2018
+ }
2019
+ const { serverConfig } = loaded;
2020
+ const serverLabel = "server";
2021
+ let connection;
2022
+ try {
2023
+ connection = await getConnection(serverLabel, serverConfig);
2024
+ } catch (error) {
2025
+ console.error(
2026
+ formatCliError(
2027
+ serverConnectionError(serverLabel, error.message)
2028
+ )
2029
+ );
2030
+ process.exit(3 /* NETWORK_ERROR */);
2031
+ }
2032
+ try {
2033
+ if (options.tool) {
2034
+ const tools = await connection.listTools();
2035
+ const tool = tools.find((t) => t.name === options.tool);
2036
+ if (!tool) {
2037
+ const availableTools = tools.map((t) => t.name);
2038
+ console.error(
2039
+ formatCliError(
2040
+ toolNotFoundError(options.tool, serverLabel, availableTools)
2041
+ )
2042
+ );
2043
+ process.exit(1 /* CLIENT_ERROR */);
2044
+ }
2045
+ console.log(formatToolSchema(serverLabel, tool));
2046
+ } else {
2047
+ const tools = await connection.listTools();
2048
+ const instructions = await connection.getInstructions();
2049
+ console.log(
2050
+ formatServerDetails(
2051
+ serverLabel,
2052
+ serverConfig,
2053
+ tools,
2054
+ options.withDescriptions,
2055
+ instructions
2056
+ )
2057
+ );
2058
+ }
2059
+ } finally {
2060
+ await safeClose(connection.close);
2061
+ }
2062
+ }
2063
+
2064
+ // src/commands/init.ts
2065
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2066
+ import { homedir as homedir4 } from "os";
2067
+ import { dirname as dirname4, join as join5 } from "path";
2068
+ import * as readline from "readline";
2069
+
2070
+ // src/commands/install-skill.ts
2071
+ import {
2072
+ existsSync as existsSync5,
2073
+ lstatSync,
2074
+ mkdirSync as mkdirSync3,
2075
+ readFileSync as readFileSync3,
2076
+ readlinkSync,
2077
+ rmSync as rmSync2,
2078
+ symlinkSync,
2079
+ writeFileSync as writeFileSync3
2080
+ } from "fs";
2081
+ import { homedir as homedir3, platform } from "os";
2082
+ import { dirname as dirname3, join as join4, normalize, relative, resolve as resolve2, sep } from "path";
2083
+ import { fileURLToPath as fileURLToPath2 } from "url";
2084
+ var home = homedir3();
2085
+ var AGENTS = {
2086
+ cursor: {
2087
+ displayName: "Cursor",
2088
+ globalSkillsDir: join4(home, ".cursor", "skills"),
2089
+ detect: () => existsSync5(join4(home, ".cursor"))
2090
+ },
2091
+ codex: {
2092
+ displayName: "Codex",
2093
+ globalSkillsDir: join4(
2094
+ process.env.CODEX_HOME?.trim() || join4(home, ".codex"),
2095
+ "skills"
2096
+ ),
2097
+ detect: () => existsSync5(process.env.CODEX_HOME?.trim() || join4(home, ".codex")) || existsSync5("/etc/codex")
2098
+ },
2099
+ "claude-code": {
2100
+ displayName: "Claude Code",
2101
+ globalSkillsDir: join4(
2102
+ process.env.CLAUDE_CONFIG_DIR?.trim() || join4(home, ".claude"),
2103
+ "skills"
2104
+ ),
2105
+ detect: () => existsSync5(
2106
+ process.env.CLAUDE_CONFIG_DIR?.trim() || join4(home, ".claude")
2107
+ )
2108
+ },
2109
+ windsurf: {
2110
+ displayName: "Windsurf",
2111
+ globalSkillsDir: join4(home, ".codeium", "windsurf", "skills"),
2112
+ detect: () => existsSync5(join4(home, ".codeium", "windsurf"))
2113
+ },
2114
+ "github-copilot": {
2115
+ displayName: "GitHub Copilot",
2116
+ globalSkillsDir: join4(home, ".copilot", "skills"),
2117
+ detect: () => existsSync5(join4(home, ".copilot"))
2118
+ },
2119
+ cline: {
2120
+ displayName: "Cline",
2121
+ globalSkillsDir: join4(home, ".cline", "skills"),
2122
+ detect: () => existsSync5(join4(home, ".cline"))
2123
+ },
2124
+ roo: {
2125
+ displayName: "Roo Code",
2126
+ globalSkillsDir: join4(home, ".roo", "skills"),
2127
+ detect: () => existsSync5(join4(home, ".roo"))
2128
+ },
2129
+ continue: {
2130
+ displayName: "Continue",
2131
+ globalSkillsDir: join4(home, ".continue", "skills"),
2132
+ detect: () => existsSync5(join4(home, ".continue")) || existsSync5(join4(process.cwd(), ".continue"))
2133
+ }
2134
+ };
2135
+ function getCanonicalSkillDir(skillName) {
2136
+ return join4(home, ".agents", "skills", skillName);
2137
+ }
2138
+ function getCanonicalSkillPath() {
2139
+ return getCanonicalSkillDir(SKILL_NAME);
2140
+ }
2141
+ function isPathSafe(base, target) {
2142
+ const normalizedBase = normalize(resolve2(base));
2143
+ const normalizedTarget = normalize(resolve2(target));
2144
+ return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
2145
+ }
2146
+ function createSymlinkSync(target, linkPath) {
2147
+ try {
2148
+ const resolvedTarget = resolve2(target);
2149
+ const resolvedLinkPath = resolve2(linkPath);
2150
+ if (resolvedTarget === resolvedLinkPath) return true;
2151
+ try {
2152
+ const stats = lstatSync(linkPath);
2153
+ if (stats.isSymbolicLink()) {
2154
+ const existing = readlinkSync(linkPath);
2155
+ const resolvedExisting = resolve2(dirname3(linkPath), existing);
2156
+ if (resolvedExisting === resolvedTarget) return true;
2157
+ rmSync2(linkPath);
2158
+ } else {
2159
+ rmSync2(linkPath, { recursive: true });
2160
+ }
2161
+ } catch (err) {
2162
+ if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") {
2163
+ try {
2164
+ rmSync2(linkPath, { force: true });
2165
+ } catch {
2166
+ }
2167
+ }
2168
+ }
2169
+ const linkDir = dirname3(linkPath);
2170
+ mkdirSync3(linkDir, { recursive: true });
2171
+ const relativePath = relative(linkDir, target);
2172
+ symlinkSync(
2173
+ relativePath,
2174
+ linkPath,
2175
+ platform() === "win32" ? "junction" : void 0
2176
+ );
2177
+ return true;
2178
+ } catch {
2179
+ return false;
2180
+ }
2181
+ }
2182
+ var SKILL_NAME = "mcp-s-cli";
2183
+ function getSkillMdContent() {
2184
+ const __filename = fileURLToPath2(import.meta.url);
2185
+ const __dirname = dirname3(__filename);
2186
+ const candidates = [
2187
+ join4(__dirname, "SKILL.md"),
2188
+ join4(__dirname, "..", "SKILL.md"),
2189
+ join4(__dirname, "..", "..", "SKILL.md")
2190
+ ];
2191
+ for (const candidate of candidates) {
2192
+ if (existsSync5(candidate)) {
2193
+ return readFileSync3(candidate, "utf-8");
2194
+ }
2195
+ }
2196
+ throw new Error(
2197
+ "Could not locate SKILL.md. Make sure to build the project first."
2198
+ );
2199
+ }
2200
+ function installSkill() {
2201
+ const content = getSkillMdContent();
2202
+ const canonicalDir = getCanonicalSkillDir(SKILL_NAME);
2203
+ if (!isPathSafe(join4(home, ".agents", "skills"), canonicalDir)) {
2204
+ throw new Error("Invalid skill name: potential path traversal detected");
2205
+ }
2206
+ rmSync2(canonicalDir, { recursive: true, force: true });
2207
+ mkdirSync3(canonicalDir, { recursive: true });
2208
+ writeFileSync3(join4(canonicalDir, "SKILL.md"), content, "utf-8");
2209
+ const result = {
2210
+ installed: [],
2211
+ skipped: [],
2212
+ failed: [],
2213
+ canonicalPath: canonicalDir
2214
+ };
2215
+ for (const [agentName, agent] of Object.entries(AGENTS)) {
2216
+ if (!agent.detect()) {
2217
+ result.skipped.push(agentName);
2218
+ continue;
2219
+ }
2220
+ const agentSkillDir = join4(agent.globalSkillsDir, SKILL_NAME);
2221
+ if (!isPathSafe(agent.globalSkillsDir, agentSkillDir)) {
2222
+ result.failed.push({
2223
+ agent: agentName,
2224
+ error: "Path traversal detected"
2225
+ });
2226
+ continue;
2227
+ }
2228
+ try {
2229
+ const symlinkOk = createSymlinkSync(canonicalDir, agentSkillDir);
2230
+ if (!symlinkOk) {
2231
+ rmSync2(agentSkillDir, { recursive: true, force: true });
2232
+ mkdirSync3(agentSkillDir, { recursive: true });
2233
+ writeFileSync3(join4(agentSkillDir, "SKILL.md"), content, "utf-8");
2234
+ }
2235
+ result.installed.push(agentName);
2236
+ } catch (err) {
2237
+ result.failed.push({
2238
+ agent: agentName,
2239
+ error: err instanceof Error ? err.message : String(err)
2240
+ });
2241
+ }
2242
+ }
2243
+ return result;
2244
+ }
2245
+ function clearSkill() {
2246
+ const result = {
2247
+ removed: [],
2248
+ skipped: [],
2249
+ failed: [],
2250
+ canonicalRemoved: false
2251
+ };
2252
+ for (const [agentName, agent] of Object.entries(AGENTS)) {
2253
+ const agentSkillDir = join4(agent.globalSkillsDir, SKILL_NAME);
2254
+ if (!existsSync5(agentSkillDir)) {
2255
+ result.skipped.push(agentName);
2256
+ continue;
2257
+ }
2258
+ try {
2259
+ rmSync2(agentSkillDir, { recursive: true, force: true });
2260
+ result.removed.push(agentName);
2261
+ } catch (err) {
2262
+ result.failed.push({
2263
+ agent: agentName,
2264
+ error: err instanceof Error ? err.message : String(err)
2265
+ });
2266
+ }
2267
+ }
2268
+ const canonicalDir = getCanonicalSkillDir(SKILL_NAME);
2269
+ if (existsSync5(canonicalDir)) {
2270
+ try {
2271
+ rmSync2(canonicalDir, { recursive: true, force: true });
2272
+ result.canonicalRemoved = true;
2273
+ } catch {
2274
+ }
2275
+ }
2276
+ return result;
2277
+ }
2278
+ function getSkillInstallInfo() {
2279
+ return Object.entries(AGENTS).filter(([, agent]) => agent.detect()).map(([agentName, agent]) => {
2280
+ const agentSkillDir = join4(agent.globalSkillsDir, SKILL_NAME);
2281
+ return {
2282
+ agent: agentName,
2283
+ displayName: agent.displayName,
2284
+ path: agentSkillDir,
2285
+ installed: existsSync5(join4(agentSkillDir, "SKILL.md"))
2286
+ };
2287
+ });
2288
+ }
2289
+
2290
+ // src/commands/login.ts
2291
+ async function loginCommand(options) {
2292
+ const { configPath } = options;
2293
+ const { serverConfig } = await loadConfig(configPath);
2294
+ if (!isHttpServer(serverConfig)) {
2295
+ throw new Error(
2296
+ "Current server is a stdio server. OAuth login is only supported for HTTP servers."
2297
+ );
2298
+ }
2299
+ const resourceMetadataUrl = `${new URL(serverConfig.url).origin}/.well-known/oauth-protected-resource`;
2300
+ const resourceMetadata = await fetchProtectedResourceMetadata(resourceMetadataUrl);
2301
+ if (!resourceMetadata?.authorization_servers?.[0]) {
2302
+ throw new Error(
2303
+ `Server does not appear to support OAuth (no authorization server found at ${resourceMetadataUrl}).`
2304
+ );
2305
+ }
2306
+ const authServerUrl = resourceMetadata.authorization_servers[0];
2307
+ const resourceUrl = resourceMetadata.resource;
2308
+ console.error(`Logging in to ${serverConfig.url}...`);
2309
+ const tokens = await performOAuthFlow(authServerUrl, resourceUrl);
2310
+ if (!tokens) {
2311
+ throw new Error("Login failed. OAuth flow did not return tokens.");
2312
+ }
2313
+ console.log(`Logged in to ${serverConfig.url} successfully.`);
2314
+ }
2315
+
2316
+ // src/commands/init.ts
2317
+ var A = {
2318
+ reset: "\x1B[0m",
2319
+ bold: "\x1B[1m",
2320
+ dim: "\x1B[2m",
2321
+ cyan: "\x1B[36m",
2322
+ green: "\x1B[32m",
2323
+ yellow: "\x1B[33m",
2324
+ red: "\x1B[31m",
2325
+ gray: "\x1B[90m",
2326
+ white: "\x1B[97m",
2327
+ bgCyan: "\x1B[46m",
2328
+ clearLine: "\x1B[2K\x1B[G",
2329
+ up: (n) => `\x1B[${n}A`
2330
+ };
2331
+ var c = (code, s) => `${code}${s}${A.reset}`;
2332
+ var BAR = c(A.gray, "\u2502");
2333
+ var BAR_END = c(A.gray, "\u2514");
2334
+ var CANCEL = /* @__PURE__ */ Symbol("cancel");
2335
+ function isCancel(v) {
2336
+ return v === CANCEL;
2337
+ }
2338
+ function intro(msg) {
2339
+ process.stdout.write(`
2340
+ ${c(A.bgCyan + A.bold, ` ${msg} `)}
2341
+ ${BAR}
2342
+ `);
2343
+ }
2344
+ function outro(msg) {
2345
+ process.stdout.write(`${BAR_END} ${c(A.green, msg)}
2346
+
2347
+ `);
2348
+ }
2349
+ function cancel(msg) {
2350
+ process.stdout.write(`${BAR_END} ${c(A.yellow, msg)}
2351
+
2352
+ `);
2353
+ }
2354
+ function readLine(prompt) {
2355
+ return new Promise((resolve4) => {
2356
+ const rl = readline.createInterface({
2357
+ input: process.stdin,
2358
+ output: process.stdout,
2359
+ terminal: false
2360
+ });
2361
+ process.stdout.write(prompt);
2362
+ rl.once("line", (line) => {
2363
+ rl.close();
2364
+ resolve4(line);
2365
+ });
2366
+ rl.once("SIGINT", () => {
2367
+ rl.close();
2368
+ process.stdout.write("\n");
2369
+ resolve4(CANCEL);
2370
+ });
2371
+ process.stdin.once("end", () => {
2372
+ rl.close();
2373
+ resolve4(CANCEL);
2374
+ });
2375
+ });
2376
+ }
2377
+ async function select(opts) {
2378
+ const { message, options, initialValue } = opts;
2379
+ const defaultIdx = Math.max(
2380
+ 0,
2381
+ options.findIndex((o) => o.value === initialValue)
2382
+ );
2383
+ process.stdout.write(`${BAR} ${c(A.bold + A.white, message)}
2384
+ `);
2385
+ options.forEach((o, i) => {
2386
+ const active = i === defaultIdx;
2387
+ const dot = active ? c(A.cyan, "\u25CF") : c(A.gray, "\u25CB");
2388
+ const label = active ? c(A.white, o.label) : c(A.dim, o.label);
2389
+ const hint = o.hint ? c(A.gray, ` (${o.hint})`) : "";
2390
+ process.stdout.write(`${BAR} ${dot} ${label}${hint}
2391
+ `);
2392
+ });
2393
+ if (process.stdin.isTTY) {
2394
+ let idx = defaultIdx;
2395
+ const render = () => {
2396
+ process.stdout.write(A.up(options.length));
2397
+ options.forEach((o, i) => {
2398
+ const active = i === idx;
2399
+ const dot = active ? c(A.cyan, "\u25CF") : c(A.gray, "\u25CB");
2400
+ const label = active ? c(A.white, o.label) : c(A.dim, o.label);
2401
+ const hint = o.hint ? c(A.gray, ` (${o.hint})`) : "";
2402
+ process.stdout.write(`${A.clearLine}${BAR} ${dot} ${label}${hint}
2403
+ `);
2404
+ });
2405
+ };
2406
+ return new Promise((resolve4) => {
2407
+ readline.emitKeypressEvents(process.stdin);
2408
+ process.stdin.setRawMode(true);
2409
+ process.stdin.resume();
2410
+ const onKey = (_, key) => {
2411
+ if (key.ctrl && key.name === "c") {
2412
+ cleanup();
2413
+ resolve4(CANCEL);
2414
+ return;
2415
+ }
2416
+ if (key.name === "up") {
2417
+ idx = (idx - 1 + options.length) % options.length;
2418
+ render();
2419
+ }
2420
+ if (key.name === "down") {
2421
+ idx = (idx + 1) % options.length;
2422
+ render();
2423
+ }
2424
+ if (key.name === "return") {
2425
+ cleanup();
2426
+ resolve4(options[idx].value);
2427
+ }
2428
+ };
2429
+ const cleanup = () => {
2430
+ process.stdin.setRawMode(false);
2431
+ process.stdin.pause();
2432
+ process.stdin.removeListener("keypress", onKey);
2433
+ process.stdout.write(`${BAR}
2434
+ `);
2435
+ };
2436
+ process.stdin.on("keypress", onKey);
2437
+ });
2438
+ }
2439
+ while (true) {
2440
+ const raw = await readLine(
2441
+ `${BAR} ${c(A.gray, `Choose 1-${options.length} (default ${defaultIdx + 1}):`)} `
2442
+ );
2443
+ if (isCancel(raw)) return CANCEL;
2444
+ const trimmed = raw.trim();
2445
+ if (trimmed === "") return options[defaultIdx].value;
2446
+ const n = Number(trimmed);
2447
+ if (Number.isInteger(n) && n >= 1 && n <= options.length)
2448
+ return options[n - 1].value;
2449
+ process.stdout.write(
2450
+ `${BAR} ${c(A.red, `Please enter a number between 1 and ${options.length}`)}
2451
+ `
2452
+ );
2453
+ }
2454
+ }
2455
+ async function text(opts) {
2456
+ const { message, placeholder, validate } = opts;
2457
+ while (true) {
2458
+ const hint = placeholder ? c(A.gray, ` (${placeholder})`) : "";
2459
+ const raw = await readLine(
2460
+ `${BAR} ${c(A.bold + A.white, message)}${hint}
2461
+ ${BAR} ${c(A.cyan, "\u25C6")} `
2462
+ );
2463
+ if (isCancel(raw)) return CANCEL;
2464
+ const val = raw;
2465
+ const error = validate?.(val);
2466
+ if (error) {
2467
+ process.stdout.write(`${BAR} ${c(A.red, `! ${error}`)}
2468
+ `);
2469
+ continue;
2470
+ }
2471
+ process.stdout.write(`${BAR}
2472
+ `);
2473
+ return val;
2474
+ }
2475
+ }
2476
+ var p = { intro, outro, cancel, isCancel, select, text };
2477
+ var GLOBAL_CONFIG_PATH2 = join5(
2478
+ homedir4(),
2479
+ ".config",
2480
+ "mcp-s-cli",
2481
+ "config.json"
2482
+ );
2483
+ function writeConfig(config) {
2484
+ mkdirSync4(dirname4(GLOBAL_CONFIG_PATH2), { recursive: true });
2485
+ writeFileSync4(
2486
+ GLOBAL_CONFIG_PATH2,
2487
+ `${JSON.stringify(config, null, 2)}
2488
+ `,
2489
+ "utf-8"
2490
+ );
2491
+ }
2492
+ async function initCommand(options) {
2493
+ const { baseUrl, org, mcp, toolkit, userAccessKey, token } = options;
2494
+ const config = {};
2495
+ if (org) {
2496
+ config.org = org;
2497
+ } else if (baseUrl) {
2498
+ config.baseUrl = baseUrl;
2499
+ }
2500
+ if (mcp) config.mcp = mcp;
2501
+ if (toolkit) config.toolkit = toolkit;
2502
+ if (userAccessKey) config.userAccessKey = userAccessKey;
2503
+ if (token) config.token = token;
2504
+ writeConfig(config);
2505
+ console.log("Initialized mcp-s-cli config");
2506
+ try {
2507
+ const skillResult = installSkill();
2508
+ if (skillResult.installed.length > 0) {
2509
+ const agents = skillResult.installed.join(", ");
2510
+ console.log(`mcp-s-cli skill installed for ${agents}`);
2511
+ }
2512
+ if (skillResult.failed.length > 0) {
2513
+ const agents = skillResult.failed.map(({ agent }) => agent).join(", ");
2514
+ console.log(`mcp-s-cli skill install failed for ${agents}`);
2515
+ }
2516
+ } catch {
2517
+ }
2518
+ const isHttpConfig = !!(org || baseUrl);
2519
+ const hasStaticAuth = !!token;
2520
+ if (isHttpConfig && !hasStaticAuth) {
2521
+ try {
2522
+ await loginCommand({});
2523
+ } catch (err) {
2524
+ console.error(`Login failed: ${err.message}`);
2525
+ }
2526
+ }
2527
+ }
2528
+ async function initInteractive() {
2529
+ p.intro("mcp-s-cli init");
2530
+ const connectionType = await p.select({
2531
+ message: "Connection",
2532
+ options: [
2533
+ { value: "org", label: "Org", hint: "derive URL from org name" },
2534
+ { value: "base-url", label: "Base URL", hint: "provide a custom URL" }
2535
+ ],
2536
+ initialValue: "org"
2537
+ });
2538
+ if (p.isCancel(connectionType)) {
2539
+ p.cancel("Operation cancelled");
2540
+ process.exit(0);
2541
+ }
2542
+ let org;
2543
+ let baseUrl;
2544
+ if (connectionType === "org") {
2545
+ const orgValue = await p.text({
2546
+ message: "Org",
2547
+ validate(value) {
2548
+ if (!value?.trim()) return "Org is required";
2549
+ }
2550
+ });
2551
+ if (p.isCancel(orgValue)) {
2552
+ p.cancel("Operation cancelled");
2553
+ process.exit(0);
2554
+ }
2555
+ org = orgValue;
2556
+ } else {
2557
+ const baseUrlValue = await p.text({
2558
+ message: "Base URL",
2559
+ validate(value) {
2560
+ if (!value?.trim()) return "Base URL is required";
2561
+ }
2562
+ });
2563
+ if (p.isCancel(baseUrlValue)) {
2564
+ p.cancel("Operation cancelled");
2565
+ process.exit(0);
2566
+ }
2567
+ baseUrl = baseUrlValue;
2568
+ }
2569
+ const mcp = await p.text({
2570
+ message: "MCP",
2571
+ placeholder: "none"
2572
+ });
2573
+ if (p.isCancel(mcp)) {
2574
+ p.cancel("Operation cancelled");
2575
+ process.exit(0);
2576
+ }
2577
+ const toolkit = await p.text({
2578
+ message: "Toolkit",
2579
+ placeholder: "none"
2580
+ });
2581
+ if (p.isCancel(toolkit)) {
2582
+ p.cancel("Operation cancelled");
2583
+ process.exit(0);
2584
+ }
2585
+ const userAccessKey = await p.text({
2586
+ message: "User Access Key (optional \u2014 leave blank for HTTP mode)",
2587
+ placeholder: "none"
2588
+ });
2589
+ if (p.isCancel(userAccessKey)) {
2590
+ p.cancel("Operation cancelled");
2591
+ process.exit(0);
2592
+ }
2593
+ p.outro("Saving config...");
2594
+ await initCommand({
2595
+ org,
2596
+ baseUrl,
2597
+ mcp: mcp?.trim() || void 0,
2598
+ toolkit: toolkit?.trim() || void 0,
2599
+ userAccessKey: userAccessKey?.trim() || void 0
2600
+ });
2601
+ }
2602
+
2603
+ // src/commands/kill-daemon.ts
2604
+ import { existsSync as existsSync7, readdirSync as readdirSync2 } from "fs";
2605
+ function killDaemonCommand() {
2606
+ const socketDir = getSocketDir();
2607
+ if (!existsSync7(socketDir)) {
2608
+ console.log("No daemons running.");
2609
+ return;
2610
+ }
2611
+ let files;
2612
+ try {
2613
+ files = readdirSync2(socketDir).filter((f) => f.endsWith(".pid"));
2614
+ } catch {
2615
+ console.log("No daemons running.");
2616
+ return;
2617
+ }
2618
+ if (files.length === 0) {
2619
+ console.log("No daemons running.");
2620
+ return;
2621
+ }
2622
+ let killed = 0;
2623
+ let alreadyDead = 0;
2624
+ for (const file of files) {
2625
+ const serverName = file.replace(".pid", "");
2626
+ const pidInfo = readPidFile(serverName);
2627
+ if (!pidInfo) {
2628
+ removeSocketFile(serverName);
2629
+ removeReadyFile(serverName);
2630
+ continue;
2631
+ }
2632
+ if (isProcessRunning(pidInfo.pid)) {
2633
+ killProcess(pidInfo.pid);
2634
+ console.log(`Killed daemon for ${serverName} (pid ${pidInfo.pid})`);
2635
+ killed++;
2636
+ } else {
2637
+ alreadyDead++;
2638
+ }
2639
+ removePidFile(serverName);
2640
+ removeSocketFile(serverName);
2641
+ removeReadyFile(serverName);
2642
+ }
2643
+ if (killed === 0 && alreadyDead > 0) {
2644
+ console.log("No daemons running (cleaned up stale files).");
2645
+ }
2646
+ }
2647
+
2648
+ // src/commands/list.ts
2649
+ async function listCommand(options) {
2650
+ let loaded;
2651
+ try {
2652
+ loaded = await loadConfig(options.configPath);
2653
+ } catch (error) {
2654
+ console.error(error.message);
2655
+ process.exit(1 /* CLIENT_ERROR */);
2656
+ }
2657
+ const { serverConfig } = loaded;
2658
+ const serverLabel = "server";
2659
+ let connection = null;
2660
+ let tools = [];
2661
+ let instructions;
2662
+ let errorMsg;
2663
+ try {
2664
+ connection = await getConnection(serverLabel, serverConfig);
2665
+ tools = await connection.listTools();
2666
+ instructions = await connection.getInstructions();
2667
+ debug(`${serverLabel}: loaded ${tools.length} tools`);
2668
+ } catch (error) {
2669
+ errorMsg = error.message;
2670
+ debug(`${serverLabel}: connection failed - ${errorMsg}`);
2671
+ } finally {
2672
+ if (connection) {
2673
+ await safeClose(connection.close);
2674
+ }
2675
+ }
2676
+ const displayServers = [
2677
+ {
2678
+ name: serverLabel,
2679
+ instructions,
2680
+ tools: errorMsg ? [
2681
+ {
2682
+ name: `<error: ${errorMsg}>`,
2683
+ description: void 0,
2684
+ inputSchema: {}
2685
+ }
2686
+ ] : tools
2687
+ }
2688
+ ];
2689
+ console.log(formatServerList(displayServers, options.withDescriptions));
2690
+ }
2691
+
2692
+ // src/commands/logout.ts
2693
+ async function logoutCommand(options) {
2694
+ const { configPath } = options;
2695
+ const { serverConfig } = await loadConfig(configPath);
2696
+ if (!isHttpServer(serverConfig)) {
2697
+ throw new Error(
2698
+ "Current server is a stdio server. OAuth logout is only supported for HTTP servers."
2699
+ );
2700
+ }
2701
+ await clearTokens(serverConfig.url);
2702
+ console.log(`Logged out from ${serverConfig.url}.`);
2703
+ }
2704
+
2705
+ // src/commands/whoami.ts
2706
+ import { existsSync as existsSync8, readFileSync as readFileSync4, statSync } from "fs";
2707
+ import { homedir as homedir5 } from "os";
2708
+ import { join as join6, resolve as resolve3 } from "path";
2709
+ function getResolvedConfigPath(explicitPath) {
2710
+ if (explicitPath) {
2711
+ return resolve3(explicitPath);
2712
+ }
2713
+ if (process.env.MCP_CONFIG_PATH) {
2714
+ return resolve3(process.env.MCP_CONFIG_PATH);
2715
+ }
2716
+ const defaultPath = join6(homedir5(), ".config", "mcp-s-cli", "config.json");
2717
+ if (existsSync8(defaultPath)) {
2718
+ return defaultPath;
2719
+ }
2720
+ return null;
2721
+ }
2722
+ async function whoamiCommand(options) {
2723
+ const { configPath } = options;
2724
+ console.log(`mcp-s-cli v${VERSION}`);
2725
+ console.log("");
2726
+ const resolvedConfigPath = getResolvedConfigPath(configPath);
2727
+ console.log("Config");
2728
+ if (resolvedConfigPath) {
2729
+ console.log(` Path: ${resolvedConfigPath}`);
2730
+ } else {
2731
+ console.log(" Path: (not found)");
2732
+ console.log(
2733
+ ' Run "mcp-s-cli init --org <org>" or "mcp-s-cli init --base-url <url>" to create a config file.'
2734
+ );
2735
+ return;
2736
+ }
2737
+ let raw;
2738
+ try {
2739
+ const loaded = await loadConfig(configPath);
2740
+ raw = loaded.raw;
2741
+ } catch (err) {
2742
+ console.log(` Error loading config: ${err.message}`);
2743
+ return;
2744
+ }
2745
+ const authFilePath = join6(homedir5(), ".config", "mcp-s-cli", "auth.json");
2746
+ console.log("");
2747
+ console.log("OAuth Tokens");
2748
+ console.log(` Path: ${authFilePath}`);
2749
+ if (!existsSync8(authFilePath)) {
2750
+ console.log(" (no tokens stored)");
2751
+ } else {
2752
+ try {
2753
+ const raw2 = readFileSync4(authFilePath, "utf-8");
2754
+ const data = JSON.parse(raw2);
2755
+ const tokenEntries = Object.entries(data.tokens ?? {});
2756
+ if (tokenEntries.length === 0) {
2757
+ console.log(" (no tokens stored)");
2758
+ }
2759
+ } catch {
2760
+ console.log(" (could not read auth file)");
2761
+ }
2762
+ }
2763
+ const historyPath = join6(homedir5(), ".config", "mcp-s-cli", "history.jsonl");
2764
+ if (existsSync8(historyPath)) {
2765
+ const lines = readFileSync4(historyPath, "utf-8").split("\n").filter(Boolean);
2766
+ const size = statSync(historyPath).size;
2767
+ const sizeStr = size >= 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)} MB` : size >= 1024 ? `${(size / 1024).toFixed(1)} KB` : `${size} B`;
2768
+ console.log("");
2769
+ console.log("History");
2770
+ console.log(` Path: ${historyPath}`);
2771
+ console.log(` Entries: ${lines.length} (${sizeStr})`);
2772
+ if (lines.length > 0) {
2773
+ try {
2774
+ const last = JSON.parse(lines[lines.length - 1]);
2775
+ const cmd = last.args?.join(" ") ?? "";
2776
+ const ts = last.ts ? new Date(last.ts).toLocaleString() : "";
2777
+ console.log(` Last: ${ts} ${cmd}`);
2778
+ } catch {
2779
+ }
2780
+ }
2781
+ }
2782
+ const skillInfo = getSkillInstallInfo();
2783
+ const canonicalPath = getCanonicalSkillPath();
2784
+ console.log("");
2785
+ console.log("Skill");
2786
+ console.log(` Canonical: ${canonicalPath}`);
2787
+ if (skillInfo.length === 0) {
2788
+ console.log(" (no supported agents detected)");
2789
+ } else {
2790
+ for (const { displayName, path, installed } of skillInfo) {
2791
+ const status = installed ? "\u2713" : "\u2717";
2792
+ console.log(` ${status} ${displayName}`);
2793
+ console.log(` ${path}`);
2794
+ }
2795
+ }
2796
+ }
2797
+
2798
+ // src/index.ts
2799
+ function isPossibleSubcommand(arg) {
2800
+ const aliases = [
2801
+ "run",
2802
+ "execute",
2803
+ "exec",
2804
+ "invoke",
2805
+ "list",
2806
+ "ls",
2807
+ "get",
2808
+ "show",
2809
+ "describe",
2810
+ "search",
2811
+ "find",
2812
+ "query"
2813
+ ];
2814
+ return aliases.includes(arg.toLowerCase());
2815
+ }
2816
+ function parseArgs2(args) {
2817
+ const result = {
2818
+ command: "info",
2819
+ withDescriptions: false
2820
+ };
2821
+ const positional = [];
2822
+ for (let i = 0; i < args.length; i++) {
2823
+ const arg = args[i];
2824
+ switch (arg) {
2825
+ case "-h":
2826
+ case "--help":
2827
+ result.command = "help";
2828
+ return result;
2829
+ case "-v":
2830
+ case "--version":
2831
+ result.command = "version";
2832
+ return result;
2833
+ case "-d":
2834
+ case "--with-descriptions":
2835
+ result.withDescriptions = true;
2836
+ break;
2837
+ case "-c":
2838
+ case "--config":
2839
+ result.configPath = args[++i];
2840
+ if (!result.configPath) {
2841
+ console.error(
2842
+ formatCliError(missingArgumentError("-c/--config", "path"))
2843
+ );
2844
+ process.exit(1 /* CLIENT_ERROR */);
2845
+ }
2846
+ break;
2847
+ case "--url":
2848
+ result.baseUrl = args[++i];
2849
+ if (!result.baseUrl) {
2850
+ console.error(formatCliError(missingArgumentError("--url", "url")));
2851
+ process.exit(1 /* CLIENT_ERROR */);
2852
+ }
2853
+ break;
2854
+ case "--base-url":
2855
+ result.baseUrl = args[++i];
2856
+ if (!result.baseUrl) {
2857
+ console.error(
2858
+ formatCliError(missingArgumentError("--base-url", "url"))
2859
+ );
2860
+ process.exit(1 /* CLIENT_ERROR */);
2861
+ }
2862
+ break;
2863
+ case "--org":
2864
+ result.org = args[++i];
2865
+ if (!result.org) {
2866
+ console.error(formatCliError(missingArgumentError("--org", "org")));
2867
+ process.exit(1 /* CLIENT_ERROR */);
2868
+ }
2869
+ break;
2870
+ case "--mcp":
2871
+ result.mcp = args[++i];
2872
+ if (!result.mcp) {
2873
+ console.error(formatCliError(missingArgumentError("--mcp", "id")));
2874
+ process.exit(1 /* CLIENT_ERROR */);
2875
+ }
2876
+ break;
2877
+ case "--toolkit":
2878
+ result.toolkit = args[++i];
2879
+ if (!result.toolkit) {
2880
+ console.error(
2881
+ formatCliError(missingArgumentError("--toolkit", "name"))
2882
+ );
2883
+ process.exit(1 /* CLIENT_ERROR */);
2884
+ }
2885
+ break;
2886
+ case "--user-access-key":
2887
+ result.userAccessKey = args[++i];
2888
+ if (!result.userAccessKey) {
2889
+ console.error(
2890
+ formatCliError(missingArgumentError("--user-access-key", "key"))
2891
+ );
2892
+ process.exit(1 /* CLIENT_ERROR */);
2893
+ }
2894
+ break;
2895
+ case "--token":
2896
+ result.token = args[++i];
2897
+ if (!result.token) {
2898
+ console.error(
2899
+ formatCliError(missingArgumentError("--token", "token"))
2900
+ );
2901
+ process.exit(1 /* CLIENT_ERROR */);
2902
+ }
2903
+ break;
2904
+ default:
2905
+ if (arg.startsWith("-") && arg !== "-") {
2906
+ console.error(formatCliError(unknownOptionError(arg)));
2907
+ process.exit(1 /* CLIENT_ERROR */);
2908
+ }
2909
+ positional.push(arg);
2910
+ }
2911
+ }
2912
+ if (positional.length === 0) {
2913
+ result.command = "list";
2914
+ return result;
2915
+ }
2916
+ const firstArg = positional[0];
2917
+ if (firstArg === "info") {
2918
+ result.command = "info";
2919
+ result.tool = positional[1];
2920
+ if (positional.length > 2) {
2921
+ console.error(
2922
+ formatCliError(tooManyArgumentsError("info", positional.length - 1, 1))
2923
+ );
2924
+ process.exit(1 /* CLIENT_ERROR */);
2925
+ }
2926
+ return result;
2927
+ }
2928
+ if (firstArg === "grep") {
2929
+ result.command = "grep";
2930
+ result.pattern = positional[1];
2931
+ if (!result.pattern) {
2932
+ console.error(formatCliError(missingArgumentError("grep", "pattern")));
2933
+ process.exit(1 /* CLIENT_ERROR */);
2934
+ }
2935
+ if (positional.length > 2) {
2936
+ console.error(
2937
+ formatCliError(tooManyArgumentsError("grep", positional.length - 1, 1))
2938
+ );
2939
+ process.exit(1 /* CLIENT_ERROR */);
2940
+ }
2941
+ return result;
2942
+ }
2943
+ if (firstArg === "call") {
2944
+ result.command = "call";
2945
+ const remaining = positional.slice(1);
2946
+ if (remaining.length === 0) {
2947
+ console.error(formatCliError(missingArgumentError("call", "tool")));
2948
+ process.exit(1 /* CLIENT_ERROR */);
2949
+ }
2950
+ result.tool = remaining[0];
2951
+ const jsonArgs = remaining.slice(1);
2952
+ if (jsonArgs.length > 0) {
2953
+ const argsValue = jsonArgs.join(" ");
2954
+ result.args = argsValue === "-" ? void 0 : argsValue;
2955
+ }
2956
+ return result;
2957
+ }
2958
+ if (firstArg === "whoami") {
2959
+ result.command = "whoami";
2960
+ return result;
2961
+ }
2962
+ if (firstArg === "login") {
2963
+ result.command = "login";
2964
+ return result;
2965
+ }
2966
+ if (firstArg === "logout") {
2967
+ result.command = "logout";
2968
+ return result;
2969
+ }
2970
+ if (firstArg === "clear") {
2971
+ result.command = "clear";
2972
+ return result;
2973
+ }
2974
+ if (firstArg === "clear-auth") {
2975
+ result.command = "clear-auth";
2976
+ return result;
2977
+ }
2978
+ if (firstArg === "clear-history") {
2979
+ result.command = "clear-history";
2980
+ return result;
2981
+ }
2982
+ if (firstArg === "clear-skill") {
2983
+ result.command = "clear-skill";
2984
+ return result;
2985
+ }
2986
+ if (firstArg === "kill-daemon") {
2987
+ result.command = "kill-daemon";
2988
+ return result;
2989
+ }
2990
+ if (firstArg === "init") {
2991
+ result.command = "init";
2992
+ if (result.baseUrl && result.org) {
2993
+ console.error(
2994
+ formatCliError({
2995
+ code: 1 /* CLIENT_ERROR */,
2996
+ type: "INVALID_ARGUMENT",
2997
+ message: "--base-url and --org are mutually exclusive",
2998
+ suggestion: "Use --base-url for a custom URL or --org for an org-derived URL, not both"
2999
+ })
3000
+ );
3001
+ process.exit(1 /* CLIENT_ERROR */);
3002
+ }
3003
+ return result;
3004
+ }
3005
+ if (isPossibleSubcommand(firstArg)) {
3006
+ console.error(formatCliError(unknownSubcommandError(firstArg)));
3007
+ process.exit(1 /* CLIENT_ERROR */);
3008
+ }
3009
+ result.command = "info";
3010
+ result.tool = firstArg;
3011
+ return result;
3012
+ }
3013
+ function printHelp() {
3014
+ console.log(`
3015
+ mcp-s-cli v${VERSION} - A lightweight CLI for connecting AI agents to the Webrix MCP Gateway
3016
+
3017
+ Usage:
3018
+ mcp-s-cli [options] List all tools
3019
+ mcp-s-cli [options] info Show server details
3020
+ mcp-s-cli [options] info <tool> Show tool schema
3021
+ mcp-s-cli [options] grep <pattern> Search tools by glob pattern
3022
+ mcp-s-cli [options] call <tool> Call tool (reads JSON from stdin if no args)
3023
+ mcp-s-cli [options] call <tool> <json> Call tool with JSON arguments
3024
+ mcp-s-cli init --org <org> [--mcp <id>] [--toolkit <tk>] [--user-access-key <key>] Initialize with org
3025
+ mcp-s-cli init --base-url <url> [--mcp <id>] [--toolkit <tk>] [--user-access-key <key>] Initialize with URL
3026
+ mcp-s-cli whoami Show config location and auth state
3027
+ mcp-s-cli login Log in to the configured server via OAuth
3028
+ mcp-s-cli logout Log out (remove stored OAuth tokens)
3029
+ mcp-s-cli clear Clear server config (resets config.json to {})
3030
+ mcp-s-cli clear-auth Clear all stored auth data (sets auth.json to {})
3031
+ mcp-s-cli clear-history Delete the history file (~/.config/mcp-s-cli/history.jsonl)
3032
+ mcp-s-cli clear-skill Remove the mcp-s-cli skill from all agent skill directories
3033
+
3034
+ Options:
3035
+ -h, --help Show this help message
3036
+ -v, --version Show version number
3037
+ -d, --with-descriptions Include tool descriptions
3038
+ -c, --config <path> Path to config.json config file
3039
+ --base-url <url> Server base URL (used with init)
3040
+ --org <org> Org name \u2014 derives URL as https://<org>.mcp-s.com/mcp (used with init)
3041
+ --mcp <id> MCP identifier header (used with init)
3042
+ --toolkit <name> Toolkit name header (used with init)
3043
+ --user-access-key <key> User access key \u2014 triggers stdio mode (used with init)
3044
+
3045
+ Output:
3046
+ mcp-s-cli/info/grep Human-readable text to stdout
3047
+ call Raw JSON to stdout (for piping)
3048
+ Errors Always to stderr
3049
+
3050
+ Examples:
3051
+ mcp-s-cli # List all tools
3052
+ mcp-s-cli -d # List with descriptions
3053
+ mcp-s-cli grep "*file*" # Search for file tools
3054
+ mcp-s-cli info # Show server details
3055
+ mcp-s-cli info read_file # Show tool schema
3056
+ mcp-s-cli call read_file '{}' # Call tool
3057
+ cat input.json | mcp-s-cli call read_file # Read args from stdin
3058
+ mcp-s-cli init --org my-org --mcp slack --toolkit my-tk # HTTP mode via org
3059
+ mcp-s-cli init --base-url example.com/123 --mcp slack # HTTP mode via base URL
3060
+ mcp-s-cli init --org my-org --mcp slack --user-access-key 123 # stdio mode
3061
+ mcp-s-cli whoami # Show config and auth state
3062
+ mcp-s-cli login # Authenticate via OAuth
3063
+ mcp-s-cli logout # Remove stored OAuth tokens
3064
+ mcp-s-cli clear # Reset server config
3065
+ mcp-s-cli clear-auth # Clear stored auth data
3066
+ mcp-s-cli clear-skill # Remove installed skill from all agents
3067
+ mcp-s-cli kill-daemon # Kill the running daemon process
3068
+
3069
+ Environment Variables:
3070
+ MCP_DAEMON=0 Disable connection caching (daemon mode, on by default)
3071
+ MCP_DAEMON_TIMEOUT=N Set daemon idle timeout in seconds (default: 300)
3072
+
3073
+ Config File:
3074
+ The CLI looks for config.json in:
3075
+ 1. Path specified by MCP_CONFIG_PATH or -c/--config
3076
+ 2. ~/.config/mcp-s-cli/config.json
3077
+
3078
+ 'mcp-s-cli init' always writes to ~/.config/mcp-s-cli/config.json
3079
+ `);
3080
+ }
3081
+ function appendHistory(argv) {
3082
+ if (process.env.MCP_CLI_HISTORY !== "1") return;
3083
+ try {
3084
+ const dir = join7(homedir6(), ".config", "mcp-s-cli");
3085
+ mkdirSync5(dir, { recursive: true });
3086
+ const entry = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), args: argv });
3087
+ appendFileSync(join7(dir, "history.jsonl"), `${entry}
3088
+ `, "utf8");
3089
+ } catch (err) {
3090
+ const msg = err instanceof Error ? err.message : String(err);
3091
+ process.stderr.write(
3092
+ `mcp-s-cli: warning: failed to write history: ${msg}
3093
+ `
3094
+ );
3095
+ }
3096
+ }
3097
+ async function main() {
3098
+ appendHistory(process.argv.slice(2));
3099
+ const args = parseArgs2(process.argv.slice(2));
3100
+ switch (args.command) {
3101
+ case "help":
3102
+ printHelp();
3103
+ break;
3104
+ case "version":
3105
+ console.log(`mcp-s-cli v${VERSION}`);
3106
+ break;
3107
+ case "list":
3108
+ await listCommand({
3109
+ withDescriptions: args.withDescriptions,
3110
+ configPath: args.configPath
3111
+ });
3112
+ break;
3113
+ case "info":
3114
+ await infoCommand({
3115
+ tool: args.tool,
3116
+ withDescriptions: args.withDescriptions,
3117
+ configPath: args.configPath
3118
+ });
3119
+ break;
3120
+ case "grep":
3121
+ await grepCommand({
3122
+ pattern: args.pattern ?? "",
3123
+ withDescriptions: args.withDescriptions,
3124
+ configPath: args.configPath
3125
+ });
3126
+ break;
3127
+ case "call":
3128
+ await callCommand({
3129
+ tool: args.tool ?? "",
3130
+ args: args.args,
3131
+ configPath: args.configPath
3132
+ });
3133
+ break;
3134
+ case "init":
3135
+ if (args.baseUrl || args.org) {
3136
+ await initCommand({
3137
+ baseUrl: args.baseUrl,
3138
+ org: args.org,
3139
+ mcp: args.mcp,
3140
+ toolkit: args.toolkit,
3141
+ userAccessKey: args.userAccessKey,
3142
+ token: args.token
3143
+ });
3144
+ } else {
3145
+ await initInteractive();
3146
+ }
3147
+ break;
3148
+ case "whoami":
3149
+ await whoamiCommand({ configPath: args.configPath });
3150
+ break;
3151
+ case "login":
3152
+ await loginCommand({ configPath: args.configPath });
3153
+ break;
3154
+ case "logout":
3155
+ await logoutCommand({ configPath: args.configPath });
3156
+ break;
3157
+ case "clear":
3158
+ await clearCommand();
3159
+ break;
3160
+ case "clear-auth":
3161
+ await clearAuthCommand();
3162
+ break;
3163
+ case "clear-history":
3164
+ clearHistoryCommand();
3165
+ break;
3166
+ case "kill-daemon":
3167
+ killDaemonCommand();
3168
+ break;
3169
+ case "clear-skill": {
3170
+ const result = clearSkill();
3171
+ if (result.removed.length > 0) {
3172
+ console.log(`Removed skill from: ${result.removed.join(", ")}`);
3173
+ }
3174
+ if (result.canonicalRemoved) {
3175
+ console.log("Removed canonical skill directory");
3176
+ }
3177
+ if (result.failed.length > 0) {
3178
+ for (const { agent, error } of result.failed) {
3179
+ console.error(`Failed to remove skill for ${agent}: ${error}`);
3180
+ }
3181
+ }
3182
+ if (result.removed.length === 0 && !result.canonicalRemoved) {
3183
+ console.log("Skill was not installed anywhere.");
3184
+ }
3185
+ break;
3186
+ }
3187
+ }
3188
+ }
3189
+ process.on("SIGINT", () => {
3190
+ process.exit(130);
3191
+ });
3192
+ process.on("SIGTERM", () => {
3193
+ process.exit(143);
3194
+ });
3195
+ main().then(() => {
3196
+ setImmediate(() => process.exit(0));
3197
+ }).catch((error) => {
3198
+ console.error(error.message);
3199
+ setImmediate(() => process.exit(1 /* CLIENT_ERROR */));
3200
+ });