@stealthmole/mcp 0.1.13 → 0.1.15

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
@@ -4,6 +4,18 @@ Connect [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs
4
4
 
5
5
  ## Quick Start
6
6
 
7
+ ### All MCP clients at once
8
+
9
+ Register StealthMole in the MCP clients installed on this machine (Claude Desktop, Claude Code, Cursor, Codex, VS Code, Windsurf, and more):
10
+
11
+ ```bash
12
+ npx -y @stealthmole/mcp install
13
+ ```
14
+
15
+ A checklist of the detected clients appears, all checked: press Space to uncheck a client, Enter to register. Without a terminal (scripts, CI), every detected client is registered.
16
+
17
+ Or name clients with `-a` (repeatable) to skip the checklist, e.g. `npx -y @stealthmole/mcp install -a cursor -a codex`. Run `npx -y @stealthmole/mcp install --help` for the full client list. A client that already runs StealthMole is left unchanged, so running it again is safe. Restart your clients afterwards.
18
+
7
19
  ### Claude Desktop
8
20
 
9
21
  Simply run the following command — it will automatically register the MCP server in your Claude Desktop configuration:
@@ -46,7 +46,7 @@ const SERVER_PACKAGE_ARG = /^(?:@stealthmole\/mcp|stealthmole\.mcp)(@.+)?$/;
46
46
  * @param {object} entry an `mcpServers` entry
47
47
  * @returns {boolean}
48
48
  */
49
- function launchesThisServer(entry) {
49
+ export function launchesThisServer(entry) {
50
50
  return Array.isArray(entry?.args) && entry.args.some((arg) => SERVER_PACKAGE_ARG.test(arg));
51
51
  }
52
52
 
package/bin/cli.mjs CHANGED
@@ -15,6 +15,20 @@ const PKG_VERSION = JSON.parse(
15
15
  readFileSync(new URL("../package.json", import.meta.url), "utf-8")
16
16
  ).version;
17
17
 
18
+ // `install` owns everything after it on the command line (-a <agent>, its own
19
+ // --help), so it is routed before the server's flags are read. Loaded lazily:
20
+ // the MCP server launch path never imports add-mcp.
21
+ if (process.argv[2] === "install") {
22
+ const installNodeProblem = unsupportedNodeMessage();
23
+ if (installNodeProblem) {
24
+ // Registering an entry this Node cannot run is worse than not registering.
25
+ console.error(installNodeProblem);
26
+ process.exit(1);
27
+ }
28
+ const { install } = await import("./install.mjs");
29
+ process.exit(await install({ pkgVersion: PKG_VERSION, args: process.argv.slice(3) }));
30
+ }
31
+
18
32
  // --help and --version come first. Both are diagnostics a customer runs to
19
33
  // report what they have installed, so nothing below may run ahead of them:
20
34
  // a fresh registration exits the process, and argv filtering rewrites
@@ -23,6 +37,7 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
23
37
  console.error(
24
38
  `@stealthmole/mcp v${PKG_VERSION}\n` +
25
39
  "\nUsage: npx -y @stealthmole/mcp [options]\n" +
40
+ " npx -y @stealthmole/mcp install [-a <agent>]... Register in MCP clients\n" +
26
41
  "\nOptions:\n" +
27
42
  " --port <number> Callback port for OAuth redirect\n" +
28
43
  " --callback-port <number> Alias for --port\n" +
@@ -0,0 +1,144 @@
1
+ // `npx -y @stealthmole/mcp install [-a <agent>]...` — register this server in
2
+ // the MCP clients on the machine (Claude Desktop, Claude Code, Cursor, Codex, …)
3
+ // through add-mcp's library, which knows each client's config path and format.
4
+ //
5
+ // -a given ─────────────────────────────┐
6
+ // terminal: detected ─▶ checkbox pick ──┼─▶ listInstalledServers ──┬─ already launches us ─▶ skip
7
+ // no terminal: all detected ────────────┘ └─ not yet ─────────────▶ upsertServer
8
+ //
9
+ // Skipping is the point: add-mcp's own CLI overwrites a same-named entry (the
10
+ // customer's `env` is lost) and adds a second one under a new name (every tool
11
+ // shows up twice). Identity is the package an entry launches, as in
12
+ // claude-desktop-config.mjs.
13
+
14
+ import { multiselect } from "@clack/prompts";
15
+ import {
16
+ agents,
17
+ detectGlobalAgents,
18
+ getAgentTypes,
19
+ listInstalledServers,
20
+ upsertServer,
21
+ } from "add-mcp";
22
+
23
+ import { SERVER_KEY, launchesThisServer } from "./claude-desktop-config.mjs";
24
+
25
+ const LOG_PREFIX = "[stealthmole-mcp]";
26
+ const AGENT_FLAGS = new Set(["-a", "--agent"]);
27
+
28
+ const USAGE =
29
+ "Usage: npx -y @stealthmole/mcp install [-a <agent>]...\n" +
30
+ "\nRegisters StealthMole MCP in the clients named with -a. Without -a, a\n" +
31
+ "terminal asks which detected clients to use; otherwise all are used.\n" +
32
+ "Clients that already run it are left unchanged.\n" +
33
+ `\nAgents: ${getAgentTypes().join(", ")}\n`;
34
+
35
+ /**
36
+ * Collect `-a <agent>` / `--agent <agent>` values.
37
+ *
38
+ * @param {string[]} args argv after `install`
39
+ * @returns {string[]}
40
+ */
41
+ export function parseAgents(args) {
42
+ return args.flatMap((arg, i) => (AGENT_FLAGS.has(arg) && args[i + 1] ? [args[i + 1]] : []));
43
+ }
44
+
45
+ /**
46
+ * Decide which agents to register in.
47
+ *
48
+ * @param {{
49
+ * requested: string[],
50
+ * detect: () => Promise<string[]>,
51
+ * interactive: boolean,
52
+ * pick: (detected: string[]) => Promise<string[] | symbol>,
53
+ * }} options
54
+ * @returns {Promise<string[] | null>} agents; [] when none detected, null when the user picked none
55
+ */
56
+ export async function chooseTargets({ requested, detect, interactive, pick }) {
57
+ if (requested.length) return requested;
58
+ const detected = await detect();
59
+ // ponytail: no terminal keeps 0.1.14's register-all so scripted runs don't break.
60
+ if (!interactive || !detected.length) return detected;
61
+ const picked = await pick(detected);
62
+ // A non-array is clack's cancel symbol (Ctrl+C / Esc).
63
+ return Array.isArray(picked) && picked.length ? picked : null;
64
+ }
65
+
66
+ /**
67
+ * Checkbox prompt over the detected agents, all pre-checked so Enter keeps
68
+ * the old "every client" result.
69
+ *
70
+ * @param {string[]} detected agent types
71
+ * @returns {Promise<string[] | symbol>} picked agent types, or clack's cancel symbol
72
+ */
73
+ function pickAgents(detected) {
74
+ return multiselect({
75
+ message: "Register StealthMole MCP in (space: toggle, enter: confirm)",
76
+ options: detected.map((type) => ({ value: type, label: agents[type].displayName })),
77
+ initialValues: detected,
78
+ required: false,
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Register this server in each chosen agent's global config.
84
+ *
85
+ * @param {{pkgVersion: string, args: string[]}} options
86
+ * @returns {Promise<number>} process exit code
87
+ */
88
+ export async function install({ pkgVersion, args }) {
89
+ if (args.includes("--help") || args.includes("-h")) {
90
+ console.error(USAGE);
91
+ return 0;
92
+ }
93
+
94
+ const known = new Set(getAgentTypes());
95
+ const requested = parseAgents(args);
96
+ const unknown = requested.filter((agent) => !known.has(agent));
97
+ if (unknown.length) {
98
+ console.error(`${LOG_PREFIX} Unknown agent: ${unknown.join(", ")}\n\n${USAGE}`);
99
+ return 1;
100
+ }
101
+
102
+ const targets = await chooseTargets({
103
+ requested,
104
+ detect: detectGlobalAgents,
105
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
106
+ pick: pickAgents,
107
+ });
108
+ if (targets === null) {
109
+ console.error(`${LOG_PREFIX} Nothing selected; nothing registered.`);
110
+ return 0;
111
+ }
112
+ if (!targets.length) {
113
+ console.error(`${LOG_PREFIX} No MCP clients found. Name one with -a <agent>.\n\n${USAGE}`);
114
+ return 1;
115
+ }
116
+
117
+ const entry = { command: "npx", args: ["-y", `@stealthmole/mcp@^${pkgVersion}`] };
118
+ let failures = 0;
119
+ let registered = 0;
120
+ for (const agent of await listInstalledServers({ global: true, agents: targets })) {
121
+ const label = `${agent.displayName} (${agent.configPath})`;
122
+ if (agent.error) {
123
+ failures += 1;
124
+ console.error(`${LOG_PREFIX} ✘ ${label}: ${agent.error}`);
125
+ continue;
126
+ }
127
+ const existing = agent.servers.find((server) => launchesThisServer(server.config));
128
+ if (existing) {
129
+ console.error(`${LOG_PREFIX} • ${label}: already registered as "${existing.serverName}"`);
130
+ continue;
131
+ }
132
+ const result = upsertServer(agent.agentType, SERVER_KEY, entry, { local: false });
133
+ if (result.success) {
134
+ registered += 1;
135
+ console.error(`${LOG_PREFIX} ✔ ${label}: registered "${SERVER_KEY}"`);
136
+ } else {
137
+ failures += 1;
138
+ console.error(`${LOG_PREFIX} ✘ ${label}: ${result.error}`);
139
+ }
140
+ }
141
+
142
+ if (registered) console.error(`\n${LOG_PREFIX} Restart your MCP clients to load StealthMole.`);
143
+ return failures ? 1 : 0;
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stealthmole/mcp",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "StealthMole Threat Intelligence MCP client - connect Claude Desktop, Cursor, and other MCP clients to StealthMole API",
5
5
  "bin": {
6
6
  "stealthmole-mcp": "bin/cli.mjs"
@@ -12,6 +12,8 @@
12
12
  "bin/"
13
13
  ],
14
14
  "dependencies": {
15
+ "@clack/prompts": "0.9.1",
16
+ "add-mcp": "2.6.1",
15
17
  "mcp-remote": "0.15.1"
16
18
  },
17
19
  "engines": {