@evantahler/mcpcli 0.5.0 → 0.5.2

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.
@@ -87,6 +87,9 @@ mcpcli deauth <server> # remove stored auth
87
87
  | `mcpcli auth <server> -s` | Check token status and TTL |
88
88
  | `mcpcli auth <server> -r` | Force token refresh |
89
89
  | `mcpcli deauth <server>` | Remove stored authentication |
90
+ | `mcpcli ping` | Check connectivity to all servers |
91
+ | `mcpcli ping <server> [server2...]` | Check specific server(s) |
90
92
  | `mcpcli add <name> --command <cmd>` | Add a stdio MCP server |
91
93
  | `mcpcli add <name> --url <url>` | Add an HTTP MCP server |
92
94
  | `mcpcli remove <name>` | Remove an MCP server |
95
+ | `mcpcli skill install --claude` | Install mcpcli skill for Claude |
package/README.md CHANGED
@@ -68,6 +68,9 @@ mcpcli search -q "manage pull requests"
68
68
  | `mcpcli add <name> --command <cmd>` | Add a stdio MCP server to your config |
69
69
  | `mcpcli add <name> --url <url>` | Add an HTTP MCP server to your config |
70
70
  | `mcpcli remove <name>` | Remove an MCP server from your config |
71
+ | `mcpcli ping` | Check connectivity to all configured servers |
72
+ | `mcpcli ping <server> [server2...]` | Check connectivity to specific server(s) |
73
+ | `mcpcli skill install --claude` | Install the mcpcli skill for Claude Code |
71
74
 
72
75
  ## Options
73
76
 
@@ -405,11 +408,20 @@ cat params.json | mcpcli exec server tool
405
408
 
406
409
  ### Claude Code Skill
407
410
 
408
- mcpcli ships a Claude Code skill at `skills/mcpcli.md` that teaches Claude Code how to discover and use MCP tools. Install it:
411
+ mcpcli ships a Claude Code skill at `.claude/skills/mcpcli.md` that teaches Claude Code how to discover and use MCP tools. Install it:
409
412
 
410
413
  ```bash
411
- # Copy the skill to your global Claude Code skills
412
- cp skills/mcpcli.md ~/.claude/skills/mcpcli.md
414
+ # Install to the current project (.claude/skills/mcpcli.md)
415
+ mcpcli skill install --claude
416
+
417
+ # Install globally (~/.claude/skills/mcpcli.md)
418
+ mcpcli skill install --claude --global
419
+
420
+ # Install to both locations
421
+ mcpcli skill install --claude --global --project
422
+
423
+ # Overwrite an existing skill file
424
+ mcpcli skill install --claude --force
413
425
  ```
414
426
 
415
427
  Then in any Claude Code session, the agent can use `/mcpcli` or the skill triggers automatically when the agent needs to interact with external services. The skill instructs the agent to:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evantahler/mcpcli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "A command-line interface for MCP servers. curl for MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "src",
11
- "skills",
11
+ ".claude",
12
12
  "README.md",
13
13
  "LICENSE"
14
14
  ],
package/src/cli.ts CHANGED
@@ -9,6 +9,8 @@ import { registerAuthCommand, registerDeauthCommand } from "./commands/auth.ts";
9
9
  import { registerIndexCommand } from "./commands/index.ts";
10
10
  import { registerAddCommand } from "./commands/add.ts";
11
11
  import { registerRemoveCommand } from "./commands/remove.ts";
12
+ import { registerSkillCommand } from "./commands/skill.ts";
13
+ import { registerPingCommand } from "./commands/ping.ts";
12
14
 
13
15
  declare const BUILD_VERSION: string | undefined;
14
16
 
@@ -33,5 +35,7 @@ registerDeauthCommand(program);
33
35
  registerIndexCommand(program);
34
36
  registerAddCommand(program);
35
37
  registerRemoveCommand(program);
38
+ registerSkillCommand(program);
39
+ registerPingCommand(program);
36
40
 
37
41
  program.parse();
