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 +9 -7
- package/bin/captain-tool-setup +114 -10
- package/package.json +7 -6
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
|
|
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
|
|
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
|
|
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
|
-
[](vscode:mcp/install?%7B%22name%22%3A%22captain
|
|
94
|
+
[](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
|
|
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
|
|
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
|
|
140
|
+
"captain": {
|
|
139
141
|
"command": "captain-tool",
|
|
140
142
|
"env": {
|
|
141
143
|
"CAPTAIN_API_URL": "https://app.getcaptain.dev/"
|
package/bin/captain-tool-setup
CHANGED
|
@@ -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
|
|
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
|
|
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][
|
|
340
|
+
config[rootKey][SERVER_NAME] = buildEntryFormatA(binaryPath);
|
|
253
341
|
} else {
|
|
254
|
-
// Format B: { servers: { "captain
|
|
342
|
+
// Format B: { servers: { "captain": { type, command, args, env } } }
|
|
255
343
|
if (!config.servers) config.servers = {};
|
|
256
|
-
config.servers[
|
|
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
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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.
|
|
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.
|
|
14
|
-
"@captain-tool/captain-tool-darwin-x64": "0.0.
|
|
15
|
-
"@captain-tool/captain-tool-darwin-arm64": "0.0.
|
|
16
|
-
"@captain-tool/captain-tool-linux-x64": "0.0.
|
|
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/",
|