@superadnim/osint-mcp-server
Version:
Professional OSINT MCP Server for intelligence gathering with privacy protection
295 lines • 13.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MindMapManager = void 0;
const uuid_1 = require("uuid");
class MindMapManager {
cache;
logger;
investigationCache = new Map();
constructor(cache, logger) {
this.cache = cache;
this.logger = logger;
}
async handleAction(investigationId, action, args) {
switch (action) {
case 'create':
return this.createInvestigation(investigationId, args);
case 'add_node':
return this.addNode(investigationId, args);
case 'add_connection':
return this.addConnection(investigationId, args);
case 'view':
return this.viewInvestigation(investigationId);
case 'export':
return this.exportInvestigation(investigationId, args);
case 'search':
return this.searchInvestigation(investigationId, args);
default:
throw new Error(`Unknown mind map action: ${action}`);
}
}
async createInvestigation(investigationId, args) {
const investigation = {
id: investigationId,
name: args.name || `Investigation ${investigationId}`,
description: args.description,
nodes: [],
connections: [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
metadata: {
creator: 'osint-mcp-server',
version: '1.0.0',
},
};
this.investigationCache.set(investigationId, investigation);
this.cache.set(`investigation:${investigationId}`, investigation, 7 * 24 * 3600); // 7 days
this.logger.info('Investigation created', { investigation_id: investigationId });
return investigation;
}
async addNode(investigationId, args) {
const investigation = await this.getInvestigation(investigationId);
if (!args.node_data || typeof args.node_data !== 'object') {
throw new Error('node_data is required for add_node action');
}
const nodeData = args.node_data;
const node = {
id: (0, uuid_1.v4)(),
type: nodeData.type,
content: nodeData.content,
metadata: nodeData.metadata || {},
confidence: nodeData.confidence || 0.5,
source: nodeData.source,
timestamp: new Date().toISOString(),
};
investigation.nodes.push(node);
investigation.updated_at = new Date().toISOString();
await this.saveInvestigation(investigation);
this.logger.info('Node added to investigation', {
investigation_id: investigationId,
node_id: node.id,
node_type: node.type,
});
return node;
}
async addConnection(investigationId, args) {
const investigation = await this.getInvestigation(investigationId);
if (!args.connection_data || typeof args.connection_data !== 'object') {
throw new Error('connection_data is required for add_connection action');
}
const connectionData = args.connection_data;
// Validate that the nodes exist
const fromNodeExists = investigation.nodes.some(node => node.id === connectionData.from_node);
const toNodeExists = investigation.nodes.some(node => node.id === connectionData.to_node);
if (!fromNodeExists || !toNodeExists) {
throw new Error('Both from_node and to_node must exist in the investigation');
}
const connection = {
id: (0, uuid_1.v4)(),
from_node: connectionData.from_node,
to_node: connectionData.to_node,
relationship: connectionData.relationship,
strength: connectionData.strength || 0.5,
evidence: connectionData.evidence || [],
timestamp: new Date().toISOString(),
};
investigation.connections.push(connection);
investigation.updated_at = new Date().toISOString();
await this.saveInvestigation(investigation);
this.logger.info('Connection added to investigation', {
investigation_id: investigationId,
connection_id: connection.id,
relationship: connection.relationship,
});
return connection;
}
async viewInvestigation(investigationId) {
return this.getInvestigation(investigationId);
}
async exportInvestigation(investigationId, args) {
const investigation = await this.getInvestigation(investigationId);
const format = args.export_format || 'json';
switch (format) {
case 'json':
return this.exportAsJSON(investigation);
case 'graphml':
return this.exportAsGraphML(investigation);
case 'csv':
return this.exportAsCSV(investigation);
case 'markdown':
return this.exportAsMarkdown(investigation);
default:
throw new Error(`Unsupported export format: ${format}`);
}
}
async searchInvestigation(investigationId, args) {
const investigation = await this.getInvestigation(investigationId);
const searchTerm = args.search_term || '';
if (!searchTerm) {
throw new Error('search_term is required for search action');
}
const matchingNodes = investigation.nodes.filter(node => node.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
node.type.toLowerCase().includes(searchTerm.toLowerCase()));
const matchingConnections = investigation.connections.filter(connection => connection.relationship.toLowerCase().includes(searchTerm.toLowerCase()) ||
connection.evidence.some(evidence => evidence.toLowerCase().includes(searchTerm.toLowerCase())));
return {
search_term: searchTerm,
matching_nodes: matchingNodes,
matching_connections: matchingConnections,
total_matches: matchingNodes.length + matchingConnections.length,
};
}
async getInvestigation(investigationId) {
// Check memory cache first
const cached = this.investigationCache.get(investigationId);
if (cached) {
return cached;
}
// Check persistent cache
const investigation = this.cache.get(`investigation:${investigationId}`);
if (investigation) {
this.investigationCache.set(investigationId, investigation);
return investigation;
}
throw new Error(`Investigation not found: ${investigationId}`);
}
async saveInvestigation(investigation) {
this.investigationCache.set(investigation.id, investigation);
this.cache.set(`investigation:${investigation.id}`, investigation, 7 * 24 * 3600); // 7 days
}
exportAsJSON(investigation) {
return JSON.stringify(investigation, null, 2);
}
exportAsGraphML(investigation) {
let graphml = `<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="type" for="node" attr.name="type" attr.type="string"/>
<key id="content" for="node" attr.name="content" attr.type="string"/>
<key id="confidence" for="node" attr.name="confidence" attr.type="double"/>
<key id="relationship" for="edge" attr.name="relationship" attr.type="string"/>
<key id="strength" for="edge" attr.name="strength" attr.type="double"/>
<graph id="${investigation.id}" edgedefault="directed">
`;
// Add nodes
for (const node of investigation.nodes) {
graphml += ` <node id="${node.id}">
<data key="type">${this.escapeXML(node.type)}</data>
<data key="content">${this.escapeXML(node.content)}</data>
<data key="confidence">${node.confidence}</data>
</node>
`;
}
// Add edges
for (const connection of investigation.connections) {
graphml += ` <edge source="${connection.from_node}" target="${connection.to_node}">
<data key="relationship">${this.escapeXML(connection.relationship)}</data>
<data key="strength">${connection.strength}</data>
</edge>
`;
}
graphml += ` </graph>
</graphml>`;
return graphml;
}
exportAsCSV(investigation) {
let csv = 'Type,ID,Content,Details\n';
// Add nodes
for (const node of investigation.nodes) {
csv += `Node,${node.id},"${this.escapeCSV(node.content)}","Type: ${node.type}, Confidence: ${node.confidence}"\n`;
}
// Add connections
for (const connection of investigation.connections) {
csv += `Connection,${connection.id},"${this.escapeCSV(connection.relationship)}","From: ${connection.from_node}, To: ${connection.to_node}, Strength: ${connection.strength}"\n`;
}
return csv;
}
exportAsMarkdown(investigation) {
let markdown = `# ${investigation.name}\n\n`;
if (investigation.description) {
markdown += `${investigation.description}\n\n`;
}
markdown += `**Created:** ${investigation.created_at}\n`;
markdown += `**Last Updated:** ${investigation.updated_at}\n\n`;
markdown += `## Nodes (${investigation.nodes.length})\n\n`;
for (const node of investigation.nodes) {
markdown += `### ${node.type}: ${node.content}\n`;
markdown += `- **ID:** ${node.id}\n`;
markdown += `- **Confidence:** ${node.confidence}\n`;
markdown += `- **Source:** ${node.source}\n`;
markdown += `- **Timestamp:** ${node.timestamp}\n\n`;
}
markdown += `## Connections (${investigation.connections.length})\n\n`;
for (const connection of investigation.connections) {
const fromNode = investigation.nodes.find(n => n.id === connection.from_node);
const toNode = investigation.nodes.find(n => n.id === connection.to_node);
markdown += `### ${fromNode?.content || connection.from_node} → ${toNode?.content || connection.to_node}\n`;
markdown += `- **Relationship:** ${connection.relationship}\n`;
markdown += `- **Strength:** ${connection.strength}\n`;
if (connection.evidence.length > 0) {
markdown += `- **Evidence:**\n`;
for (const evidence of connection.evidence) {
markdown += ` - ${evidence}\n`;
}
}
markdown += `- **Timestamp:** ${connection.timestamp}\n\n`;
}
return markdown;
}
escapeXML(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
escapeCSV(text) {
return text.replace(/"/g, '""');
}
async autoCorrelate(investigationId) {
const investigation = await this.getInvestigation(investigationId);
const newConnections = [];
// Simple auto-correlation logic
for (let i = 0; i < investigation.nodes.length; i++) {
for (let j = i + 1; j < investigation.nodes.length; j++) {
const node1 = investigation.nodes[i];
const node2 = investigation.nodes[j];
// Check if nodes should be connected based on content similarity
const similarity = this.calculateSimilarity(node1.content, node2.content);
if (similarity > 0.7) {
const connection = {
id: (0, uuid_1.v4)(),
from_node: node1.id,
to_node: node2.id,
relationship: 'similar_content',
strength: similarity,
evidence: ['Auto-detected content similarity'],
timestamp: new Date().toISOString(),
};
newConnections.push(connection);
}
}
}
// Add new connections to investigation
investigation.connections.push(...newConnections);
investigation.updated_at = new Date().toISOString();
await this.saveInvestigation(investigation);
this.logger.info('Auto-correlation completed', {
investigation_id: investigationId,
new_connections: newConnections.length,
});
return newConnections;
}
calculateSimilarity(text1, text2) {
const words1 = text1.toLowerCase().split(/\s+/);
const words2 = text2.toLowerCase().split(/\s+/);
const intersection = words1.filter(word => words2.includes(word));
const union = [...new Set([...words1, ...words2])];
return union.length > 0 ? intersection.length / union.length : 0;
}
}
exports.MindMapManager = MindMapManager;
//# sourceMappingURL=mind-map.js.map