@@ -0,0 +1,69 @@
1
+ import { green, red } from "ansis";
2
+ import type { Command } from "commander";
3
+ import { getContext } from "../context.ts";
4
+ import { formatError } from "../output/formatter.ts";
5
+ import { logger } from "../output/logger.ts";
6
+
7
+ interface PingResult {
8
+ server: string;
9
+ success: boolean;
10
+ latencyMs?: number;
11
+ error?: string;
12
+ }
13
+
14
+ export function registerPingCommand(program: Command) {
15
+ program
16
+ .command("ping [servers...]")
17
+ .description("Check connectivity to MCP servers")
18
+ .action(async (servers: string[]) => {
19
+ const { manager, formatOptions } = await getContext(program);
20
+
21
+ const targetServers = servers.length > 0 ? servers : manager.getServerNames();
22
+
23
+ if (targetServers.length === 0) {
24
+ console.error(formatError("No servers configured", formatOptions));
25
+ await manager.close();
26
+ process.exit(1);
27
+ }
28
+
29
+ const spinner = logger.startSpinner(
30
+ `Pinging ${targetServers.length} server(s)...`,
31
+ formatOptions,
32
+ );
33
+
34
+ const results: PingResult[] = [];
35
+
36
+ try {
37
+ await Promise.all(
38
+ targetServers.map(async (serverName) => {
39
+ const start = Date.now();
40
+ try {
41
+ await manager.getClient(serverName);
42
+ results.push({ server: serverName, success: true, latencyMs: Date.now() - start });
43
+ } catch (err) {
44
+ results.push({ server: serverName, success: false, error: String(err) });
45
+ }
46
+ }),
47
+ );
48
+
49
+ spinner.stop();
50
+
51
+ if (formatOptions.json) {
52
+ console.log(JSON.stringify(results, null, 2));
53
+ } else {
54
+ for (const r of results) {
55
+ if (r.success) {
56
+ console.log(`${green("✔")} ${r.server} connected (${r.latencyMs}ms)`);
57
+ } else {
58
+ console.log(`${red("✖")} ${r.server} failed: ${r.error}`);
59
+ }
60
+ }
61
+ }
62
+ } finally {
63
+ await manager.close();
64
+ }
65
+
66
+ const anyFailed = results.some((r) => !r.success);
67
+ if (anyFailed) process.exit(1);
68
+ });
69
+ }
@@ -0,0 +1,70 @@
1
+ import type { Command } from "commander";
2
+ import { resolve, dirname, join } from "path";
3
+ import { readFile, mkdir, writeFile, access } from "fs/promises";
4
+ import { homedir } from "os";
5
+
6
+ export function registerSkillCommand(program: Command) {
7
+ const skill = program.command("skill").description("manage mcpcli skills");
8
+
9
+ skill
10
+ .command("install")
11
+ .description("install the mcpcli skill for an AI agent")
12
+ .requiredOption("--claude", "install for Claude Code")
13
+ .option("--global", "install to ~/.claude/skills/")
14
+ .option("--project", "install to ./.claude/skills/ (default)")
15
+ .option("-f, --force", "overwrite if file already exists")
16
+ .action(
17
+ async (options: {
18
+ claude?: boolean;
19
+ global?: boolean;
20
+ project?: boolean;
21
+ force?: boolean;
22
+ }) => {
23
+ // Resolve the bundled skill file
24
+ const skillSource = resolve(dirname(Bun.main), "..", ".claude", "skills", "mcpcli.md");
25
+
26
+ let content: string;
27
+ try {
28
+ content = await readFile(skillSource, "utf-8");
29
+ } catch {
30
+ console.error(`Could not read skill file: ${skillSource}`);
31
+ process.exit(1);
32
+ }
33
+
34
+ // Determine targets — default to project if neither flag is set
35
+ const targets: { label: string; dir: string }[] = [];
36
+
37
+ if (options.global) {
38
+ targets.push({
39
+ label: "global",
40
+ dir: join(homedir(), ".claude", "skills"),
41
+ });
42
+ }
43
+ if (options.project || !options.global) {
44
+ targets.push({
45
+ label: "project",
46
+ dir: resolve(".claude", "skills"),
47
+ });
48
+ }
49
+
50
+ for (const target of targets) {
51
+ const dest = join(target.dir, "mcpcli.md");
52
+
53
+ // Check if file already exists
54
+ if (!options.force) {
55
+ try {
56
+ await access(dest);
57
+ console.error(`${dest} already exists (use --force to overwrite)`);
58
+ process.exit(1);
59
+ } catch {
60
+ // File doesn't exist — good
61
+ }
62
+ }
63
+
64
+ await mkdir(target.dir, { recursive: true });
65
+ await writeFile(dest, content, "utf-8");
66
+ console.log(`Installed mcpcli skill (${target.label}): ${dest}`);
67
+ }
68
+ },
69
+ );
70
+ }