@tocharianou/abuseipdb-mcp 1.0.1

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +306 -0
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.js +148 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/blacklist-tools.d.ts +3 -0
  7. package/dist/src/blacklist-tools.js +41 -0
  8. package/dist/src/blacklist-tools.js.map +1 -0
  9. package/dist/src/block-tools.d.ts +3 -0
  10. package/dist/src/block-tools.js +38 -0
  11. package/dist/src/block-tools.js.map +1 -0
  12. package/dist/src/handlers/blacklist.d.ts +7 -0
  13. package/dist/src/handlers/blacklist.js +57 -0
  14. package/dist/src/handlers/blacklist.js.map +1 -0
  15. package/dist/src/handlers/block.d.ts +7 -0
  16. package/dist/src/handlers/block.js +34 -0
  17. package/dist/src/handlers/block.js.map +1 -0
  18. package/dist/src/handlers/index.d.ts +3 -0
  19. package/dist/src/handlers/index.js +4 -0
  20. package/dist/src/handlers/index.js.map +1 -0
  21. package/dist/src/handlers/ip.d.ts +14 -0
  22. package/dist/src/handlers/ip.js +106 -0
  23. package/dist/src/handlers/ip.js.map +1 -0
  24. package/dist/src/ip-tools.d.ts +3 -0
  25. package/dist/src/ip-tools.js +79 -0
  26. package/dist/src/ip-tools.js.map +1 -0
  27. package/dist/src/types/abuseipdb.d.ts +69 -0
  28. package/dist/src/types/abuseipdb.js +37 -0
  29. package/dist/src/types/abuseipdb.js.map +1 -0
  30. package/dist/src/types.d.ts +18 -0
  31. package/dist/src/types.js +21 -0
  32. package/dist/src/types.js.map +1 -0
  33. package/dist/src/utils/api.d.ts +10 -0
  34. package/dist/src/utils/api.js +30 -0
  35. package/dist/src/utils/api.js.map +1 -0
  36. package/dist/src/utils/token-limiter.d.ts +8 -0
  37. package/dist/src/utils/token-limiter.js +29 -0
  38. package/dist/src/utils/token-limiter.js.map +1 -0
  39. package/logos/logo-240.svg +13 -0
  40. package/logos/logo-48.svg +9 -0
  41. package/package.json +54 -0
  42. package/server.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 MCP AbuseIPDB Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # MCP AbuseIPDB Server
