@mcp-s/cli 0.0.16 → 0.0.18

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/SKILL.md CHANGED
@@ -14,6 +14,8 @@ Access a single MCP server through the command line. MCP enables interaction wit
14
14
  | `mcp-s-cli` | List all available tools |
15
15
  | `mcp-s-cli info <tool>` | Get tool JSON schema |
16
16
  | `mcp-s-cli grep <query>` | Search tools by name (case-insensitive substring) |
17
+ | `mcp-s-cli grep <query> -d` | Search tools by name, include descriptions |
18
+ | `mcp-s-cli get-servers` | List available servers (slug, name, description) |
17
19
  | `mcp-s-cli call <tool>` | Call tool (reads JSON from stdin if no args) |
18
20
  | `mcp-s-cli call <tool> '<json>'` | Call tool with arguments |
19
21
 
@@ -30,7 +32,7 @@ mcp-s-cli check-auth
30
32
 
31
33
  ## Workflow
32
34
 
33
- 1. **Discover**: `mcp-s-cli grep <query>` → find tools by keyword; fall back to `mcp-s-cli` only when you need a full inventory
35
+ 1. **Discover**: find the right tool (see discovery ladder below)
34
36
  2. **Inspect**: `mcp-s-cli info <tool>` → get full JSON schema
35
37
  3. **Execute**: `mcp-s-cli call <tool> '<json>'` → run with arguments
36
38
 
@@ -38,13 +40,14 @@ mcp-s-cli check-auth
38
40
 
39
41
  Every command consumes tokens. Pick the cheapest operation that gets the job done.
40
42
 
41
- ### Discovery: start narrow
43
+ ### Discovery ladder (follow in order, stop as soon as you're confident)
42
44
 
43
- - **`grep <query>`** — cheapest. Use when you can guess a keyword from the user's request (e.g., "read a github file" → `grep file` or `grep content`).
44
- - **`mcp-s-cli`** (names only) moderate. Use when you need a full inventory (e.g., "what tools do I have?" or "analyze logs from all tools").
45
- - **`mcp-s-cli -d`** (names + descriptions)most expensive. Use only when names alone aren't enough to pick the right tool.
45
+ 1. **`grep <query>`** — cheapest. Guess a keyword from the user's request (e.g., "read a github file" → `grep file`). If the results clearly contain the right tool, skip to **Inspect**.
46
+ 2. **`get-servers`** lightweight REST call, returns each server's **slug**, name, and description. Use when grep results are ambiguous or empty and you need to understand which servers/domains are available.
47
+ 3. **`grep <query>` scoped to a slug** now that you know the relevant server slug(s), grep again to narrow results. If you're not confident you and still need more details, continue to step 4.
48
+ 4. **`grep <query> -d`** — includes tool descriptions in the output. Use to disambiguate between similar-sounding candidates.
46
49
 
47
- **Rule: if you can guess a keyword, grep first.**
50
+ **Rule: always start with a plain `grep`. Escalate only when you're not confident you found the right tool.**
48
51
 
49
52
  ### Execution: keep responses small
50
53
 
@@ -63,7 +66,9 @@ When you pipe through a filter and get empty output, **do not** fall back to the
63
66
  ### Decision heuristic
64
67
 
65
68
  - User mentions a specific tool or domain keyword → `grep` for it.
66
- - User asks a broad question across all tools list tools first, then act.
69
+ - Grep results are empty or ambiguous `get-servers` to see available servers, then grep again with a better keyword or scoped to the relevant slug.
70
+ - You have candidates but can't tell them apart by name → `grep -d` for descriptions.
71
+ - User asks a broad question across all tools → `get-servers` first, then grep per relevant server.
67
72
  - Task involves multiple items of the same type → use one bulk/list call, not N individual calls.
68
73
  - Tool may return large payloads → use filter params + pipe to trim output.
69
74
  - Filter script produced no output → **probe one item** to learn the shape, then re-run with a corrected filter. Never fall back to the raw unfiltered call.
package/dist/SKILL.md CHANGED
@@ -14,6 +14,8 @@ Access a single MCP server through the command line. MCP enables interaction wit
14
14
  | `mcp-s-cli` | List all available tools |
15
15
  | `mcp-s-cli info <tool>` | Get tool JSON schema |
16
16
  | `mcp-s-cli grep <query>` | Search tools by name (case-insensitive substring) |
17
+ | `mcp-s-cli grep <query> -d` | Search tools by name, include descriptions |
18
+ | `mcp-s-cli get-servers` | List available servers (slug, name, description) |
17
19
  | `mcp-s-cli call <tool>` | Call tool (reads JSON from stdin if no args) |
