mcp-threatintel-server
Version:
MCP server for unified threat intelligence - AlienVault OTX, AbuseIPDB, GreyNoise, and abuse.ch feeds
683 lines • 27.1 kB
JavaScript
#!/usr/bin/env node
/**
* MCP Server for Unified Threat Intelligence
*
* Aggregates data from multiple free threat intel sources:
* - AlienVault OTX (IOCs, pulses, campaigns)
* - AbuseIPDB (IP reputation)
* - GreyNoise (noise vs threat classification)
* - abuse.ch feeds (URLhaus, MalwareBazaar, ThreatFox, Feodo Tracker)
*
* Environment variables:
* - OTX_API_KEY: AlienVault OTX API key (free at otx.alienvault.com)
* - ABUSEIPDB_API_KEY: AbuseIPDB API key (free at abuseipdb.com)
* - GREYNOISE_API_KEY: GreyNoise API key (free community tier)
* - ABUSECH_AUTH_KEY: abuse.ch auth key (free at auth.abuse.ch) - optional
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
// API Configuration
const config = {
otx: {
apiKey: process.env.OTX_API_KEY,
baseUrl: "https://otx.alienvault.com/api/v1",
},
abuseipdb: {
apiKey: process.env.ABUSEIPDB_API_KEY,
baseUrl: "https://api.abuseipdb.com/api/v2",
},
greynoise: {
apiKey: process.env.GREYNOISE_API_KEY,
baseUrl: "https://api.greynoise.io/v3",
},
abusech: {
authKey: process.env.ABUSECH_AUTH_KEY,
urlhaus: "https://urlhaus-api.abuse.ch/v1",
malwarebazaar: "https://mb-api.abuse.ch/api/v1",
threatfox: "https://threatfox-api.abuse.ch/api/v1",
feodo: "https://feodotracker.abuse.ch/downloads",
},
};
// Track which services are configured
const services = {
otx: !!config.otx.apiKey,
abuseipdb: !!config.abuseipdb.apiKey,
greynoise: !!config.greynoise.apiKey,
abusech: !!config.abusech.authKey, // abuse.ch now requires auth
feodo: true, // Feodo Tracker public JSON feeds still work
};
const configuredServices = Object.entries(services)
.filter(([, enabled]) => enabled)
.map(([name]) => name);
if (configuredServices.length === 0) {
console.error("Warning: No API keys configured. Some features will be limited.");
console.error("Set OTX_API_KEY, ABUSEIPDB_API_KEY, and/or GREYNOISE_API_KEY for full functionality.");
}
// Helper function for API requests
async function apiRequest(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
...(options.headers || {}),
},
});
if (!response.ok) {
const text = await response.text();
throw new Error(`API error ${response.status}: ${text}`);
}
return response.json();
}
// Define available tools
const TOOLS = [
// Status tool
{
name: "threatintel_status",
description: `Check which threat intelligence sources are configured. Currently available: ${configuredServices.join(", ") || "none (abuse.ch feeds work without auth)"}`,
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
// Unified lookup
{
name: "threatintel_lookup_ip",
description: "Look up an IP address across all configured threat intelligence sources (OTX, AbuseIPDB, GreyNoise, Feodo Tracker)",
inputSchema: {
type: "object",
properties: {
ip: {
type: "string",
description: "IP address to look up",
},
},
required: ["ip"],
},
},
{
name: "threatintel_lookup_domain",
description: "Look up a domain across threat intelligence sources (OTX, URLhaus)",
inputSchema: {
type: "object",
properties: {
domain: {
type: "string",
description: "Domain name to look up",
},
},
required: ["domain"],
},
},
{
name: "threatintel_lookup_hash",
description: "Look up a file hash (MD5, SHA1, SHA256) across threat intelligence sources (OTX, MalwareBazaar)",
inputSchema: {
type: "object",
properties: {
hash: {
type: "string",
description: "File hash (MD5, SHA1, or SHA256)",
},
},
required: ["hash"],
},
},
{
name: "threatintel_lookup_url",
description: "Look up a URL for malware/phishing indicators (OTX, URLhaus)",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "URL to check",
},
},
required: ["url"],
},
},
];
// AbuseIPDB tools
if (services.abuseipdb) {
TOOLS.push({
name: "abuseipdb_check",
description: "Check IP reputation on AbuseIPDB - returns abuse confidence score and recent reports",
inputSchema: {
type: "object",
properties: {
ip: {
type: "string",
description: "IP address to check",
},
maxAgeInDays: {
type: "number",
description: "How far back to check (default: 90, max: 365)",
},
},
required: ["ip"],
},
});
}
// OTX tools
if (services.otx) {
TOOLS.push({
name: "otx_get_pulses",
description: "Get recent threat intelligence pulses from AlienVault OTX",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of pulses to retrieve (default: 10)",
},
},
required: [],
},
}, {
name: "otx_search_pulses",
description: "Search OTX pulses by keyword (malware name, campaign, threat actor)",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query (e.g., 'emotet', 'apt29', 'ransomware')",
},
},
required: ["query"],
},
});
}
// GreyNoise tools
if (services.greynoise) {
TOOLS.push({
name: "greynoise_ip",
description: "Check if an IP is internet background noise or a targeted threat (GreyNoise)",
inputSchema: {
type: "object",
properties: {
ip: {
type: "string",
description: "IP address to check",
},
},
required: ["ip"],
},
});
}
// abuse.ch tools (require auth key)
if (services.abusech) {
TOOLS.push({
name: "urlhaus_lookup",
description: "Check if a URL or domain is distributing malware (URLhaus)",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "URL or domain to check",
},
},
required: ["url"],
},
}, {
name: "urlhaus_recent",
description: "Get recent malware URLs from URLhaus",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of URLs to retrieve (default: 25)",
},
},
required: [],
},
}, {
name: "malwarebazaar_hash",
description: "Look up malware sample by hash on MalwareBazaar",
inputSchema: {
type: "object",
properties: {
hash: {
type: "string",
description: "MD5, SHA1, or SHA256 hash",
},
},
required: ["hash"],
},
}, {
name: "malwarebazaar_recent",
description: "Get recent malware samples from MalwareBazaar",
inputSchema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Number of samples (default: 25, max: 1000)",
},
},
required: [],
},
}, {
name: "malwarebazaar_tag",
description: "Get malware samples by tag (e.g., 'emotet', 'cobalt-strike', 'ransomware')",
inputSchema: {
type: "object",
properties: {
tag: {
type: "string",
description: "Malware tag to search for",
},
limit: {
type: "number",
description: "Number of samples (default: 25)",
},
},
required: ["tag"],
},
}, {
name: "threatfox_iocs",
description: "Get recent IOCs from ThreatFox (C2 servers, malware infrastructure)",
inputSchema: {
type: "object",
properties: {
days: {
type: "number",
description: "Get IOCs from last N days (default: 7)",
},
},
required: [],
},
}, {
name: "threatfox_search",
description: "Search ThreatFox for IOCs by malware family or tag",
inputSchema: {
type: "object",
properties: {
search_term: {
type: "string",
description: "Malware family or tag (e.g., 'Emotet', 'CobaltStrike')",
},
},
required: ["search_term"],
},
});
}
// Feodo Tracker (public JSON feed - no auth required)
TOOLS.push({
name: "feodo_tracker",
description: "Get active botnet C2 servers from Feodo Tracker (Emotet, Dridex, QakBot, etc.)",
inputSchema: {
type: "object",
properties: {},
required: [],
},
});
// Create server instance
const server = new Server({
name: "mcp-threatintel",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "threatintel_status": {
return {
content: [{
type: "text",
text: JSON.stringify({
configured_services: {
otx: services.otx ? "configured" : "not configured (set OTX_API_KEY)",
abuseipdb: services.abuseipdb ? "configured" : "not configured (set ABUSEIPDB_API_KEY)",
greynoise: services.greynoise ? "configured" : "not configured (set GREYNOISE_API_KEY)",
abusech: services.abusech ? "configured" : "not configured (set ABUSECH_AUTH_KEY)",
feodo: "available (public JSON feeds)",
},
available_tools: TOOLS.map(t => t.name),
}, null, 2),
}],
};
}
// Unified IP lookup
case "threatintel_lookup_ip": {
const { ip } = args;
const results = { ip };
// AbuseIPDB
if (services.abuseipdb) {
try {
const abuseResult = await apiRequest(`${config.abuseipdb.baseUrl}/check?ipAddress=${encodeURIComponent(ip)}&maxAgeInDays=90`, { headers: { Key: config.abuseipdb.apiKey } });
results.abuseipdb = abuseResult.data;
}
catch (e) {
results.abuseipdb = { error: e instanceof Error ? e.message : String(e) };
}
}
// OTX
if (services.otx) {
try {
const otxResult = await apiRequest(`${config.otx.baseUrl}/indicators/IPv4/${ip}/general`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
results.otx = otxResult;
}
catch (e) {
results.otx = { error: e instanceof Error ? e.message : String(e) };
}
}
// GreyNoise
if (services.greynoise) {
try {
const gnResult = await apiRequest(`${config.greynoise.baseUrl}/community/${ip}`, { headers: { key: config.greynoise.apiKey } });
results.greynoise = gnResult;
}
catch (e) {
results.greynoise = { error: e instanceof Error ? e.message : String(e) };
}
}
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
// Unified domain lookup
case "threatintel_lookup_domain": {
const { domain } = args;
const results = { domain };
// OTX
if (services.otx) {
try {
const otxResult = await apiRequest(`${config.otx.baseUrl}/indicators/domain/${domain}/general`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
results.otx = otxResult;
}
catch (e) {
results.otx = { error: e instanceof Error ? e.message : String(e) };
}
}
// URLhaus
try {
const urlhausResult = await apiRequest(config.abusech.urlhaus + "/host/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `host=${encodeURIComponent(domain)}`,
});
results.urlhaus = urlhausResult;
}
catch (e) {
results.urlhaus = { error: e instanceof Error ? e.message : String(e) };
}
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
// Unified hash lookup
case "threatintel_lookup_hash": {
const { hash } = args;
const results = { hash };
// OTX
if (services.otx) {
try {
const hashType = hash.length === 32 ? "MD5" : hash.length === 40 ? "SHA1" : "SHA256";
const otxResult = await apiRequest(`${config.otx.baseUrl}/indicators/file/${hashType}/${hash}/general`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
results.otx = otxResult;
}
catch (e) {
results.otx = { error: e instanceof Error ? e.message : String(e) };
}
}
// MalwareBazaar
try {
const mbResult = await apiRequest(config.abusech.malwarebazaar, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `query=get_info&hash=${encodeURIComponent(hash)}`,
});
results.malwarebazaar = mbResult;
}
catch (e) {
results.malwarebazaar = { error: e instanceof Error ? e.message : String(e) };
}
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
// Unified URL lookup
case "threatintel_lookup_url": {
const { url } = args;
const results = { url };
// OTX
if (services.otx) {
try {
const otxResult = await apiRequest(`${config.otx.baseUrl}/indicators/url/${encodeURIComponent(url)}/general`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
results.otx = otxResult;
}
catch (e) {
results.otx = { error: e instanceof Error ? e.message : String(e) };
}
}
// URLhaus
try {
const urlhausResult = await apiRequest(config.abusech.urlhaus + "/url/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `url=${encodeURIComponent(url)}`,
});
results.urlhaus = urlhausResult;
}
catch (e) {
results.urlhaus = { error: e instanceof Error ? e.message : String(e) };
}
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
// AbuseIPDB check
case "abuseipdb_check": {
if (!services.abuseipdb)
throw new Error("AbuseIPDB not configured");
const { ip, maxAgeInDays = 90 } = args;
const result = await apiRequest(`${config.abuseipdb.baseUrl}/check?ipAddress=${encodeURIComponent(ip)}&maxAgeInDays=${maxAgeInDays}&verbose`, { headers: { Key: config.abuseipdb.apiKey } });
return {
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
};
}
// OTX get pulses
case "otx_get_pulses": {
if (!services.otx)
throw new Error("OTX not configured");
const { limit = 10 } = args;
const result = await apiRequest(`${config.otx.baseUrl}/pulses/subscribed?limit=${limit}`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// OTX search pulses
case "otx_search_pulses": {
if (!services.otx)
throw new Error("OTX not configured");
const { query } = args;
const result = await apiRequest(`${config.otx.baseUrl}/search/pulses?q=${encodeURIComponent(query)}`, { headers: { "X-OTX-API-KEY": config.otx.apiKey } });
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// GreyNoise IP
case "greynoise_ip": {
if (!services.greynoise)
throw new Error("GreyNoise not configured");
const { ip } = args;
const result = await apiRequest(`${config.greynoise.baseUrl}/community/${ip}`, { headers: { key: config.greynoise.apiKey } });
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// URLhaus lookup
case "urlhaus_lookup": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { url } = args;
// Try as URL first, then as host
let result;
if (url.startsWith("http")) {
result = await apiRequest(config.abusech.urlhaus + "/url/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Auth-Key": config.abusech.authKey,
},
body: `url=${encodeURIComponent(url)}`,
});
}
else {
result = await apiRequest(config.abusech.urlhaus + "/host/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Auth-Key": config.abusech.authKey,
},
body: `host=${encodeURIComponent(url)}`,
});
}
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// URLhaus recent
case "urlhaus_recent": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { limit = 25 } = args;
const result = await apiRequest(config.abusech.urlhaus + "/urls/recent/limit/" + limit + "/", {
method: "GET",
headers: { "Auth-Key": config.abusech.authKey },
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// MalwareBazaar hash lookup
case "malwarebazaar_hash": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { hash } = args;
const result = await apiRequest(config.abusech.malwarebazaar, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Auth-Key": config.abusech.authKey,
},
body: `query=get_info&hash=${encodeURIComponent(hash)}`,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// MalwareBazaar recent
case "malwarebazaar_recent": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { limit = 25 } = args;
const result = await apiRequest(config.abusech.malwarebazaar, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Auth-Key": config.abusech.authKey,
},
body: `query=get_recent&selector=${limit}`,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// MalwareBazaar by tag
case "malwarebazaar_tag": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { tag, limit = 25 } = args;
const result = await apiRequest(config.abusech.malwarebazaar, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Auth-Key": config.abusech.authKey,
},
body: `query=get_taginfo&tag=${encodeURIComponent(tag)}&limit=${limit}`,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// ThreatFox IOCs
case "threatfox_iocs": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { days = 7 } = args;
const result = await apiRequest(config.abusech.threatfox, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Auth-Key": config.abusech.authKey,
},
body: JSON.stringify({ query: "get_iocs", days }),
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// ThreatFox search
case "threatfox_search": {
if (!services.abusech)
throw new Error("abuse.ch not configured (set ABUSECH_AUTH_KEY)");
const { search_term } = args;
const result = await apiRequest(config.abusech.threatfox, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Auth-Key": config.abusech.authKey,
},
body: JSON.stringify({ query: "search_ioc", search_term }),
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// Feodo Tracker
case "feodo_tracker": {
// Feodo provides JSON feed of active C2s
const result = await apiRequest("https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.json");
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${errorMessage}` }],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`Threat Intel MCP server running - configured: ${configuredServices.join(", ") || "abuse.ch (no auth)"}`);
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map