@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 +12 -0
- package/bin/claude-desktop-config.mjs +1 -1
- package/bin/cli.mjs +15 -0
- package/bin/install.mjs +144 -0
- package/package.json +3 -1
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" +
|
package/bin/install.mjs
ADDED
|
@@ -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.
|
|
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": {
|