18
20
  | `mcp-s-cli call <tool> '<json>'` | Call tool with arguments |
19
21
 
@@ -30,7 +32,7 @@ mcp-s-cli check-auth
30
32
 
31
33
  ## Workflow
32
34
 
33
- 1. **Discover**: `mcp-s-cli grep <query>` → find tools by keyword; fall back to `mcp-s-cli` only when you need a full inventory
35
+ 1. **Discover**: find the right tool (see discovery ladder below)
34
36
  2. **Inspect**: `mcp-s-cli info <tool>` → get full JSON schema
35
37
  3. **Execute**: `mcp-s-cli call <tool> '<json>'` → run with arguments
36
38
 
@@ -38,13 +40,14 @@ mcp-s-cli check-auth
38
40
 
39
41
  Every command consumes tokens. Pick the cheapest operation that gets the job done.
40
42
 
41
- ### Discovery: start narrow
43
+ ### Discovery ladder (follow in order, stop as soon as you're confident)
42
44
 
43
- - **`grep <query>`** — cheapest. Use when you can guess a keyword from the user's request (e.g., "read a github file" → `grep file` or `grep content`).
44
- - **`mcp-s-cli`** (names only) moderate. Use when you need a full inventory (e.g., "what tools do I have?" or "analyze logs from all tools").
45
- - **`mcp-s-cli -d`** (names + descriptions)most expensive. Use only when names alone aren't enough to pick the right tool.
45
+ 1. **`grep <query>`** — cheapest. Guess a keyword from the user's request (e.g., "read a github file" → `grep file`). If the results clearly contain the right tool, skip to **Inspect**.
46
+ 2. **`get-servers`** lightweight REST call, returns each server's **slug**, name, and description. Use when grep results are ambiguous or empty and you need to understand which servers/domains are available.
47
+ 3. **`grep <query>` scoped to a slug** now that you know the relevant server slug(s), grep again to narrow results. If you're not confident you and still need more details, continue to step 4.
48
+ 4. **`grep <query> -d`** — includes tool descriptions in the output. Use to disambiguate between similar-sounding candidates.
46
49
 
47
- **Rule: if you can guess a keyword, grep first.**
50
+ **Rule: always start with a plain `grep`. Escalate only when you're not confident you found the right tool.**
48
51
 
49
52
  ### Execution: keep responses small
50
53
 
@@ -63,7 +66,9 @@ When you pipe through a filter and get empty output, **do not** fall back to the
63
66
  ### Decision heuristic
64
67
 
65
68
  - User mentions a specific tool or domain keyword → `grep` for it.
66
- - User asks a broad question across all tools list tools first, then act.
69
+ - Grep results are empty or ambiguous `get-servers` to see available servers, then grep again with a better keyword or scoped to the relevant slug.
70
+ - You have candidates but can't tell them apart by name → `grep -d` for descriptions.
71
+ - User asks a broad question across all tools → `get-servers` first, then grep per relevant server.
67
72
  - Task involves multiple items of the same type → use one bulk/list call, not N individual calls.
68
73
  - Tool may return large payloads → use filter params + pipe to trim output.
69
74
  - Filter script produced no output → **probe one item** to learn the shape, then re-run with a corrected filter. Never fall back to the raw unfiltered call.
