index.ts•43.4 kB
#!/usr/bin/env node
/**
* ExploitDB MCP Server
*
* Developed by Cyreslab.ai (https://cyreslab.ai)
* Contact: contact@cyreslab.ai
* GitHub: https://github.com/Cyreslab-AI
*
* This server provides access to ExploitDB functionality through the Model Context Protocol.
* It allows AI assistants to query information about security exploits and vulnerabilities,
* enhancing cybersecurity research and threat intelligence capabilities.
*
* Copyright (c) 2025 Cyreslab.ai. All rights reserved.
* Licensed under the MIT License.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import config, { ensureDataDir } from './config.js';
import db from './db/index.js';
import exploitdb from './exploitdb/index.js';
/**
* ExploitDB MCP Server class
*/
class ExploitDBServer {
private server: Server;
private updateInterval: NodeJS.Timeout | null = null;
constructor() {
this.server = new Server(
{
name: 'mcp-exploitdb-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupResourceHandlers();
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.close();
process.exit(0);
});
}
/**
* Set up resource handlers
* For now, we're not implementing any static resources
*/
private setupResourceHandlers() {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: []
};
});
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid URI: ${request.params.uri}`
);
});
}
/**
* Set up tool handlers for ExploitDB functionality
*/
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'search_exploits',
description: 'Search for exploits in the ExploitDB database',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query (searches in description and CVE)'
},
platform: {
type: 'string',
description: 'Filter by platform (e.g., windows, linux, php, etc.)'
},
type: {
type: 'string',
description: 'Filter by exploit type (e.g., webapps, remote, local, etc.)'
},
cve: {
type: 'string',
description: 'Filter by CVE ID (e.g., CVE-2021-44228)'
},
author: {
type: 'string',
description: 'Filter by author name'
},
start_date: {
type: 'string',
description: 'Filter by start date (YYYY-MM-DD)'
},
end_date: {
type: 'string',
description: 'Filter by end date (YYYY-MM-DD)'
},
verified: {
type: 'boolean',
description: 'Filter by verified status'
},
limit: {
type: 'number',
description: `Maximum number of results to return (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
}
}
},
{
name: 'get_exploit',
description: 'Get detailed information about a specific exploit',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'Exploit ID'
},
include_code: {
type: 'boolean',
description: 'Whether to include the exploit code (default: true)'
}
},
required: ['id']
}
},
{
name: 'find_by_cve',
description: 'Find exploits by CVE ID',
inputSchema: {
type: 'object',
properties: {
cve: {
type: 'string',
description: 'CVE ID (e.g., CVE-2021-44228)'
},
limit: {
type: 'number',
description: `Maximum number of results to return (default: ${config.maxResults})`
}
},
required: ['cve']
}
},
{
name: 'get_recent_exploits',
description: 'Get recently added exploits',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: `Maximum number of results to return (default: ${config.maxResults})`
}
}
}
},
{
name: 'get_statistics',
description: 'Get statistics about the exploits in the database',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'search_by_platform',
description: 'Search exploits for a specific platform with advanced filters',
inputSchema: {
type: 'object',
properties: {
platform: {
type: 'string',
description: 'Platform name (e.g., windows, linux, php, etc.)'
},
type: {
type: 'string',
description: 'Filter by exploit type'
},
verified: {
type: 'boolean',
description: 'Filter by verified status'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
},
required: ['platform']
}
},
{
name: 'search_by_type',
description: 'Search exploits by type (webapps, remote, local, dos, hardware)',
inputSchema: {
type: 'object',
properties: {
type: {
type: 'string',
description: 'Exploit type (webapps, remote, local, dos, hardware)'
},
platform: {
type: 'string',
description: 'Filter by platform'
},
verified: {
type: 'boolean',
description: 'Filter by verified status'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
},
required: ['type']
}
},
{
name: 'search_by_author',
description: 'Find all exploits by a specific author',
inputSchema: {
type: 'object',
properties: {
author: {
type: 'string',
description: 'Author name (partial match supported)'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
},
required: ['author']
}
},
{
name: 'search_by_date_range',
description: 'Find exploits within a specific date range',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date (YYYY-MM-DD)'
},
end_date: {
type: 'string',
description: 'End date (YYYY-MM-DD)'
},
platform: {
type: 'string',
description: 'Filter by platform'
},
type: {
type: 'string',
description: 'Filter by exploit type'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
},
required: ['start_date', 'end_date']
}
},
{
name: 'search_by_tags',
description: 'Search exploits by generated tags (e.g., sql injection, xss, buffer overflow)',
inputSchema: {
type: 'object',
properties: {
tags: {
type: 'array',
items: { type: 'string' },
description: 'Array of tags to search for'
},
match_all: {
type: 'boolean',
description: 'Whether to match all tags (AND) or any tag (OR). Default: false (OR)'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)'
}
},
required: ['tags']
}
},
{
name: 'get_platform_statistics',
description: 'Get detailed statistics for a specific platform',
inputSchema: {
type: 'object',
properties: {
platform: {
type: 'string',
description: 'Platform name'
}
},
required: ['platform']
}
},
{
name: 'get_trending_exploits',
description: 'Find recently added exploits (last 30 days by default)',
inputSchema: {
type: 'object',
properties: {
days: {
type: 'number',
description: 'Number of days to look back (default: 30)'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
}
}
}
},
{
name: 'compare_exploits',
description: 'Compare multiple exploits side-by-side',
inputSchema: {
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'number' },
description: 'Array of exploit IDs to compare (2-10 exploits)'
}
},
required: ['ids']
}
},
{
name: 'get_exploit_timeline',
description: 'Get chronological timeline of exploits for a CVE or search term',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'CVE ID or search term'
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
}
},
required: ['query']
}
},
{
name: 'batch_get_exploits',
description: 'Retrieve multiple exploits efficiently in one call',
inputSchema: {
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'number' },
description: 'Array of exploit IDs (max 50)'
},
include_code: {
type: 'boolean',
description: 'Whether to include exploit code (default: false)'
}
},
required: ['ids']
}
},
{
name: 'get_related_exploits',
description: 'Find exploits related to a specific exploit (same platform, similar CVE, etc.)',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'Reference exploit ID'
},
relation_type: {
type: 'string',
description: 'Type of relation: platform, author, cve, or tags (default: platform)',
enum: ['platform', 'author', 'cve', 'tags']
},
limit: {
type: 'number',
description: `Maximum number of results (default: ${config.maxResults})`
}
},
required: ['id']
}
},
{
name: 'validate_exploit_id',
description: 'Check if an exploit ID exists and is valid',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'Exploit ID to validate'
}
},
required: ['id']
}
},
{
name: 'export_search_results',
description: 'Export search results in various formats (JSON, CSV)',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query'
},
format: {
type: 'string',
description: 'Export format (json or csv)',
enum: ['json', 'csv']
},
platform: {
type: 'string',
description: 'Filter by platform'
},
type: {
type: 'string',
description: 'Filter by exploit type'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 100)'
}
},
required: ['format']
}
}
]
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'search_exploits': {
const query = String(request.params.arguments?.query || '');
const platform = request.params.arguments?.platform ? String(request.params.arguments.platform) : undefined;
const type = request.params.arguments?.type ? String(request.params.arguments.type) : undefined;
const cve = request.params.arguments?.cve ? String(request.params.arguments.cve) : undefined;
const author = request.params.arguments?.author ? String(request.params.arguments.author) : undefined;
const startDate = request.params.arguments?.start_date ? String(request.params.arguments.start_date) : undefined;
const endDate = request.params.arguments?.end_date ? String(request.params.arguments.end_date) : undefined;
const verified = request.params.arguments?.verified !== undefined ? Boolean(request.params.arguments.verified) : undefined;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
const result = await db.searchExploits(query, {
platform,
type,
cve,
author,
startDate,
endDate,
verified,
limit,
offset
});
return {
content: [{
type: 'text',
text: JSON.stringify({
total: result.total,
offset,
limit,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Error searching exploits: ${(error as Error).message}`
);
}
}
case 'get_exploit': {
const id = Number(request.params.arguments?.id);
if (isNaN(id)) {
throw new McpError(
ErrorCode.InvalidParams,
'Exploit ID must be a number'
);
}
const includeCode = request.params.arguments?.include_code !== false;
try {
const exploit = await db.getExploitById(id);
if (!exploit) {
throw new McpError(
ErrorCode.InvalidParams,
`Exploit with ID ${id} not found`
);
}
const result: any = { ...exploit };
if (includeCode) {
const code = await exploitdb.getExploitCode(exploit.file);
if (code) {
result.code = code;
}
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Error getting exploit: ${(error as Error).message}`
);
}
}
case 'find_by_cve': {
const cve = String(request.params.arguments?.cve);
if (!cve) {
throw new McpError(
ErrorCode.InvalidParams,
'CVE ID is required'
);
}
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
try {
const exploits = await db.findExploitsByCve(cve, limit);
return {
content: [{
type: 'text',
text: JSON.stringify({
cve,
count: exploits.length,
exploits
}, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Error finding exploits by CVE: ${(error as Error).message}`
);
}
}
case 'get_recent_exploits': {
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
try {
const exploits = await db.getRecentExploits(limit);
return {
content: [{
type: 'text',
text: JSON.stringify({
count: exploits.length,
exploits
}, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Error getting recent exploits: ${(error as Error).message}`
);
}
}
case 'get_statistics': {
try {
const statistics = await db.getStatistics();
return {
content: [{
type: 'text',
text: JSON.stringify(statistics, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Error getting statistics: ${(error as Error).message}`
);
}
}
case 'search_by_platform': {
const platform = String(request.params.arguments?.platform);
const type = request.params.arguments?.type ? String(request.params.arguments.type) : undefined;
const verified = request.params.arguments?.verified !== undefined ? Boolean(request.params.arguments.verified) : undefined;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
const result = await db.searchExploits('', {
platform,
type,
verified,
limit,
offset
});
return {
content: [{
type: 'text',
text: JSON.stringify({
platform,
total: result.total,
offset,
limit,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error searching by platform: ${(error as Error).message}`
);
}
}
case 'search_by_type': {
const type = String(request.params.arguments?.type);
const platform = request.params.arguments?.platform ? String(request.params.arguments.platform) : undefined;
const verified = request.params.arguments?.verified !== undefined ? Boolean(request.params.arguments.verified) : undefined;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
const result = await db.searchExploits('', {
type,
platform,
verified,
limit,
offset
});
return {
content: [{
type: 'text',
text: JSON.stringify({
type,
total: result.total,
offset,
limit,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error searching by type: ${(error as Error).message}`
);
}
}
case 'search_by_author': {
const author = String(request.params.arguments?.author);
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
const result = await db.searchExploits('', {
author,
limit,
offset
});
return {
content: [{
type: 'text',
text: JSON.stringify({
author,
total: result.total,
offset,
limit,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error searching by author: ${(error as Error).message}`
);
}
}
case 'search_by_date_range': {
const startDate = String(request.params.arguments?.start_date);
const endDate = String(request.params.arguments?.end_date);
const platform = request.params.arguments?.platform ? String(request.params.arguments.platform) : undefined;
const type = request.params.arguments?.type ? String(request.params.arguments.type) : undefined;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
const result = await db.searchExploits('', {
startDate,
endDate,
platform,
type,
limit,
offset
});
return {
content: [{
type: 'text',
text: JSON.stringify({
date_range: { start: startDate, end: endDate },
total: result.total,
offset,
limit,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error searching by date range: ${(error as Error).message}`
);
}
}
case 'search_by_tags': {
const tags = request.params.arguments?.tags as string[];
const matchAll = request.params.arguments?.match_all ? Boolean(request.params.arguments.match_all) : false;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
const offset = request.params.arguments?.offset ? Number(request.params.arguments.offset) : 0;
try {
// Build query to search for tags in description
const query = matchAll ? tags.join(' ') : tags.join('|');
const result = await db.searchExploits(query, { limit, offset });
// Filter results to match tag criteria
const filteredExploits = result.exploits.filter(exploit => {
if (!exploit.tags || exploit.tags.length === 0) return false;
if (matchAll) {
return tags.every(tag => exploit.tags!.includes(tag));
} else {
return tags.some(tag => exploit.tags!.includes(tag));
}
});
return {
content: [{
type: 'text',
text: JSON.stringify({
tags,
match_all: matchAll,
total: filteredExploits.length,
exploits: filteredExploits.slice(0, limit)
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error searching by tags: ${(error as Error).message}`
);
}
}
case 'get_platform_statistics': {
const platform = String(request.params.arguments?.platform);
try {
const stats = await db.getStatistics();
const platformStats = stats.byPlatform.find((p: any) => p.platform === platform);
if (!platformStats) {
throw new McpError(
ErrorCode.InvalidParams,
`Platform '${platform}' not found`
);
}
// Get type distribution for this platform
const result = await db.searchExploits('', { platform, limit: 1000 });
const typeDistribution: { [key: string]: number } = {};
result.exploits.forEach(e => {
typeDistribution[e.type] = (typeDistribution[e.type] || 0) + 1;
});
return {
content: [{
type: 'text',
text: JSON.stringify({
platform,
total_exploits: platformStats.count,
type_distribution: typeDistribution,
sample_exploits: result.exploits.slice(0, 5)
}, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) throw error;
throw new McpError(
ErrorCode.InternalError,
`Error getting platform statistics: ${(error as Error).message}`
);
}
}
case 'get_trending_exploits': {
const days = request.params.arguments?.days ? Number(request.params.arguments.days) : 30;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
try {
const endDate = new Date().toISOString().split('T')[0];
const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const result = await db.searchExploits('', {
startDate,
endDate,
limit
});
return {
content: [{
type: 'text',
text: JSON.stringify({
period_days: days,
date_range: { start: startDate, end: endDate },
count: result.exploits.length,
exploits: result.exploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error getting trending exploits: ${(error as Error).message}`
);
}
}
case 'compare_exploits': {
const ids = request.params.arguments?.ids as number[];
if (!ids || ids.length < 2 || ids.length > 10) {
throw new McpError(
ErrorCode.InvalidParams,
'Must provide between 2 and 10 exploit IDs'
);
}
try {
const exploits = await Promise.all(
ids.map(id => db.getExploitById(id))
);
const validExploits = exploits.filter(e => e !== null);
if (validExploits.length === 0) {
throw new McpError(
ErrorCode.InvalidParams,
'None of the provided IDs were found'
);
}
// Create comparison summary
const comparison = {
total_compared: validExploits.length,
platforms: [...new Set(validExploits.map(e => e!.platform))],
types: [...new Set(validExploits.map(e => e!.type))],
authors: [...new Set(validExploits.map(e => e!.author))],
date_range: {
earliest: validExploits.reduce((min, e) => e!.date < min ? e!.date : min, validExploits[0]!.date),
latest: validExploits.reduce((max, e) => e!.date > max ? e!.date : max, validExploits[0]!.date)
},
exploits: validExploits
};
return {
content: [{
type: 'text',
text: JSON.stringify(comparison, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) throw error;
throw new McpError(
ErrorCode.InternalError,
`Error comparing exploits: ${(error as Error).message}`
);
}
}
case 'get_exploit_timeline': {
const query = String(request.params.arguments?.query);
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
try {
const result = await db.searchExploits(query, { limit: 1000 });
// Sort by date and create timeline
const timeline = result.exploits
.sort((a, b) => a.date.localeCompare(b.date))
.slice(0, limit);
// Group by year
const byYear: { [key: string]: number } = {};
timeline.forEach(e => {
const year = e.date.substring(0, 4);
byYear[year] = (byYear[year] || 0) + 1;
});
return {
content: [{
type: 'text',
text: JSON.stringify({
query,
total_found: result.total,
timeline_count: timeline.length,
by_year: byYear,
timeline
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error getting exploit timeline: ${(error as Error).message}`
);
}
}
case 'batch_get_exploits': {
const ids = request.params.arguments?.ids as number[];
const includeCode = request.params.arguments?.include_code ? Boolean(request.params.arguments.include_code) : false;
if (!ids || ids.length === 0 || ids.length > 50) {
throw new McpError(
ErrorCode.InvalidParams,
'Must provide between 1 and 50 exploit IDs'
);
}
try {
const exploits = await Promise.all(
ids.map(async id => {
const exploit = await db.getExploitById(id);
if (!exploit) return null;
const result: any = { ...exploit };
if (includeCode) {
const code = await exploitdb.getExploitCode(exploit.file);
if (code) result.code = code;
}
return result;
})
);
const validExploits = exploits.filter(e => e !== null);
return {
content: [{
type: 'text',
text: JSON.stringify({
requested: ids.length,
found: validExploits.length,
exploits: validExploits
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error batch getting exploits: ${(error as Error).message}`
);
}
}
case 'get_related_exploits': {
const id = Number(request.params.arguments?.id);
const relationType = request.params.arguments?.relation_type ? String(request.params.arguments.relation_type) : 'platform';
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : config.maxResults;
try {
const exploit = await db.getExploitById(id);
if (!exploit) {
throw new McpError(
ErrorCode.InvalidParams,
`Exploit with ID ${id} not found`
);
}
let result;
switch (relationType) {
case 'platform':
result = await db.searchExploits('', { platform: exploit.platform, limit: limit + 1 });
break;
case 'author':
result = await db.searchExploits('', { author: exploit.author, limit: limit + 1 });
break;
case 'cve':
if (exploit.cve) {
const exploits = await db.findExploitsByCve(exploit.cve, limit + 1);
result = { exploits, total: exploits.length };
} else {
result = { exploits: [], total: 0 };
}
break;
case 'tags':
if (exploit.tags && exploit.tags.length > 0) {
const query = exploit.tags.join(' ');
result = await db.searchExploits(query, { limit: limit + 1 });
} else {
result = { exploits: [], total: 0 };
}
break;
default:
throw new McpError(
ErrorCode.InvalidParams,
`Invalid relation type: ${relationType}`
);
}
// Filter out the original exploit
const related = result.exploits.filter(e => e.id !== id).slice(0, limit);
return {
content: [{
type: 'text',
text: JSON.stringify({
reference_exploit: id,
relation_type: relationType,
total_related: related.length,
related_exploits: related
}, null, 2)
}]
};
} catch (error) {
if (error instanceof McpError) throw error;
throw new McpError(
ErrorCode.InternalError,
`Error getting related exploits: ${(error as Error).message}`
);
}
}
case 'validate_exploit_id': {
const id = Number(request.params.arguments?.id);
if (isNaN(id)) {
throw new McpError(
ErrorCode.InvalidParams,
'Exploit ID must be a number'
);
}
try {
const exploit = await db.getExploitById(id);
return {
content: [{
type: 'text',
text: JSON.stringify({
id,
exists: exploit !== null,
exploit: exploit ? {
description: exploit.description,
platform: exploit.platform,
type: exploit.type,
date: exploit.date
} : null
}, null, 2)
}]
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error validating exploit ID: ${(error as Error).message}`
);
}
}
case 'export_search_results': {
const query = request.params.arguments?.query ? String(request.params.arguments.query) : '';
const format = String(request.params.arguments?.format);
const platform = request.params.arguments?.platform ? String(request.params.arguments.platform) : undefined;
const type = request.params.arguments?.type ? String(request.params.arguments.type) : undefined;
const limit = request.params.arguments?.limit ? Number(request.params.arguments.limit) : 100;
try {
const result = await db.searchExploits(query, {
platform,
type,
limit
});
if (format === 'csv') {
// Convert to CSV
const headers = ['ID', 'Description', 'Date', 'Author', 'Type', 'Platform', 'CVE', 'Verified'];
const rows = result.exploits.map(e => [
e.id,
`"${e.description.replace(/"/g, '""')}"`,
e.date,
`"${e.author.replace(/"/g, '""')}"`,
e.type,
e.platform,
e.cve || '',
e.verified
]);
const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
return {
content: [{
type: 'text',
text: csv
}]
};
} else {
// JSON format
return {
content: [{
type: 'text',
text: JSON.stringify({
format: 'json',
total: result.total,
exported: result.exploits.length,
exploits: result.exploits
}, null, 2)
}]
};
}
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Error exporting search results: ${(error as Error).message}`
);
}
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
/**
* Initialize the server
*/
async init() {
try {
// Ensure data directory exists
await ensureDataDir();
// Initialize the database
await db.initDatabase();
// Set up automatic updates if configured
if (config.updateInterval > 0) {
this.updateInterval = setInterval(async () => {
try {
console.log('Running scheduled database update...');
await exploitdb.updateDatabase();
console.log('Scheduled database update completed');
} catch (error) {
console.error('Error in scheduled database update:', error);
}
}, config.updateInterval * 60 * 60 * 1000); // Convert hours to milliseconds
}
console.log('ExploitDB MCP server initialized');
} catch (error) {
console.error('Error initializing server:', error);
throw error;
}
}
/**
* Start the server
*/
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('ExploitDB MCP server running on stdio');
}
/**
* Close the server
*/
async close() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
await db.closeDatabase();
await this.server.close();
}
}
// Main function
const main = async () => {
try {
const server = new ExploitDBServer();
await server.init();
await server.run();
} catch (error) {
console.error('Server error:', error);
process.exit(1);
}
};
// Run the main function
main();