2
+
3
+ An MCP (Model Context Protocol) server that provides threat intelligence lookups against the AbuseIPDB database. This server enables any MCP-capable client to perform IP reputation checks, CIDR block analysis, and access curated blacklists with intelligent caching and rate limiting.
4
+
5
+ ## Features
6
+
7
+ - **IP Reputation Checks**: Single IP address lookups with detailed abuse data
8
+ - **CIDR Block Analysis**: Check entire network ranges for malicious activity
9
+ - **Blacklist Access**: Retrieve current AbuseIPDB blacklist with configurable confidence levels
10
+ - **Bulk Operations**: Check multiple IP addresses efficiently
11
+ - **Log Enrichment**: Extract and analyze IP addresses from log lines
12
+ - **Intelligent Caching**: SQLite-based caching with TTL to minimize API usage
13
+ - **Rate Limiting**: Built-in quota management for AbuseIPDB API limits
14
+ - **Security Focused**: Input validation, private IP filtering, and secure defaults
15
+
16
+ ## Quick Start
17
+
18
+ ### Prerequisites
19
+
20
+ - Python 3.11 or higher
21
+ - AbuseIPDB API key (get one at [abuseipdb.com](https://www.abuseipdb.com/api))
22
+
23
+ ### Installation
24
+
25
+ 1. Clone the repository:
26
+ ```bash
27
+ git clone <repository-url>
28
+ cd AbuseIPDB-MCP
29
+ ```
30
+
31
+ 2. Install the package:
32
+ ```bash
33
+ pip install -e .
34
+ ```
35
+
36
+ 3. Set up your environment:
37
+ ```bash
38
+ cp .env.example .env
39
+ # Edit .env and add your ABUSEIPDB_API_KEY
40
+ ```
41
+
42
+ 4. Run the server:
43
+ ```bash
44
+ python -m mcp_abuseipdb.server
45
+ ```
46
+
47
+ ### MCP Client Configuration
48
+
49
+ #### Option 1: Using the Enhanced Startup Script (Recommended)
50
+
51
+ Add to your MCP client configuration (e.g., `mcp.json`):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "mcp-abuseipdb": {
57
+ "command": "python",
58
+ "args": ["scripts/start_mcp_server.py"],
59
+ "cwd": "/path/to/AbuseIPDB-MCP",
60
+ "env": {
61
+ "ABUSEIPDB_API_KEY": "your_api_key_here"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ #### Option 2: Direct Module Execution
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "mcp-abuseipdb": {
74
+ "command": "python",
75
+ "args": ["-m", "mcp_abuseipdb.server"],
76
+ "cwd": "/path/to/AbuseIPDB-MCP",
77
+ "env": {
78
+ "ABUSEIPDB_API_KEY": "your_api_key_here"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ **Important Notes:**
86
+ - Replace `your_api_key_here` with your actual AbuseIPDB API key
87
+ - Update `/path/to/AbuseIPDB-MCP` to the actual path where you cloned this repository
88
+ - The enhanced startup script (Option 1) provides better error diagnostics
89
+ - Ensure your API key is valid and not expired on the AbuseIPDB website
90
+
91
+ ## Available Tools
92
+
93
+ ### `check_ip`
94
+ Check the reputation of a single IP address.
95
+
96
+ **Parameters:**
97
+ - `ip_address` (required): IP address to check
98
+ - `max_age_days` (optional): Maximum age of reports (default: 30)
99
+ - `verbose` (optional): Include detailed reports (default: false)
100
+ - `threshold` (optional): Confidence threshold for flagging (default: 75)
101
+
102
+ ### `check_block`
103
+ Check the reputation of a CIDR network block.
104
+
105
+ **Parameters:**
106
+ - `network` (required): CIDR network (e.g., "192.168.1.0/24")
107
+ - `max_age_days` (optional): Maximum age of reports (default: 30)
108
+
109
+ ### `get_blacklist`
110
+ Retrieve the AbuseIPDB blacklist.
111
+
112
+ **Parameters:**
113
+ - `confidence_minimum` (optional): Minimum confidence level (default: 90)
114
+ - `limit` (optional): Maximum entries to retrieve
115
+
116
+ ### `bulk_check`
117
+ Check multiple IP addresses efficiently.
118
+
119
+ **Parameters:**
120
+ - `ip_addresses` (required): List of IP addresses
121
+ - `max_age_days` (optional): Maximum age of reports (default: 30)
122
+ - `threshold` (optional): Confidence threshold for flagging (default: 75)
123
+
124
+ ### `enrich_log_line`
125
+ Extract and analyze IP addresses from log entries.
126
+
127
+ **Parameters:**
128
+ - `log_line` (required): Log line containing IP addresses
129
+ - `threshold` (optional): Confidence threshold for flagging (default: 75)
130
+ - `max_age_days` (optional): Maximum age of reports (default: 30)
131
+
132
+ ## Available Resources
133
+
134
+ ### `cache://info`
135
+ Get current cache statistics and rate limiter status.
136
+
137
+ ### `doc://usage`
138
+ Complete API usage documentation and examples.
139
+
140
+ ## Available Prompts
141
+
142
+ ### `triage_ip`
143
+ Generate security analyst triage notes for an IP address.
144
+
145
+ **Parameters:**
146
+ - `ip_data` (required): IP check data from AbuseIPDB
147
+
148
+ ## Configuration
149
+
150
+ All configuration is done via environment variables. Copy `.env.example` to `.env` and customize:
151
+
152
+ ### Required Settings
153
+ - `ABUSEIPDB_API_KEY`: Your AbuseIPDB API key
154
+
155
+ ### Optional Settings
156
+ - `MAX_AGE_DAYS`: Default report age limit (default: 30)
157
+ - `CONFIDENCE_THRESHOLD`: Default confidence threshold (default: 75)
158
+ - `DAILY_QUOTA`: API request quota (default: 1000)
159
+ - `CACHE_DB_PATH`: SQLite cache file location (default: ./cache.db)
160
+ - `LOG_LEVEL`: Logging level (default: INFO)
161
+ - `ALLOW_PRIVATE_IPS`: Allow checking private IPs (default: false)
162
+
163
+ ## Usage Examples
164
+
165
+ ### Basic IP Check
166
+ ```
167
+ Check the reputation of 8.8.8.8
168
+ ```
169
+
170
+ ### Log Analysis
171
+ ```
172
+ Analyze this log line for threats:
173
+ 192.168.1.100 - - [10/Jan/2024:10:00:00 +0000] "GET /admin/login.php HTTP/1.1" 200 1234
174
+ ```
175
+
176
+ ### Bulk Analysis
177
+ ```
178
+ Check these IPs for malicious activity:
179
+ - 203.0.113.100
180
+ - 198.51.100.50
181
+ - 192.0.2.25
182
+ ```
183
+
184
+ ### Security Investigation
185
+ ```
186
+ I'm investigating suspicious activity from 203.0.113.100. Can you:
187
+ 1. Check its reputation with detailed reports
188
+ 2. Analyze the surrounding network block
189
+ 3. Generate triage notes for our security team
190
+ ```
191
+
192
+ See `examples/queries.md` for more detailed examples.
193
+
194
+ ## Docker Deployment
195
+
196
+ Build and run with Docker:
197
+
198
+ ```bash
199
+ # Build the image
200
+ docker build -f docker/Dockerfile -t mcp-abuseipdb .
201
+
202
+ # Run the container
203
+ docker run -e ABUSEIPDB_API_KEY=your_key_here mcp-abuseipdb
204
+ ```
205
+
206
+ ## Development
207
+
208
+ ### Setup Development Environment
209
+ ```bash
210
+ pip install -e ".[dev]"
211
+ pre-commit install
212
+ ```
213
+
214
+ ### Run Tests
215
+ ```bash
216
+ pytest
217
+ ```
218
+
219
+
220
+ ## Security Considerations
221
+
222
+ - **API Key Protection**: Never commit API keys to version control
223
+ - **Private IP Filtering**: Private IPs are blocked by default
224
+ - **Rate Limiting**: Built-in quota management prevents API abuse
225
+ - **Input Validation**: All inputs are validated and sanitized
226
+ - **Caching**: Reduces API calls and improves performance
227
+
228
+ ## Rate Limits
229
+
230
+ AbuseIPDB free tier provides 1,000 requests per day. This server:
231
+ - Implements intelligent caching to minimize API usage
232
+ - Provides rate limiting with configurable quotas
233
+ - Gracefully handles rate limit errors with backoff
234
+
235
+ ## Troubleshooting
236
+
237
+ ### "Unauthorized API key" Error in Claude App
238
+
239
+ If you're getting unauthorized API key errors when using the MCP server with Claude:
240
+
241
+ 1. **Verify API Key Configuration**:
242
+ ```bash
243
+ # Test your API key with the diagnostic script
244
+ python diagnostics/api_auth_diagnostic.py
245
+ ```
246
+
247
+ 2. **Check Claude App Configuration**:
248
+ - Ensure your `mcp.json` has the correct API key in the `env` section
249
+ - Verify the `cwd` path points to your project directory
250
+ - Make sure the API key value matches exactly (no extra spaces)
251
+
252
+ 3. **Use Enhanced Startup Script**:
253
+ - Switch to Option 1 configuration (enhanced startup script)
254
+ - Check the server logs in Claude app for diagnostic messages
255
+ - Look for `[MCP AbuseIPDB]` prefixed messages
256
+
257
+ 4. **Environment Variable Issues**:
258
+ - Ensure your `.env` file is in the project root directory
259
+ - Verify the API key in `.env` matches your Claude app configuration
260
+ - Check that the API key is valid on the AbuseIPDB website
261
+
262
+ 5. **Debug Steps**:
263
+ ```bash
264
+ # Test local server startup
265
+ python scripts/start_mcp_server.py
266
+
267
+ # Check environment loading
268
+ python -c "from mcp_abuseipdb.settings import Settings; print('API key loaded:', bool(Settings().abuseipdb_api_key))"
269
+ ```
270
+
271
+ ### Common Issues
272
+
273
+ - **"No .env file found"**: Make sure `.env` exists in project root or set API key in Claude app config
274
+ - **"Settings API key: EMPTY"**: API key not properly loaded from environment
275
+ - **"Environment var: EMPTY"**: API key not set in Claude app MCP configuration
276
+ - **Connection timeouts**: Check your internet connection and AbuseIPDB service status
277
+
278
+ ## Contributing
279
+
280
+ 1. Fork the repository
281
+ 2. Create a feature branch
282
+ 3. Make your changes with tests
283
+ 4. Run the test suite and linting
284
+ 5. Submit a pull request
285
+
286
+ ## License
287
+
288
+ MIT License — see [LICENSE](LICENSE) for details.
289
+
290
+ ## Support
291
+
292
+ - Documentation: See `examples/` directory
293
+ - Issues: Please report bugs and feature requests via GitHub issues
294
+ - API Documentation: [AbuseIPDB API Docs](https://docs.abuseipdb.com/)
295
+
296
+ ## Changelog
297
+
298
+ ### v0.1.0
299
+ - Initial release
300
+ - Basic IP checking functionality
301
+ - CIDR block analysis
302
+ - Blacklist access
303
+ - Bulk operations
304
+ - Log enrichment
305
+ - Caching and rate limiting
306
+ - Docker support
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { AbuseIPDBConfig } from './src/types.js';
5
+ interface ServerCreationOptions {
6
+ name: string;
7
+ version: string;
8
+ config: AbuseIPDBConfig;
9
+ description?: string;
10
+ }
11
+ export declare function createAbuseIPDBMcpServer(options: ServerCreationOptions): Promise<McpServer>;
12
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Copyright (c) 2024 TocharianOU Contributors
4
+ import 'dotenv/config';
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import express from 'express';
9
+ import { randomUUID } from 'crypto';
10
+ import { AbuseIPDBConfigSchema } from './src/types.js';
11
+ import { createAbuseIPDBClient } from './src/utils/api.js';
12
+ import { registerIpTools } from './src/ip-tools.js';
13
+ import { registerBlockTools } from './src/block-tools.js';
14
+ import { registerBlacklistTools } from './src/blacklist-tools.js';
15
+ export async function createAbuseIPDBMcpServer(options) {
16
+ const { name, version, config, description } = options;
17
+ const validatedConfig = AbuseIPDBConfigSchema.parse(config);
18
+ const client = createAbuseIPDBClient(validatedConfig);
19
+ const server = new McpServer({
20
+ name,
21
+ version,
22
+ ...(description ? { description } : {}),
23
+ });
24
+ const maxTokenCall = parseInt(process.env.MAX_TOKEN_CALL ?? '20000', 10);
25
+ registerIpTools(server, client, maxTokenCall);
26
+ registerBlockTools(server, client, maxTokenCall);
27
+ registerBlacklistTools(server, client, maxTokenCall);
28
+ registerBlockTools(server, client, maxTokenCall);
29
+ registerBlacklistTools(server, client, maxTokenCall);
30
+ return server;
31
+ }
32
+ async function main() {
33
+ const config = {
34
+ apiKey: process.env.ABUSEIPDB_API_KEY,
35
+ baseUrl: process.env.ABUSEIPDB_BASE_URL,
36
+ authToken: process.env.ABUSEIPDB_AUTH_TOKEN,
37
+ timeout: parseInt(process.env.ABUSEIPDB_TIMEOUT ?? '30000', 10),
38
+ };
39
+ const SERVER_NAME = 'abuseipdb-mcp-server';
40
+ const SERVER_VERSION = '1.0.0';
41
+ const SERVER_DESCRIPTION = 'AbuseIPDB MCP Server – IP reputation, abuse confidence scoring, and threat blacklist';
42
+ const useHttp = process.env.MCP_TRANSPORT === 'http';
43
+ const httpPort = parseInt(process.env.MCP_HTTP_PORT ?? '3000', 10);
44
+ const httpHost = process.env.MCP_HTTP_HOST ?? 'localhost';
45
+ if (useHttp) {
46
+ process.stderr.write(`Starting AbuseIPDB MCP Server in HTTP mode on ${httpHost}:${httpPort}\n`);
47
+ const app = express();
48
+ app.use(express.json());
49
+ const transports = new Map();
50
+ app.get('/health', (_req, res) => {
51
+ res.json({ status: 'ok', transport: 'streamable-http' });
52
+ });
53
+ app.post('/mcp', async (req, res) => {
54
+ const sessionId = req.headers['mcp-session-id'];
55
+ try {
56
+ let transport;
57
+ if (sessionId !== undefined && transports.has(sessionId)) {
58
+ transport = transports.get(sessionId);
59
+ }
60
+ else {
61
+ transport = new StreamableHTTPServerTransport({
62
+ sessionIdGenerator: () => randomUUID(),
63
+ onsessioninitialized: async (newSessionId) => {
64
+ transports.set(newSessionId, transport);
65
+ process.stderr.write(`MCP session initialized: ${newSessionId}\n`);
66
+ },
67
+ onsessionclosed: async (closedSessionId) => {
68
+ transports.delete(closedSessionId);
69
+ process.stderr.write(`MCP session closed: ${closedSessionId}\n`);
70
+ },
71
+ });
72
+ const server = await createAbuseIPDBMcpServer({
73
+ name: SERVER_NAME,
74
+ version: SERVER_VERSION,
75
+ config,
76
+ description: SERVER_DESCRIPTION,
77
+ });
78
+ await server.connect(transport);
79
+ }
80
+ await transport.handleRequest(req, res, req.body);
81
+ }
82
+ catch (error) {
83
+ process.stderr.write(`Error handling MCP request: ${error}\n`);
84
+ if (!res.headersSent) {
85
+ res.status(500).json({
86
+ jsonrpc: '2.0',
87
+ error: { code: -32603, message: 'Internal server error' },
88
+ id: null,
89
+ });
90
+ }
91
+ }
92
+ });
93
+ app.get('/mcp', async (req, res) => {
94
+ const sessionId = req.headers['mcp-session-id'];
95
+ if (sessionId === undefined || !transports.has(sessionId)) {
96
+ res.status(400).json({
97
+ jsonrpc: '2.0',
98
+ error: { code: -32000, message: 'Invalid or missing session ID' },
99
+ id: null,
100
+ });
101
+ return;
102
+ }
103
+ try {
104
+ const transport = transports.get(sessionId);
105
+ await transport.handleRequest(req, res);
106
+ }
107
+ catch (error) {
108
+ process.stderr.write(`Error handling SSE stream: ${error}\n`);
109
+ if (!res.headersSent) {
110
+ res.status(500).json({
111
+ jsonrpc: '2.0',
112
+ error: { code: -32603, message: 'Failed to establish SSE stream' },
113
+ id: null,
114
+ });
115
+ }
116
+ }
117
+ });
118
+ app.listen(httpPort, httpHost, () => {
119
+ process.stderr.write(`AbuseIPDB MCP Server (HTTP mode) started on http://${httpHost}:${httpPort}\n`);
120
+ });
121
+ process.on('SIGINT', async () => {
122
+ for (const [, transport] of transports.entries()) {
123
+ await transport.close();
124
+ }
125
+ process.exit(0);
126
+ });
127
+ }
128
+ else {
129
+ process.stderr.write('Starting AbuseIPDB MCP Server in Stdio mode\n');
130
+ const server = await createAbuseIPDBMcpServer({
131
+ name: SERVER_NAME,
132
+ version: SERVER_VERSION,
133
+ config,
134
+ description: SERVER_DESCRIPTION,
135
+ });
136
+ const transport = new StdioServerTransport();
137
+ await server.connect(transport);
138
+ process.on('SIGINT', async () => {
139
+ await server.close();
140
+ process.exit(0);
141
+ });
142
+ }
143
+ }
144
+ main().catch((error) => {
145
+ process.stderr.write(`Fatal error: ${error instanceof Error ? error.message : String(error)}\n`);
146
+ process.exit(1);
147
+ });
148
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { AxiosInstance } from 'axios';
3
+ export declare function registerBlacklistTools(server: McpServer, client: AxiosInstance, maxTokenCall?: number): void;
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { handleGetBlacklist } from './handlers/blacklist.js';
3
+ import { checkTokenLimit } from './utils/token-limiter.js';
4
+ const GetBlacklistSchema = z.object({
5
+ confidence_minimum: z
6
+ .number()
7
+ .int()
8
+ .min(25)
9
+ .max(100)
10
+ .optional()
11
+ .describe('Minimum abuse confidence score to include in blacklist (25–100, default: 90)'),
12
+ limit: z
13
+ .number()
14
+ .int()
15
+ .min(1)
16
+ .max(500000)
17
+ .optional()
18
+ .describe('Maximum number of entries to return (default: all entries up to plan limit)'),
19
+ plain_text: z
20
+ .boolean()
21
+ .optional()
22
+ .describe('Return plain text IP list instead of JSON (default: false)'),
23
+ break_token_rule: z
24
+ .boolean()
25
+ .optional()
26
+ .default(false)
27
+ .describe('Set to true to bypass token limits in critical situations (default: false)'),
28
+ });
29
+ export function registerBlacklistTools(server, client, maxTokenCall = 20000) {
30
+ const registerTool = server.tool.bind(server);
31
+ registerTool('get_blacklist', 'Retrieve the AbuseIPDB blacklist of most-reported malicious IP addresses. Returns confidence distribution, top countries, and sample entries. Requires AbuseIPDB subscription plan.', GetBlacklistSchema.shape, async (args) => {
32
+ const parsed = GetBlacklistSchema.parse(args);
33
+ const text = await handleGetBlacklist(client, parsed);
34
+ const tokenCheck = checkTokenLimit(text, maxTokenCall, parsed.break_token_rule ?? false);
35
+ if (!tokenCheck.allowed) {
36
+ return { content: [{ type: 'text', text: tokenCheck.error }] };
37
+ }
38
+ return { content: [{ type: 'text', text }] };
39
+ });
40
+ }
41
+ //# sourceMappingURL=blacklist-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blacklist-tools.js","sourceRoot":"","sources":["../../src/blacklist-tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAK3D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,kBAAkB,EAAE,CAAC;SAClB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,EAAE,CAAC;SACP,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,8EAA8E,CAAC;IAC3F,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,MAAM,CAAC;SACX,QAAQ,EAAE;SACV,QAAQ,CAAC,6EAA6E,CAAC;IAC1F,UAAU,EAAE,CAAC;SACV,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;IACzE,gBAAgB,EAAE,CAAC;SAChB,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,4EAA4E,CAAC;CAC1F,CAAC,CAAC;AAEH,MAAM,UAAU,sBAAsB,CAAC,MAAiB,EAAE,MAAqB,EAAE,YAAY,GAAG,KAAK;IACnG,MAAM,YAAY,GAAI,MAAc,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAY,CAAC;IAElE,YAAY,CACV,eAAe,EACf,qLAAqL,EACrL,kBAAkB,CAAC,KAAK,EACxB,KAAK,EAAE,IAAa,EAAE,EAAE;QACtB,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAC;QACzF,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,KAAM,EAAE,CAAC,EAAE,CAAC;QAClE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { AxiosInstance } from 'axios';
3
+ export declare function registerBlockTools(server: McpServer, client: AxiosInstance, maxTokenCall?: number): void;
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { handleCheckBlock } from './handlers/block.js';
3
+ import { checkTokenLimit } from './utils/token-limiter.js';
4
+ const CheckBlockSchema = z.object({
5
+ network: z.string().describe('CIDR network block to check, e.g. "198.51.100.0/24"'),
6
+ max_age_days: z
7
+ .number()
8
+ .int()
9
+ .min(1)
10
+ .max(365)
11
+ .optional()
12
+ .describe('Look-back window in days for abuse reports (1–365, default: 30)'),
13
+ confidence_threshold: z
14
+ .number()
15
+ .int()
16
+ .min(0)
17
+ .max(100)
18
+ .optional()
19
+ .describe('Confidence % to classify addresses as high-confidence threats (default: 75)'),
20
+ break_token_rule: z
21
+ .boolean()
22
+ .optional()
23
+ .default(false)
24
+ .describe('Set to true to bypass token limits in critical situations (default: false)'),
25
+ });
26
+ export function registerBlockTools(server, client, maxTokenCall = 20000) {
27
+ const registerTool = server.tool.bind(server);
28
+ registerTool('check_block', 'Check all reported IP addresses within a CIDR network block against AbuseIPDB. Returns network summary, total reported addresses, and top threats sorted by confidence score. Requires AbuseIPDB subscription plan.', CheckBlockSchema.shape, async (args) => {
29
+ const parsed = CheckBlockSchema.parse(args);
30
+ const text = await handleCheckBlock(client, parsed);
31
+ const tokenCheck = checkTokenLimit(text, maxTokenCall, parsed.break_token_rule ?? false);
32
+ if (!tokenCheck.allowed) {
33
+ return { content: [{ type: 'text', text: tokenCheck.error }] };
34
+ }
35
+ return { content: [{ type: 'text', text }] };
36
+ });
37
+ }
38
+ //# sourceMappingURL=block-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-tools.js","sourceRoot":"","sources":["../../src/block-tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAK3D,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;IACnF,YAAY,EAAE,CAAC;SACZ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,iEAAiE,CAAC;IAC9E,oBAAoB,EAAE,CAAC;SACpB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,6EAA6E,CAAC;IAC1F,gBAAgB,EAAE,CAAC;SAChB,OAAO,EAAE;SACT,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,4EAA4E,CAAC;CAC1F,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,MAAqB,EAAE,YAAY,GAAG,KAAK;IAC/F,MAAM,YAAY,GAAI,MAAc,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAY,CAAC;IAElE,YAAY,CACV,aAAa,EACb,qNAAqN,EACrN,gBAAgB,CAAC,KAAK,EACtB,KAAK,EAAE,IAAa,EAAE,EAAE;QACtB,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAC;QACzF,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,KAAM,EAAE,CAAC,EAAE,CAAC;QAClE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { AxiosInstance } from 'axios';
2
+ export interface GetBlacklistArgs {
3
+ confidence_minimum?: number;
4
+ limit?: number;
5
+ plain_text?: boolean;
6
+ }
7
+ export declare function handleGetBlacklist(client: AxiosInstance, args: GetBlacklistArgs): Promise<string>;
@@ -0,0 +1,57 @@
1
+ import { assessRiskLevel } from '../types/abuseipdb.js';
2
+ export async function handleGetBlacklist(client, args) {
3
+ const { confidence_minimum = 90, limit, plain_text = false } = args;
4
+ const params = { confidenceMinimum: confidence_minimum };
5
+ if (limit !== undefined)
6
+ params['limit'] = limit;
7
+ if (plain_text)
8
+ params['plaintext'] = true;
9
+ const { data: resp } = await client.get('/blacklist', { params });
10
+ const entries = resp.data ?? [];
11
+ const generatedAt = resp.meta?.generatedAt ?? 'Unknown';
12
+ const countryStats = {};
13
+ const confidenceDist = { '90-100': 0, '75-89': 0, '50-74': 0, '0-49': 0 };
14
+ for (const e of entries) {
15
+ const cc = e.countryCode ?? 'Unknown';
16
+ countryStats[cc] = (countryStats[cc] ?? 0) + 1;
17
+ const s = e.abuseConfidenceScore;
18
+ if (s >= 90)
19
+ confidenceDist['90-100']++;
20
+ else if (s >= 75)
21
+ confidenceDist['75-89']++;
22
+ else if (s >= 50)
23
+ confidenceDist['50-74']++;
24
+ else
25
+ confidenceDist['0-49']++;
26
+ }
27
+ const topCountries = Object.entries(countryStats)
28
+ .sort(([, a], [, b]) => b - a)
29
+ .slice(0, 10);
30
+ const lines = [
31
+ `Blacklist Retrieved: ${entries.length.toLocaleString()} entries`,
32
+ `Generated: ${generatedAt}`,
33
+ `Minimum Confidence: ${confidence_minimum}%`,
34
+ ];
35
+ if (limit !== undefined)
36
+ lines.push(`Limit Applied: ${limit.toLocaleString()}`);
37
+ lines.push('\nConfidence Distribution:', ` • 90-100%: ${confidenceDist['90-100'].toLocaleString()}`, ` • 75-89%: ${confidenceDist['75-89'].toLocaleString()}`, ` • 50-74%: ${confidenceDist['50-74'].toLocaleString()}`, ` • 0-49%: ${confidenceDist['0-49'].toLocaleString()}`);
38
+ if (topCountries.length > 0) {
39
+ lines.push('\nTop Countries:');
40
+ topCountries.forEach(([cc, count]) => {
41
+ lines.push(` • ${cc}: ${count.toLocaleString()}`);
42
+ });
43
+ }
44
+ if (entries.length > 0) {
45
+ lines.push('\nSample Entries (top 20 by confidence):');
46
+ const sorted = [...entries].sort((a, b) => b.abuseConfidenceScore - a.abuseConfidenceScore);
47
+ sorted.slice(0, 20).forEach((e) => {
48
+ const level = assessRiskLevel(e.abuseConfidenceScore);
49
+ const last = e.lastReportedAt ? e.lastReportedAt.slice(0, 10) : 'Unknown';
50
+ lines.push(` • ${e.ipAddress} (${e.countryCode ?? '?'}) – ${e.abuseConfidenceScore}% [${level}] – last: ${last}`);
51
+ });
52
+ if (entries.length > 20)
53
+ lines.push(` ... and ${entries.length - 20} more entries`);
54
+ }
55
+ return lines.join('\n');
56
+ }
57
+ //# sourceMappingURL=blacklist.js.map