captain-tool 0.0.30 → 0.0.32

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/README.md CHANGED
@@ -54,7 +54,7 @@ Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Appli
54
54
  ```json
55
55
  {
56
56
  "mcpServers": {
57
- "captain-tool": {
57
+ "captain": {
58
58
  "command": "captain-tool",
59
59
  "env": {
60
60
  "CAPTAIN_API_URL": "https://app.getcaptain.dev/"
@@ -68,7 +68,7 @@ Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Appli
68
68
  ```json
69
69
  {
70
70
  "mcpServers": {
71
- "captain-tool": {
71
+ "captain": {
72
72
  "command": "cmd",
73
73
  "args": ["/c", "captain-tool"],
74
74
  "env": {
@@ -84,19 +84,21 @@ Restart Claude Desktop to activate.
84
84
  ### Claude Code (CLI)
85
85
 
86
86
  ```bash
87
- claude mcp add captain-tool -- cmd /c captain-tool
87
+ claude mcp add captain -- cmd /c captain-tool
88
88
  ```
89
89
 
90
+ > Registers under the short name `captain` (tool prefix `mcp__captain__`). If you previously added it as `captain-tool`, run `claude mcp remove captain-tool` to avoid a duplicate registration.
91
+
90
92
  ### VS Code + Copilot
91
93
 
92
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_Server-0098FF?logo=visualstudiocode)](vscode:mcp/install?%7B%22name%22%3A%22captain-tool%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22captain-tool%22%5D%2C%22env%22%3A%7B%22CAPTAIN_API_URL%22%3A%22https%3A%2F%2Fapp.getcaptain.dev%2F%22%7D%7D)
94
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_Server-0098FF?logo=visualstudiocode)](vscode:mcp/install?%7B%22name%22%3A%22captain%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22captain-tool%22%5D%2C%22env%22%3A%7B%22CAPTAIN_API_URL%22%3A%22https%3A%2F%2Fapp.getcaptain.dev%2F%22%7D%7D)
93
95
 
94
96
  Or edit `%APPDATA%\Code\User\mcp.json` (Windows) / `~/.config/Code/User/mcp.json` (Linux) / `~/Library/Application Support/Code/User/mcp.json` (macOS):
95
97
 
96
98
  ```json
97
99
  {
98
100
  "servers": {
99
- "captain-tool": {
101
+ "captain": {
100
102
  "type": "stdio",
101
103
  "command": "captain-tool",
102
104
  "env": {
@@ -116,7 +118,7 @@ Edit `~/.cursor/mcp.json`:
116
118
  ```json
117
119
  {
118
120
  "mcpServers": {
119
- "captain-tool": {
121
+ "captain": {
120
122
  "command": "captain-tool",
121
123
  "env": {
122
124
  "CAPTAIN_API_URL": "https://app.getcaptain.dev/"
@@ -135,7 +137,7 @@ Edit `~/.codeium/windsurf/mcp_config.json` (macOS/Linux) or `%USERPROFILE%\.code
135
137
  ```json
136
138
  {
137
139
  "mcpServers": {
138
- "captain-tool": {
140
+ "captain": {
139
141
  "command": "captain-tool",
140
142
  "env": {
141
143
  "CAPTAIN_API_URL": "https://app.getcaptain.dev/"
@@ -25,6 +25,20 @@ const readline = require('readline');
25
25
 
26
26
  const CAPTAIN_API_URL = 'https://app.getcaptain.dev/';
27
27
 
28
+ // The MCP server registration key. This becomes the `mcp__<name>__` tool prefix
29
+ // the agent sees, so we keep it short: `captain`, not `captain-tool`. The npm
30
+ // package and binary are still named `captain-tool`; only the registration key
31
+ // is shortened.
32
+ const SERVER_NAME = 'captain';
33
+
34
+ // Names this server may have been registered under previously. On (re-)run we
35
+ // remove any of these so an agent never has to search two prefixes (e.g. both
36
+ // `mcp__captain__` and `mcp__captain-tool__`) — a duplicate that doubles tool
37
+ // discovery cost. We only delete an entry that actually points at our binary
38
+ // (see entryIsOurs), so an unrelated server a user happens to name `captain-tool`
39
+ // is left alone.
40
+ const LEGACY_NAMES = ['captain-tool'];
41
+
28
42
  // ── Binary resolution (same logic as the runner) ─────────────────────────────
29
43
 
30
44
  const PLATFORMS = [
@@ -232,10 +246,80 @@ function readJsonConfig(filePath) {
232
246
  }
233
247
  }
234
248
 
249
+ // ── Legacy-registration cleanup ───────────────────────────────────────────────
250
+
251
+ /**
252
+ * Return true if an existing MCP server entry points at OUR binary.
253
+ *
254
+ * Every entry we write invokes captain-tool one of three ways:
255
+ * - the binary directly → command basename is `captain-tool[.exe]`
256
+ * - `npx captain-tool` → an arg is exactly `captain-tool`
257
+ * - `cmd /c npx captain-tool`→ an arg is exactly `captain-tool`
258
+ *
259
+ * We match on the command BASENAME (not a substring) and on an EXACT arg, so an
260
+ * unrelated server that merely contains the substring "captain-tool" in a path
261
+ * (e.g. command `/opt/my-captain-tools/run`) is NOT matched. Combined with the
262
+ * caller only ever checking keys in LEGACY_NAMES, this keeps deletion tightly
263
+ * scoped to registrations this installer actually created.
264
+ */
265
+ function entryIsOurs(entry) {
266
+ if (!entry || typeof entry !== 'object') return false;
267
+ if (typeof entry.command === 'string') {
268
+ const base = entry.command.replace(/\\/g, '/').split('/').pop().toLowerCase();
269
+ if (base === 'captain-tool' || base === 'captain-tool.exe') return true;
270
+ }
271
+ if (Array.isArray(entry.args) && entry.args.some((a) => a === 'captain-tool')) {
272
+ return true;
273
+ }
274
+ return false;
275
+ }
276
+
277
+ /**
278
+ * Delete any LEGACY_NAMES entries from a single server-map (mcpServers / servers)
279
+ * that resolve to our binary, so re-running setup heals a duplicate registration.
280
+ */
281
+ function removeLegacyEntries(root) {
282
+ if (!root || typeof root !== 'object') return;
283
+ for (const legacy of LEGACY_NAMES) {
284
+ if (legacy !== SERVER_NAME && entryIsOurs(root[legacy])) {
285
+ delete root[legacy];
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Sweep legacy entries out of EVERY scope in a config object: the top-level
292
+ * server maps AND any per-project maps.
293
+ *
294
+ * WHY per-project: Claude Code's `~/.claude.json` stores MCP servers both at the
295
+ * top level (`mcpServers`) and per working directory under
296
+ * `projects[<cwd>].mcpServers`. The previous npm README registered with
297
+ * `claude mcp add captain-tool …` WITHOUT `-s user`, which defaults to *local*
298
+ * (project) scope — so the legacy duplicate often lives under `projects[*]`, not
299
+ * at the top level. Cleaning only the top level would leave exactly the
300
+ * two-prefix duplicate this is meant to remove.
301
+ */
302
+ function removeLegacyEverywhere(config) {
303
+ removeLegacyEntries(config.mcpServers);
304
+ removeLegacyEntries(config.servers);
305
+ if (config.projects && typeof config.projects === 'object') {
306
+ for (const proj of Object.values(config.projects)) {
307
+ if (proj && typeof proj === 'object') {
308
+ removeLegacyEntries(proj.mcpServers);
309
+ removeLegacyEntries(proj.servers);
310
+ }
311
+ }
312
+ }
313
+ }
314
+
235
315
  // ── Per-client config writer ──────────────────────────────────────────────────
236
316
 
237
317
  /**
238
- * Write (or update) the captain-tool entry in a single client's config file.
318
+ * Write (or update) the captain server entry in a single client's config file.
319
+ *
320
+ * Registers under SERVER_NAME ("captain") and removes any legacy registration
321
+ * (e.g. "captain-tool") that points at our binary, so the agent only ever sees
322
+ * one server prefix.
239
323
  *
240
324
  * Returns the absolute path that was written so the caller can print it.
241
325
  */
@@ -245,15 +329,19 @@ function configureClient(client, binaryPath) {
245
329
  // Read the existing config (or start with an empty object).
246
330
  const config = readJsonConfig(configPath);
247
331
 
332
+ // Heal duplicate registrations in every scope (top-level + per-project) before
333
+ // writing the canonical entry.
334
+ removeLegacyEverywhere(config);
335
+
248
336
  if (client.format === 'A') {
249
- // Format A: { mcpServers: { "captain-tool": { command, args, env } } }
337
+ // Format A: { mcpServers: { "captain": { command, args, env } } }
250
338
  const rootKey = client.rootKey || 'mcpServers';
251
339
  if (!config[rootKey]) config[rootKey] = {};
252
- config[rootKey]['captain-tool'] = buildEntryFormatA(binaryPath);
340
+ config[rootKey][SERVER_NAME] = buildEntryFormatA(binaryPath);
253
341
  } else {
254
- // Format B: { servers: { "captain-tool": { type, command, args, env } } }
342
+ // Format B: { servers: { "captain": { type, command, args, env } } }
255
343
  if (!config.servers) config.servers = {};
256
- config.servers['captain-tool'] = buildEntryFormatB(binaryPath);
344
+ config.servers[SERVER_NAME] = buildEntryFormatB(binaryPath);
257
345
  }
258
346
 
259
347
  atomicWriteJson(configPath, config);
@@ -366,10 +454,26 @@ async function main() {
366
454
  process.exit(1);
367
455
  }
368
456
 
369
- console.log('Setup complete. captain-tool is registered and ready to use.');
457
+ console.log('Setup complete. The captain server is registered and ready to use.');
458
+ }
459
+
460
+ // Only run the interactive wizard when invoked directly. Guarding on
461
+ // require.main lets tests `require()` this file to exercise the pure helpers
462
+ // (entryIsOurs / removeLegacyEntries / configureClient) without launching the
463
+ // stdin-driven prompt.
464
+ if (require.main === module) {
465
+ main().catch((err) => {
466
+ console.error('Unexpected error:', err);
467
+ process.exit(1);
468
+ });
370
469
  }
371
470
 
372
- main().catch((err) => {
373
- console.error('Unexpected error:', err);
374
- process.exit(1);
375
- });
471
+ module.exports = {
472
+ SERVER_NAME,
473
+ LEGACY_NAMES,
474
+ entryIsOurs,
475
+ removeLegacyEntries,
476
+ removeLegacyEverywhere,
477
+ configureClient,
478
+ resolveCommand,
479
+ };
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "captain-tool",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "MCP server connecting Claude Desktop and VS Code Copilot to Captain Cloud",
5
5
  "bin": {
6
6
  "captain-tool": "bin/captain-tool",
7
7
  "captain-tool-setup": "bin/captain-tool-setup"
8
8
  },
9
9
  "scripts": {
10
- "postinstall": "node scripts/postinstall.js"
10
+ "postinstall": "node scripts/postinstall.js",
11
+ "test": "node test/setup.test.js"
11
12
  },
12
13
  "optionalDependencies": {
13
- "@captain-tool/captain-tool-win32-x64": "0.0.30",
14
- "@captain-tool/captain-tool-darwin-x64": "0.0.30",
15
- "@captain-tool/captain-tool-darwin-arm64": "0.0.30",
16
- "@captain-tool/captain-tool-linux-x64": "0.0.30"
14
+ "@captain-tool/captain-tool-win32-x64": "0.0.32",
15
+ "@captain-tool/captain-tool-darwin-x64": "0.0.32",
16
+ "@captain-tool/captain-tool-darwin-arm64": "0.0.32",
17
+ "@captain-tool/captain-tool-linux-x64": "0.0.32"
17
18
  },
18
19
  "files": [
19
20
  "bin/",