@sentinelsup/mcp 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +27 -10
  2. package/index.js +54 -25
  3. package/package.json +7 -2
package/README.md CHANGED
@@ -1,17 +1,19 @@
1
1
  # Maskbreak MCP Server
2
2
 
3
- Check any IP address for fraud signals — VPN, proxy, Tor, datacenter hosting, anonymity — straight from Claude, Cursor, or any MCP client. Powered by the [Maskbreak](https://maskbreak.com) fraud detection API.
3
+ Look up limited public IP intelligence — cloud-hosting ranges and Tor exit nodes — from Claude, Cursor, or another MCP client. Powered by the [Maskbreak](https://maskbreak.com) fraud detection API. Requires Node.js 18+ for the local server.
4
4
 
5
5
  ## Tools
6
6
 
7
7
  | Tool | What it does |
8
8
  |------|--------------|
9
- | `lookup_ip` | Verdict for any public IPv4/IPv6: `allow` / `review` / `block`, 0–100 risk score, VPN/proxy/Tor/datacenter/anonymity signals, ASN + org + country |
9
+ | `lookup_ip` | Public IPv4/IPv6 lookup: `known`, `allow` / `review` / `block`, risk score, and nullable signal/network metadata. Public-feed coverage is limited to cloud hosting and Tor. |
10
10
  | `service_status` | Maskbreak API health, uptime, and latency |
11
11
 
12
12
  ## Setup
13
13
 
14
- Grab a free API key at [maskbreak.com/signup](https://maskbreak.com/signup) (1,000 lookups/hour, no card). The server also works without a key for a few lookups per minute — enough to try it.
14
+ Grab a free API key at [maskbreak.com/signup](https://maskbreak.com/signup) (1,000 lookups/hour, shared with evaluations, no card). Keyless mode uses the free web-tool endpoint, limited to 12 lookups/minute and 80/day per caller IP. Additional endpoint and abuse-protection limits can apply.
15
+
16
+ Neither mode performs a browser visit. VPN/proxy and device evidence require a browser-SDK-backed `/v1/evaluate` request outside these tools; service names are available only when known. Unknown IPs, false signals and `allow` verdicts are not proof of safety.
15
17
 
16
18
  ### Claude Code
17
19
 
@@ -45,16 +47,18 @@ Prefer a remote server? The same tools are hosted at **`https://maskbreak.com/mc
45
47
 
46
48
  ## Example
47
49
 
48
- > "Is 185.220.101.34 safe to allow through signup?"
50
+ > "What public-feed evidence is available for this IP?"
51
+
52
+ Illustrative unknown-IP response, not a live lookup:
49
53
 
50
54
  ```json
51
55
  {
52
- "ip": "185.220.101.34",
53
- "known": true,
54
- "verdict": "block",
55
- "risk_score": 90,
56
- "signals": { "vpn": false, "proxied": false, "tor": true, "dch": true, "anon": true },
57
- "network": { "asn": 205100, "org": "F3 Netze e.V.", "country": "DE" }
56
+ "ip": "192.0.2.1",
57
+ "known": false,
58
+ "verdict": "allow",
59
+ "risk_score": 0,
60
+ "signals": null,
61
+ "network": null
58
62
  }
59
63
  ```
60
64
 
@@ -65,6 +69,19 @@ Prefer a remote server? The same tools are hosted at **`https://maskbreak.com/mc
65
69
  | `SENTINEL_API_KEY` | recommended | — | API key from your [dashboard](https://maskbreak.com/dashboard); unlocks 1,000 lookups/hour |
66
70
  | `SENTINEL_BASE_URL` | no | `https://maskbreak.com` | Override for testing |
67
71
 
72
+ ## Failures and local checks
73
+
74
+ Both tools return MCP `isError: true` for HTTP failures, malformed responses or requests that exceed the five-second timeout (including body reads). An unavailable lookup is not an allow decision.
75
+
76
+ ```bash
77
+ npm ci --ignore-scripts
78
+ npm test
79
+ npm pack --dry-run --ignore-scripts
80
+ npm audit
81
+ ```
82
+
83
+ Tests use a real stdio MCP client with loopback HTTP fixtures, without production keys. CI checks Node.js 18, 22 and 24. No package is published by these checks.
84
+
68
85
  ## Links
69
86
 
70
87
  - [API docs](https://maskbreak.com/api)
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Sentinel MCP server — IP fraud intelligence for MCP clients (Claude Code,
2
+ // Maskbreak MCP server — IP fraud intelligence for MCP clients (Claude Code,
3
3
  // Claude Desktop, Cursor, ...). One stdio server, two tools.
4
4
  //
5
5
  // With SENTINEL_API_KEY set, lookups go through the authenticated API
@@ -9,44 +9,74 @@
9
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
11
  import { z } from 'zod';
12
+ import { readFileSync } from 'node:fs';
12
13
 
13
- const BASE = process.env.SENTINEL_BASE_URL || 'https://maskbreak.com';
14
+ // Read the version rather than hardcoding it — it had drifted to 0.1.0 while
15
+ // the package was 0.1.2, so every client saw the wrong version.
16
+ const { version: VERSION } = JSON.parse(
17
+ readFileSync(new URL('./package.json', import.meta.url), 'utf8')
18
+ );
19
+
20
+ const BASE = (process.env.SENTINEL_BASE_URL || 'https://maskbreak.com').replace(/\/+$/, '');
14
21
  const API_KEY = process.env.SENTINEL_API_KEY || '';
22
+ const TIMEOUT_MS = 5000;
23
+
24
+ async function requestJson(path, init = {}, keylessLookup = false) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
27
+ try {
28
+ const res = await fetch(`${BASE}${path}`, {
29
+ ...init,
30
+ headers: { 'Accept': 'application/json', ...init.headers },
31
+ signal: controller.signal
32
+ });
33
+ let data;
34
+ try {
35
+ data = await res.json();
36
+ } catch (error) {
37
+ if (error.name === 'AbortError') throw error;
38
+ data = null;
39
+ }
40
+ if (!res.ok) {
41
+ const hint = keylessLookup && res.status === 429
42
+ ? ' (keyless mode is tightly rate-limited — set SENTINEL_API_KEY for 1,000 lookups/hour; free key at https://maskbreak.com/signup)'
43
+ : '';
44
+ throw new Error(`${data?.error || `HTTP ${res.status}`}${hint}`);
45
+ }
46
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
47
+ throw new Error('Invalid JSON response from Maskbreak API');
48
+ }
49
+ return data;
50
+ } catch (error) {
51
+ if (error.name === 'AbortError') throw new Error(`Maskbreak request timed out after ${TIMEOUT_MS}ms`);
52
+ throw error;
53
+ } finally {
54
+ clearTimeout(timer);
55
+ }
56
+ }
15
57
 
16
58
  async function lookupIp(ip) {
17
- const opts = { headers: { 'Accept': 'application/json' } };
18
- let res;
19
59
  if (API_KEY) {
20
- res = await fetch(`${BASE}/v1/lookup/${encodeURIComponent(ip)}`, {
21
- ...opts,
22
- headers: { ...opts.headers, 'Authorization': `Bearer ${API_KEY}` }
23
- });
24
- } else {
25
- res = await fetch(`${BASE}/api/lookup`, {
26
- method: 'POST',
27
- headers: { ...opts.headers, 'Content-Type': 'application/json' },
28
- body: JSON.stringify({ ip })
60
+ return requestJson(`/v1/lookup/${encodeURIComponent(ip)}`, {
61
+ headers: { 'Authorization': `Bearer ${API_KEY}` }
29
62
  });
30
63
  }
31
- const data = await res.json().catch(() => ({}));
32
- if (!res.ok) {
33
- const hint = !API_KEY && res.status === 429
34
- ? ' (keyless mode is tightly rate-limited — set SENTINEL_API_KEY for 1,000 lookups/hour; free key at https://maskbreak.com/signup)'
35
- : '';
36
- throw new Error(`${data.error || `HTTP ${res.status}`}${hint}`);
37
- }
38
- return data;
64
+ return requestJson('/api/lookup', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({ ip })
68
+ }, true);
39
69
  }
40
70
 
41
71
  function text(obj) {
42
72
  return { content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }] };
43
73
  }
44
74
 
45
- const server = new McpServer({ name: 'sentinel', version: '0.1.0' });
75
+ const server = new McpServer({ name: 'maskbreak', version: VERSION });
46
76
 
47
77
  server.tool(
48
78
  'lookup_ip',
49
- 'Check an IP address for fraud signals: VPN, proxy, Tor exit node, datacenter hosting, and anonymity. Returns an allow/review/block verdict, a 0-100 risk score, the individual signals, and network info (ASN, organization, country).',
79
+ 'Look up limited public IP intelligence: cloud-hosting ranges and Tor exit nodes. Returns known, an allow/review/block verdict, a 0-100 risk score, and nullable signals/network metadata. Unknown or false signals do not establish safety. VPN/proxy and device evidence require a browser-SDK-backed visit via /v1/evaluate, which this tool does not perform.',
50
80
  { ip: z.string().describe('Public IPv4 or IPv6 address to check, e.g. "185.220.101.34"') },
51
81
  async ({ ip }) => {
52
82
  try {
@@ -63,8 +93,7 @@ server.tool(
63
93
  {},
64
94
  async () => {
65
95
  try {
66
- const res = await fetch(`${BASE}/api/status`, { headers: { 'Accept': 'application/json' } });
67
- return text(await res.json());
96
+ return text(await requestJson('/api/status'));
68
97
  } catch (e) {
69
98
  return { ...text({ error: e.message }), isError: true };
70
99
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@sentinelsup/mcp",
3
- "version": "0.1.2",
4
- "description": "MCP server for Maskbreak — check any IP for VPNs, proxies, Tor, datacenter hosting, and fraud risk from Claude, Cursor, or any MCP client.",
3
+ "version": "0.1.4",
4
+ "mcpName": "io.github.sentinelsup/maskbreak-mcp",
5
+ "description": "MCP server for Maskbreak — look up public cloud-hosting and Tor IP signals, with limited reputation evidence and API status, from an MCP client.",
5
6
  "keywords": [
6
7
  "mcp",
7
8
  "model-context-protocol",
@@ -16,6 +17,7 @@
16
17
  "type": "module",
17
18
  "main": "index.js",
18
19
  "bin": {
20
+ "maskbreak-mcp": "index.js",
19
21
  "sentinel-mcp": "index.js"
20
22
  },
21
23
  "files": [
@@ -36,6 +38,9 @@
36
38
  "engines": {
37
39
  "node": ">=18"
38
40
  },
41
+ "scripts": {
42
+ "test": "node --test test.js"
43
+ },
39
44
  "dependencies": {
40
45
  "@modelcontextprotocol/sdk": "^1.0.0",
41
46
  "zod": "^3.23.0"