@stealthmole/mcp 0.1.8 → 0.1.9

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
@@ -1,22 +1,176 @@
1
- # @stealthmole/mcp
1
+ # StealthMole MCP
2
2
 
3
- > Alias package for [`stealthmole.mcp`](https://www.npmjs.com/package/stealthmole.mcp)
4
-
5
- This package provides the **same functionality** as `stealthmole.mcp` under an alternative name.
6
- Use whichever name you prefer — both are maintained in sync.
3
+ Connect [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.sh), [Windsurf](https://codeium.com/windsurf), and other MCP clients to the [StealthMole](https://www.stealthmole.com) Threat Intelligence API.
7
4
 
8
5
  ## Quick Start
9
6
 
7
+ ### Claude Desktop
8
+
9
+ Simply run the following command — it will automatically register the MCP server in your Claude Desktop configuration:
10
+
11
+ ```bash
12
+ npx -y @stealthmole/mcp
13
+ ```
14
+
15
+ Or manually add to your config file:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "stealthmole": {
21
+ "command": "npx",
22
+ "args": ["-y", "@stealthmole/mcp"],
23
+ "env": {
24
+ "STEALTHMOLE_CLIENT_SECRET": "<your-client-secret>"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### Claude Code
32
+
33
+ Add to your project's `.mcp.json`:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "stealthmole": {
39
+ "command": "npx",
40
+ "args": ["-y", "@stealthmole/mcp"],
41
+ "env": {
42
+ "STEALTHMOLE_CLIENT_SECRET": "<your-client-secret>"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Other MCP Clients
50
+
51
+ Add the configuration above to your MCP client's config file:
52
+
53
+ | Client | Config File |
54
+ |--------|-------------|
55
+ | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
56
+ | Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
57
+ | Claude Desktop (Linux) | `~/.config/Claude/claude_desktop_config.json` |
58
+ | Cursor | `.cursor/mcp.json` in your project root |
59
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
60
+
61
+ ## How It Works
62
+
63
+ 1. Run `npx -y @stealthmole/mcp` (or your MCP client starts it automatically)
64
+ 2. On first run, it auto-registers itself in Claude Desktop's config (if not already registered)
65
+ 3. A browser window opens for StealthMole authentication via OAuth 2.1
66
+ 4. After login the browser is redirected to [stealthmole.com](https://www.stealthmole.com/), and the OAuth token is cached in `~/.mcp-auth/`
67
+ 5. The stdio-to-HTTP bridge connects your MCP client to the remote server
68
+
69
+ ## Requirements
70
+
71
+ - **Node.js 20.18.1+**
72
+ - A StealthMole account with API access
73
+ - `STEALTHMOLE_CLIENT_SECRET` environment variable (optional, for confidential client flow)
74
+
75
+ ## Available Tools
76
+
77
+ Once connected, your MCP client will have access to the following tools:
78
+
79
+ ### Dark Web Tracker
80
+
81
+ | Tool | Description |
82
+ |------|-------------|
83
+ | `dark_web_search` | Search dark web content by indicator (domain, email, keyword, etc.) |
84
+ | `dark_web_targets` | Get available search targets for a given indicator |
85
+ | `download_dark_web_file` | Download a file by SHA256 hash |
86
+
87
+ ### Telegram Tracker
88
+
89
+ | Tool | Description |
90
+ |------|-------------|
91
+ | `telegram_search` | Search Telegram content by indicator |
92
+ | `telegram_targets` | Get available search targets for a given indicator |
93
+ | `download_telegram_file` | Download a file by SHA256 hash |
94
+
95
+ ### Credential Lookout
96
+
97
+ | Tool | Description |
98
+ |------|-------------|
99
+ | `credential_search` | Search for leaked credentials |
100
+ | `credential_export` | Export leaked credentials to CSV/JSON |
101
+
102
+ ### Compromised Dataset
103
+
104
+ | Tool | Description |
105
+ |------|-------------|
106
+ | `compromised_dataset_search` | Search compromised data with host/URL info |
107
+ | `compromised_dataset_node` | Get detailed node information |
108
+ | `compromised_dataset_export` | Export compromised data to CSV/JSON |
109
+
110
+ ### Combo Binder
111
+
112
+ | Tool | Description |
113
+ |------|-------------|
114
+ | `combo_binder_search` | Search leaked ID/Password combinations |
115
+ | `combo_binder_export` | Export combo data to CSV/JSON |
116
+
117
+ ### ULP Binder
118
+
119
+ | Tool | Description |
120
+ |------|-------------|
121
+ | `ulp_binder_search` | Search URL-Login-Password combinations |
122
+ | `ulp_binder_export` | Export ULP data to CSV/JSON |
123
+
124
+ ### Monitoring
125
+
126
+ | Tool | Description |
127
+ |------|-------------|
128
+ | `ransomware_monitoring_search` | Track ransomware activities and attack groups |
129
+ | `government_monitoring_search` | Monitor government sector threats |
130
+ | `leaked_monitoring_search` | Monitor enterprise data leaks |
131
+
132
+ ### Utilities
133
+
134
+ | Tool | Description |
135
+ |------|-------------|
136
+ | `node_details` | Get detailed information about a specific node from any service |
137
+ | `search_pagination` | Paginate through large result sets (DT/TT) |
138
+ | `search_targets` | Get available targets for any service and indicator |
139
+ | `api_usage` | Check API usage quotas by service |
140
+
141
+ ## CLI Options
142
+
10
143
  ```bash
11
- npx @stealthmole/mcp
144
+ npx -y @stealthmole/mcp --debug # Enable debug logging
145
+ npx -y @stealthmole/mcp --port 3335 # Use custom callback port
146
+ npx -y @stealthmole/mcp --callback-port 3335 # Alias for --port
147
+ npx -y @stealthmole/mcp --help # Show help message
148
+ npx -y @stealthmole/mcp --version # Show version number
12
149
  ```
13
150
 
14
- Or install globally:
151
+ > A free callback port is automatically selected if `--port` is not specified.
152
+ > Only the flags listed above are supported; other flags are silently ignored.
153
+
154
+ ## Troubleshooting
155
+
156
+ ### Reset authentication
157
+
158
+ Delete the cached token directory `~/.mcp-auth/`, then restart your MCP client to re-authenticate.
159
+
160
+ ### Connection issues
161
+
162
+ 1. Ensure Node.js 20.18.1+ is installed: `node --version`
163
+ 2. Try with debug logging: `npx -y @stealthmole/mcp --debug`
164
+ 3. Check that your StealthMole account has API access
165
+
166
+ ### Port conflicts
167
+
168
+ If you see "Port is already in use" errors, the client will automatically retry with a different port. You can also specify a port manually:
15
169
 
16
170
  ```bash
17
- npm install -g @stealthmole/mcp
171
+ npx -y @stealthmole/mcp --port 3335
18
172
  ```
19
173
 
20
- ## Documentation
174
+ ## License
21
175
 
22
- See the main package for full documentation: [stealthmole.mcp](https://www.npmjs.com/package/stealthmole.mcp)
176
+ MIT
package/bin/cli.mjs CHANGED
@@ -20,7 +20,7 @@ const PKG_VERSION = JSON.parse(
20
20
  ).version;
21
21
 
22
22
  /**
23
- * Auto-register stealthmole.mcp in Claude Desktop's claude_desktop_config.json
23
+ * Auto-register the StealthMole MCP server in Claude Desktop's claude_desktop_config.json
24
24
  * if not already present. Returns true if newly registered, false otherwise.
25
25
  */
26
26
  function ensureClaudeDesktopConfig() {
@@ -38,7 +38,7 @@ function ensureClaudeDesktopConfig() {
38
38
  } else if (os === "win32") {
39
39
  if (!process.env.APPDATA) {
40
40
  console.error(
41
- "[stealthmole.mcp] APPDATA environment variable not set, skipping config registration."
41
+ "[stealthmole-mcp] APPDATA environment variable not set, skipping config registration."
42
42
  );
43
43
  return false;
44
44
  }
@@ -96,21 +96,21 @@ function ensureClaudeDesktopConfig() {
96
96
  }
97
97
 
98
98
  console.error(
99
- "\n[stealthmole.mcp] ✔ Registered stealthmole.mcp in Claude Desktop settings."
99
+ "\n[stealthmole-mcp] ✔ Registered stealthmole.mcp in Claude Desktop settings."
100
100
  );
101
101
  console.error(
102
- "[stealthmole.mcp] Please restart Claude Desktop to apply the changes.\n"
102
+ "[stealthmole-mcp] Please restart Claude Desktop to apply the changes.\n"
103
103
  );
104
104
  return true;
105
105
  } catch (err) {
106
106
  // Config write failed — warn and continue normal execution
107
107
  if (process.argv.includes("--debug")) {
108
108
  console.error(
109
- `[stealthmole.mcp] ⚠ Failed to auto-register in Claude Desktop settings: ${err.message}`
109
+ `[stealthmole-mcp] ⚠ Failed to auto-register in Claude Desktop settings: ${err.message}`
110
110
  );
111
111
  } else {
112
112
  console.error(
113
- "[stealthmole.mcp] ⚠ Failed to auto-register in Claude Desktop settings."
113
+ "[stealthmole-mcp] ⚠ Failed to auto-register in Claude Desktop settings."
114
114
  );
115
115
  }
116
116
  return false;
@@ -125,13 +125,20 @@ if (newlyRegistered) {
125
125
  }
126
126
 
127
127
  const SERVER_URL = "https://mcp.stealthmole.com/mcp";
128
+ // OAuth 인증 완료 후 브라우저가 이동할 주소.
129
+ // mcp-remote 가 303 으로 리다이렉트하므로 "이 창을 닫으세요" 안내가 노출되지 않음.
130
+ const AUTH_SUCCESS_URL = "https://www.stealthmole.com/";
131
+ // mcp-remote 0.15 부터 win32 기본 콜백 호스트가 127.0.0.1 로 바뀌었다.
132
+ // Keycloak stealthmole_front 의 Valid Redirect URIs 가 localhost 기준이므로
133
+ // 플랫폼 무관하게 localhost 로 고정해 redirect_uri 를 기존과 동일하게 유지한다.
134
+ const CALLBACK_HOST = "localhost";
128
135
  const DEBUG = process.argv.includes("--debug");
129
136
 
130
137
  // Handle --help and --version before argv filtering
131
138
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
132
139
  console.error(
133
- `stealthmole.mcp v${PKG_VERSION}\n` +
134
- "\nUsage: npx stealthmole.mcp [options]\n" +
140
+ `@stealthmole/mcp v${PKG_VERSION}\n` +
141
+ "\nUsage: npx -y @stealthmole/mcp [options]\n" +
135
142
  "\nOptions:\n" +
136
143
  " --port <number> Callback port for OAuth redirect\n" +
137
144
  " --callback-port <number> Alias for --port\n" +
@@ -211,15 +218,15 @@ function findFreePort(maxAttempts = 20) {
211
218
  process.on("uncaughtException", (err) => {
212
219
  if (err.code === "EADDRINUSE") {
213
220
  console.error(
214
- `[stealthmole.mcp] Port ${err.port ?? ""} is already in use. ` +
221
+ `[stealthmole-mcp] Port ${err.port ?? ""} is already in use. ` +
215
222
  "Retrying should resolve this. " +
216
- "To use a specific port: npx stealthmole.mcp --port <number>"
223
+ "To use a specific port: npx -y @stealthmole/mcp --port <number>"
217
224
  );
218
225
  process.exit(1);
219
226
  }
220
227
  // Re-throwing inside uncaughtException causes undefined behavior in Node.js.
221
228
  // Log and exit instead.
222
- console.error("[stealthmole.mcp] Unexpected error:", err);
229
+ console.error("[stealthmole-mcp] Unexpected error:", err);
223
230
  process.exit(1);
224
231
  });
225
232
 
@@ -261,6 +268,10 @@ const CLIENT_METADATA = JSON.stringify({
261
268
  const ALLOWED_FLAGS = new Set(["--port", "--callback-port", "--debug"]);
262
269
  const VALUE_FLAGS = new Set(["--port", "--callback-port"]);
263
270
  const filteredArgs = [];
271
+ // mcp-remote accepts the callback port ONLY as a positional argument
272
+ // (`<server-url> [callback-port]`), so the flag value is held here and injected
273
+ // as a positional below. Forwarding `--port` itself would be silently ignored.
274
+ let requestedPort = null;
264
275
  {
265
276
  const rawArgs = process.argv.slice(2);
266
277
  for (let i = 0; i < rawArgs.length; i++) {
@@ -274,7 +285,7 @@ const filteredArgs = [];
274
285
  i++;
275
286
  continue;
276
287
  }
277
- filteredArgs.push(rawArgs[i], val);
288
+ requestedPort = port;
278
289
  i++;
279
290
  } else {
280
291
  filteredArgs.push(rawArgs[i]);
@@ -287,27 +298,26 @@ const filteredArgs = [];
287
298
  process.argv.length = 2;
288
299
  process.argv.push(...filteredArgs);
289
300
 
290
- // Check if the user specified a callback port explicitly
291
- const userSpecifiedPort =
292
- process.argv.includes("--port") || process.argv.includes("--callback-port");
293
-
294
301
  // Inject server URL as the first positional argument for mcp-remote
295
302
  process.argv.splice(2, 0, SERVER_URL);
296
303
 
297
- // If no port was specified, auto-discover a free port and inject it.
304
+ // If no port was specified, auto-discover a free port.
298
305
  // The guard server holds the port until mcp-remote is ready to bind.
299
306
  let guardServer = null;
300
- if (!userSpecifiedPort) {
301
- const { port: callbackPort, server } = await findFreePort();
307
+ if (requestedPort === null) {
308
+ const { port, server } = await findFreePort();
302
309
  guardServer = server;
303
- process.argv.splice(3, 0, String(callbackPort));
310
+ requestedPort = port;
304
311
  if (DEBUG) {
305
- console.error(
306
- `Using automatically selected callback port: ${callbackPort}`
307
- );
312
+ console.error(`Using automatically selected callback port: ${requestedPort}`);
308
313
  }
309
314
  }
310
315
 
316
+ // Second positional argument: the callback port
317
+ process.argv.splice(3, 0, String(requestedPort));
318
+
319
+ process.argv.push("--auth-success-url", AUTH_SUCCESS_URL);
320
+ process.argv.push("--host", CALLBACK_HOST);
311
321
  process.argv.push("--static-oauth-client-info", CLIENT_INFO);
312
322
  process.argv.push("--static-oauth-client-metadata", CLIENT_METADATA);
313
323
 
package/package.json CHANGED
@@ -1,26 +1,21 @@
1
1
  {
2
2
  "name": "@stealthmole/mcp",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "StealthMole Threat Intelligence MCP client - connect Claude Desktop, Cursor, and other MCP clients to StealthMole API",
5
5
  "bin": {
6
- "stealthmole-mcp": "./bin/cli.mjs",
7
- "stealthmole.mcp": "./bin/cli.mjs"
8
- },
9
- "scripts": {
10
- "postinstall": "node scripts/patch-oauth-callback.mjs"
6
+ "stealthmole-mcp": "bin/cli.mjs"
11
7
  },
12
8
  "type": "module",
13
9
  "license": "MIT",
14
10
  "homepage": "https://www.stealthmole.com",
15
11
  "files": [
16
- "bin/",
17
- "scripts/"
12
+ "bin/"
18
13
  ],
19
14
  "dependencies": {
20
- "mcp-remote": "0.1.38"
15
+ "mcp-remote": "0.15.1"
21
16
  },
22
17
  "engines": {
23
- "node": ">=18.0.0"
18
+ "node": ">=20.18.1"
24
19
  },
25
20
  "keywords": [
26
21
  "mcp",
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Postinstall script that patches mcp-remote's OAuth callback page
5
- * to redirect to https://www.stealthmole.com/ after successful authentication,
6
- * instead of showing a "close this window" message.
7
- *
8
- * Safety:
9
- * - Uses exact string matching (not greedy regex) to minimize unintended replacements
10
- * - Verifies the result still contains the auth-code-received event emission
11
- * - Non-fatal: CLI works normally even if patching fails
12
- */
13
-
14
- import { readdirSync, readFileSync, writeFileSync } from "fs";
15
- import { join, dirname } from "path";
16
- import { fileURLToPath } from "url";
17
- import { createRequire } from "module";
18
-
19
- // pnpm 호환: require.resolve로 mcp-remote의 실제 위치를 찾음
20
- // (pnpm은 node_modules를 symlink 구조로 관리하므로 상대 경로가 동작하지 않음)
21
- const require = createRequire(import.meta.url);
22
- const distDir = join(dirname(require.resolve("mcp-remote/package.json")), "dist");
23
-
24
- // Exact string that identifies the callback response in mcp-remote
25
- const OLD_RESPONSE = `res.send(\`
26
- Authorization successful!
27
- You may close this window and return to the CLI.
28
- <script>
29
- // If this is a non-interactive session (no manual approval step was required) then
30
- // this should automatically close the window. If not, this will have no effect and
31
- // the user will see the message above.
32
- window.close();
33
- </script>
34
- \`)`;
35
-
36
- const NEW_HTML = [
37
- "<!DOCTYPE html>",
38
- '<html lang="en">',
39
- "<head>",
40
- ' <meta charset="utf-8">',
41
- ' <meta http-equiv="refresh" content="3;url=https://www.stealthmole.com/">',
42
- " <title>Authorization Successful - StealthMole</title>",
43
- " <style>",
44
- " body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#0f172a;color:#e2e8f0}",
45
- " .card{text-align:center;padding:2.5rem 3rem;background:#1e293b;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.3)}",
46
- " h1{font-size:1.4rem;margin:0 0 .8rem;color:#34d399}",
47
- " p{margin:0;font-size:.95rem;color:#94a3b8}",
48
- " span{color:#34d399;font-weight:600}",
49
- " </style>",
50
- "</head>",
51
- "<body>",
52
- ' <div class="card">',
53
- " <h1>Authorization Successful</h1>",
54
- ' <p>Redirecting to StealthMole in <span id="c">3</span>s\\u2026</p>',
55
- " </div>",
56
- " <script>",
57
- ' var t=3,e=document.getElementById("c"),i=setInterval(function(){if(--t<=0){clearInterval(i);location.href="https://www.stealthmole.com/"}e.textContent=t},1e3);',
58
- " </script>",
59
- "</body>",
60
- "</html>",
61
- ].join("\n");
62
-
63
- const NEW_RESPONSE = "res.send(`" + NEW_HTML + "`)";
64
-
65
- // Post-patch integrity marker: this line must exist after the patched res.send()
66
- const INTEGRITY_MARKER = 'options.events.emit("auth-code-received"';
67
-
68
- try {
69
- const files = readdirSync(distDir).filter((f) => f.endsWith(".js"));
70
- let patched = false;
71
-
72
- for (const file of files) {
73
- const filePath = join(distDir, file);
74
- const content = readFileSync(filePath, "utf-8");
75
-
76
- if (!content.includes(OLD_RESPONSE)) continue;
77
-
78
- const newContent = content.replace(OLD_RESPONSE, NEW_RESPONSE);
79
-
80
- // Verify: the replacement should change exactly one occurrence
81
- if (newContent === content) continue;
82
-
83
- // Integrity check: auth-code-received event must still be present
84
- if (!newContent.includes(INTEGRITY_MARKER)) {
85
- console.error(
86
- `[stealthmole.mcp] Patch integrity check failed for ${file}, skipping.`
87
- );
88
- continue;
89
- }
90
-
91
- writeFileSync(filePath, newContent, "utf-8");
92
- console.error(
93
- `[stealthmole.mcp] Patched OAuth callback page in ${file}`
94
- );
95
- patched = true;
96
- }
97
-
98
- if (!patched) {
99
- console.error(
100
- "[stealthmole.mcp] OAuth callback page already patched or pattern not found."
101
- );
102
- }
103
- } catch {
104
- // Non-fatal: if patching fails the CLI still works, just shows the default page
105
- console.error(
106
- "[stealthmole.mcp] Could not patch OAuth callback page."
107
- );
108
- }