package/dist/daemon.js CHANGED
@@ -303,10 +303,23 @@ async function performOAuthFlow(authServerUrl, resourceUrl) {
303
303
  if (open) {
304
304
  try {
305
305
  const { spawn: spawn2 } = await import("child_process");
306
- spawn2(open, [authUrl.toString()], {
307
- detached: true,
308
- stdio: "ignore"
309
- }).unref();
306
+ const urlStr = authUrl.toString();
307
+ let child;
308
+ if (process.platform === "win32") {
309
+ const escapedUrl = urlStr.replace(/&/g, "^&");
310
+ child = spawn2("cmd", ["/c", "start", '""', escapedUrl], {
311
+ detached: true,
312
+ stdio: "ignore"
313
+ });
314
+ } else {
315
+ child = spawn2(open, [urlStr], {
316
+ detached: true,
317
+ stdio: "ignore"
318
+ });
319
+ }
320
+ child.on("error", () => {
321
+ });
322
+ child.unref();
310
323
  } catch {
311
324
  console.error("Could not open browser automatically.");
312
325
  }
@@ -414,7 +427,7 @@ import { join as join2 } from "path";
414
427
  import { fileURLToPath } from "url";
415
428
 
416
429
  // src/version.ts
417
- var VERSION = "0.0.16";
430
+ var VERSION = "0.0.18";
418
431
 
419
432
  // src/client.ts
420
433
  function getRetryConfig(settings) {
package/dist/index.js CHANGED
@@ -299,10 +299,23 @@ async function performOAuthFlow(authServerUrl, resourceUrl) {
299
299
  if (open) {
300
300
  try {
301
301
  const { spawn: spawn2 } = await import("child_process");
302
- spawn2(open, [authUrl.toString()], {
303
- detached: true,
304
- stdio: "ignore"
305
- }).unref();
302
+ const urlStr = authUrl.toString();
303
+ let child;
304
+ if (process.platform === "win32") {
305
+ const escapedUrl = urlStr.replace(/&/g, "^&");
306
+ child = spawn2("cmd", ["/c", "start", '""', escapedUrl], {
307
+ detached: true,
308
+ stdio: "ignore"
309
+ });
310
+ } else {
311
+ child = spawn2(open, [urlStr], {
312
+ detached: true,
313
+ stdio: "ignore"
314
+ });
315
+ }
316
+ child.on("error", () => {
317
+ });
318
+ child.unref();
306
319
  } catch {
307
320
  console.error("Could not open browser automatically.");
308
321
  }
@@ -345,6 +358,48 @@ async function clearTokens(url) {
345
358
  tokens.delete(origin);
346
359
  await saveAuthData();
347
360
  }
361
+ async function fetchWithAuth(url, options = {}) {
362
+ const { timeout = 6e4, ...fetchOptions } = options;
363
+ const tokens = await getStoredTokens(url);
364
+ if (tokens) {
365
+ fetchOptions.headers = {
366
+ ...fetchOptions.headers,
367
+ Authorization: `Bearer ${tokens.access_token}`
368
+ };
369
+ }
370
+ let response = await fetch(url, {
371
+ ...fetchOptions,
372
+ signal: AbortSignal.timeout(timeout)
373
+ });
374
+ if (response.status === 401) {
375
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
376
+ if (wwwAuthHeader) {
377
+ const challenge = parseWWWAuthenticateHeader(wwwAuthHeader);
378
+ if (challenge.resource_metadata) {
379
+ const resourceMetadata = await fetchProtectedResourceMetadata(
380
+ challenge.resource_metadata
381
+ );
382
+ if (resourceMetadata?.authorization_servers?.[0]) {
383
+ const newTokens = await performOAuthFlow(
384
+ resourceMetadata.authorization_servers[0],
385
+ resourceMetadata.resource
386
+ );
387
+ if (newTokens) {
388
+ fetchOptions.headers = {
389
+ ...fetchOptions.headers,
390
+ Authorization: `Bearer ${newTokens.access_token}`
391
+ };
392
+ response = await fetch(url, {
393
+ ...fetchOptions,
394
+ signal: AbortSignal.timeout(timeout)
395
+ });
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
401
+ return response;
402
+ }
348
403
 
349
404
  // src/config.ts
350
405
  import { createHash } from "crypto";
@@ -1300,7 +1355,7 @@ async function cleanupOrphanedDaemons() {
1300
1355
  }
1301
1356
 
1302
1357
  // src/version.ts
1303
- var VERSION = "0.0.16";
1358
+ var VERSION = "0.0.18";
1304
1359
 
1305
1360
  // src/client.ts
1306
1361
  function getRetryConfig(settings) {
@@ -2257,6 +2312,87 @@ function configCommand(opts) {
2257
2312
  }
2258
2313
  }
2259
2314
 
2315
+ // src/commands/get-servers.ts
2316
+ async function getServersCommand(options) {
2317
+ const { configPath } = options;
2318
+ let raw;
2319
+ let serverConfig;
2320
+ try {
2321
+ const loaded = await loadConfig(configPath);
2322
+ raw = loaded.raw;
2323
+ serverConfig = loaded.serverConfig;
2324
+ } catch (error) {
2325
+ console.error(error.message);
2326
+ process.exit(1 /* CLIENT_ERROR */);
2327
+ }
2328
+ if (!isHttpServer(serverConfig)) {
2329
+ console.error(
2330
+ "get-servers is only supported for HTTP servers (org/baseUrl config)."
2331
+ );
2332
+ process.exit(1 /* CLIENT_ERROR */);
2333
+ }
2334
+ const mcpUrl = serverConfig.url.replace(/\/+$/, "");
2335
+ const url = `${mcpUrl}/servers`;
2336
+ const headers = {};
2337
+ if (raw.token) {
2338
+ headers.Authorization = `Bearer ${raw.token}`;
2339
+ }
2340
+ if (raw.mcp) {
2341
+ headers["x-mcp"] = raw.mcp;
2342
+ }
2343
+ if (raw.toolkit) {
2344
+ headers["x-toolkit"] = raw.toolkit;
2345
+ }
2346
+ let response;
2347
+ try {
2348
+ response = await fetchWithAuth(url, { headers });
2349
+ } catch (error) {
2350
+ const msg = error.message;
2351
+ if (msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND") || msg.includes("fetch failed")) {
2352
+ console.error(
2353
+ `Network error: could not connect to ${mcpUrl}. Is the server running?`
2354
+ );
2355
+ process.exit(3 /* NETWORK_ERROR */);
2356
+ }
2357
+ console.error(`Request failed: ${msg}`);
2358
+ process.exit(2 /* SERVER_ERROR */);
2359
+ }
2360
+ if (response.status === 401) {
2361
+ console.error("Not authenticated. Run: mcp-s-cli login");
2362
+ process.exit(4 /* AUTH_ERROR */);
2363
+ }
2364
+ if (!response.ok) {
2365
+ console.error(`Server returned ${response.status}: ${response.statusText}`);
2366
+ process.exit(2 /* SERVER_ERROR */);
2367
+ }
2368
+ let data;
2369
+ try {
2370
+ data = await response.json();
2371
+ } catch {
2372
+ console.error("Invalid JSON response from server.");
2373
+ process.exit(2 /* SERVER_ERROR */);
2374
+ }
2375
+ const servers = data.data ?? [];
2376
+ if (servers.length === 0) {
2377
+ console.log("No servers available.");
2378
+ return;
2379
+ }
2380
+ const lines = [];
2381
+ for (const server of servers) {
2382
+ lines.push(`slug: ${server.slug}`);
2383
+ if (server.description) {
2384
+ lines.push(`${server.name} - ${server.description}`);
2385
+ } else {
2386
+ lines.push(server.name);
2387
+ }
2388
+ lines.push("");
2389
+ }
2390
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
2391
+ lines.pop();
2392
+ }
2393
+ console.log(lines.join("\n"));
2394
+ }
2395
+
2260
2396
  // src/commands/grep.ts
2261
2397
  function matchesToolName(name, query) {
2262
2398
  return name.toLowerCase().includes(query.toLowerCase());
@@ -3321,6 +3457,10 @@ function parseArgs2(args) {
3321
3457
  result.command = "disable";
3322
3458
  return result;
3323
3459
  }
3460
+ if (firstArg === "get-servers") {
3461
+ result.command = "get-servers";
3462
+ return result;
3463
+ }
3324
3464
  if (firstArg === "config") {
3325
3465
  result.command = "config";
3326
3466
  const sub = positional[1];
@@ -3385,6 +3525,7 @@ Usage:
3385
3525
  mcp-s-cli login Log in to the configured server via OAuth
3386
3526
  mcp-s-cli logout Log out (remove stored OAuth tokens)
3387
3527
  mcp-s-cli check-auth Check auth state offline (no network); exits 0=ok, 4=needs login
3528
+ mcp-s-cli get-servers List available MCP servers
3388
3529
  mcp-s-cli config set <key> <value> Set a value in config.json
3389
3530
  mcp-s-cli config get <key> Get a value from config.json
3390
3531
  mcp-s-cli config show Print full config.json
@@ -3427,6 +3568,7 @@ Examples:
3427
3568
  mcp-s-cli login # Authenticate via OAuth
3428
3569
  mcp-s-cli logout # Remove stored OAuth tokens
3429
3570
  mcp-s-cli check-auth # Check auth state offline (fast preflight)
3571
+ mcp-s-cli get-servers # List available MCP servers
3430
3572
  mcp-s-cli config set settings.timeout 30 # Set request timeout to 30s
3431
3573
  mcp-s-cli clear # Reset server config
3432
3574
  mcp-s-cli clear-auth # Clear stored auth data
@@ -3555,6 +3697,11 @@ async function main() {
3555
3697
  case "check-auth":
3556
3698
  await checkAuthCommand({ configPath: args.configPath });
3557
3699
  break;
3700
+ case "get-servers":
3701
+ await getServersCommand({
3702
+ configPath: args.configPath
3703
+ });
3704
+ break;
3558
3705
  case "clear":
3559
3706
  await clearCommand();
3560
3707
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-s/cli",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "A lightweight CLI for connecting AI agents to the Webrix MCP Gateway",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",