@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 +12 -7
- package/dist/SKILL.md +12 -7
- package/dist/daemon.js +18 -5
- package/dist/index.js +152 -5
- package/package.json +1 -1
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**:
|
|
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
|
|
43
|
+
### Discovery ladder (follow in order, stop as soon as you're confident)
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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
|
-
-
|
|
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**:
|
|
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
|
|
43
|
+
### Discovery ladder (follow in order, stop as soon as you're confident)
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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
|
-
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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.
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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.
|
|
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;
|