mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-03-20 17:23:52 +00:00
116 modules | 100 vuln types | 18 API routes | 18 frontend pages Major features: - VulnEngine: 100 vuln types, 526+ payloads, 12 testers, anti-hallucination prompts - Autonomous Agent: 3-stream auto pentest, multi-session (5 concurrent), pause/resume/stop - CLI Agent: Claude Code / Gemini CLI / Codex CLI inside Kali containers - Validation Pipeline: negative controls, proof of execution, confidence scoring, judge - AI Reasoning: ReACT engine, token budget, endpoint classifier, CVE hunter, deep recon - Multi-Agent: 5 specialists + orchestrator + researcher AI + vuln type agents - RAG System: BM25/TF-IDF/ChromaDB vectorstore, few-shot, reasoning templates - Smart Router: 20 providers (8 CLI OAuth + 12 API), tier failover, token refresh - Kali Sandbox: container-per-scan, 56 tools, VPN support, on-demand install - Full IA Testing: methodology-driven comprehensive pentest sessions - Notifications: Discord, Telegram, WhatsApp/Twilio multi-channel alerts - Frontend: React/TypeScript with 18 pages, real-time WebSocket updates
321 lines
10 KiB
Python
321 lines
10 KiB
Python
"""
|
|
NeuroSploit v3 - MCP Server Management API
|
|
|
|
CRUD for Model Context Protocol server connections.
|
|
Persists to config/config.json mcp_servers section.
|
|
"""
|
|
import json
|
|
import asyncio
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
router = APIRouter()
|
|
|
|
CONFIG_PATH = Path(__file__).parent.parent.parent.parent / "config" / "config.json"
|
|
|
|
BUILTIN_SERVER = "neurosploit_tools"
|
|
|
|
|
|
# --- Schemas ---
|
|
|
|
class MCPServerCreate(BaseModel):
|
|
name: str = Field(..., min_length=1, max_length=100, description="Unique server identifier")
|
|
transport: str = Field("stdio", description="Transport type: stdio or sse")
|
|
command: Optional[str] = Field(None, description="Command for stdio transport")
|
|
args: Optional[List[str]] = Field(None, description="Args for stdio transport")
|
|
url: Optional[str] = Field(None, description="URL for sse transport")
|
|
env: Optional[Dict[str, str]] = Field(None, description="Environment variables")
|
|
description: str = Field("", description="Server description")
|
|
enabled: bool = Field(True, description="Whether server is enabled")
|
|
|
|
|
|
class MCPServerUpdate(BaseModel):
|
|
transport: Optional[str] = None
|
|
command: Optional[str] = None
|
|
args: Optional[List[str]] = None
|
|
url: Optional[str] = None
|
|
env: Optional[Dict[str, str]] = None
|
|
description: Optional[str] = None
|
|
enabled: Optional[bool] = None
|
|
|
|
|
|
class MCPServerResponse(BaseModel):
|
|
name: str
|
|
transport: str
|
|
command: Optional[str] = None
|
|
args: Optional[List[str]] = None
|
|
url: Optional[str] = None
|
|
env: Optional[Dict[str, str]] = None
|
|
description: str = ""
|
|
enabled: bool = True
|
|
is_builtin: bool = False
|
|
|
|
|
|
class MCPToolResponse(BaseModel):
|
|
name: str
|
|
description: str
|
|
server_name: str
|
|
|
|
|
|
# --- Config helpers ---
|
|
|
|
def _read_config() -> dict:
|
|
if not CONFIG_PATH.exists():
|
|
return {}
|
|
with open(CONFIG_PATH) as f:
|
|
return json.load(f)
|
|
|
|
|
|
def _write_config(config: dict):
|
|
with open(CONFIG_PATH, "w") as f:
|
|
json.dump(config, f, indent=4)
|
|
|
|
|
|
def _get_mcp_servers(config: dict) -> dict:
|
|
return config.get("mcp_servers", {})
|
|
|
|
|
|
def _server_to_response(name: str, server: dict) -> MCPServerResponse:
|
|
return MCPServerResponse(
|
|
name=name,
|
|
transport=server.get("transport", "stdio"),
|
|
command=server.get("command"),
|
|
args=server.get("args"),
|
|
url=server.get("url"),
|
|
env=server.get("env"),
|
|
description=server.get("description", ""),
|
|
enabled=server.get("enabled", True),
|
|
is_builtin=(name == BUILTIN_SERVER),
|
|
)
|
|
|
|
|
|
# --- Endpoints ---
|
|
|
|
@router.get("/servers", response_model=List[MCPServerResponse])
|
|
async def list_servers():
|
|
"""List all configured MCP servers."""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
return [_server_to_response(name, srv) for name, srv in servers.items()]
|
|
|
|
|
|
@router.get("/servers/{name}", response_model=MCPServerResponse)
|
|
async def get_server(name: str):
|
|
"""Get a specific MCP server configuration."""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
return _server_to_response(name, servers[name])
|
|
|
|
|
|
@router.post("/servers", response_model=MCPServerResponse)
|
|
async def create_server(body: MCPServerCreate):
|
|
"""Add a new MCP server configuration."""
|
|
config = _read_config()
|
|
if "mcp_servers" not in config:
|
|
config["mcp_servers"] = {}
|
|
|
|
servers = config["mcp_servers"]
|
|
if body.name in servers:
|
|
raise HTTPException(409, f"Server '{body.name}' already exists")
|
|
|
|
# Validate transport-specific fields
|
|
if body.transport == "stdio" and not body.command:
|
|
raise HTTPException(400, "stdio transport requires 'command' field")
|
|
if body.transport == "sse" and not body.url:
|
|
raise HTTPException(400, "sse transport requires 'url' field")
|
|
|
|
server_config = {
|
|
"transport": body.transport,
|
|
"description": body.description,
|
|
"enabled": body.enabled,
|
|
}
|
|
if body.command:
|
|
server_config["command"] = body.command
|
|
if body.args:
|
|
server_config["args"] = body.args
|
|
if body.url:
|
|
server_config["url"] = body.url
|
|
if body.env:
|
|
server_config["env"] = body.env
|
|
|
|
servers[body.name] = server_config
|
|
_write_config(config)
|
|
|
|
return _server_to_response(body.name, server_config)
|
|
|
|
|
|
@router.put("/servers/{name}", response_model=MCPServerResponse)
|
|
async def update_server(name: str, body: MCPServerUpdate):
|
|
"""Update an MCP server configuration."""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
|
|
srv = servers[name]
|
|
if body.transport is not None:
|
|
srv["transport"] = body.transport
|
|
if body.command is not None:
|
|
srv["command"] = body.command
|
|
if body.args is not None:
|
|
srv["args"] = body.args
|
|
if body.url is not None:
|
|
srv["url"] = body.url
|
|
if body.env is not None:
|
|
srv["env"] = body.env
|
|
if body.description is not None:
|
|
srv["description"] = body.description
|
|
if body.enabled is not None:
|
|
srv["enabled"] = body.enabled
|
|
|
|
_write_config(config)
|
|
return _server_to_response(name, srv)
|
|
|
|
|
|
@router.delete("/servers/{name}")
|
|
async def delete_server(name: str):
|
|
"""Delete an MCP server configuration."""
|
|
if name == BUILTIN_SERVER:
|
|
raise HTTPException(403, f"Cannot delete built-in server '{BUILTIN_SERVER}'")
|
|
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
|
|
del servers[name]
|
|
_write_config(config)
|
|
return {"message": f"Server '{name}' deleted"}
|
|
|
|
|
|
@router.post("/servers/{name}/toggle", response_model=MCPServerResponse)
|
|
async def toggle_server(name: str):
|
|
"""Toggle a server's enabled state."""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
|
|
srv = servers[name]
|
|
srv["enabled"] = not srv.get("enabled", True)
|
|
_write_config(config)
|
|
return _server_to_response(name, srv)
|
|
|
|
|
|
@router.post("/servers/{name}/test")
|
|
async def test_server_connection(name: str):
|
|
"""Test connection to an MCP server."""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
|
|
srv = servers[name]
|
|
transport = srv.get("transport", "stdio")
|
|
|
|
try:
|
|
if transport == "sse":
|
|
# Test SSE endpoint
|
|
import aiohttp
|
|
url = srv.get("url", "")
|
|
if not url:
|
|
return {"success": False, "error": "No URL configured", "tools_count": 0}
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
|
|
if resp.status < 400:
|
|
return {"success": True, "message": f"SSE endpoint reachable (HTTP {resp.status})", "tools_count": 0}
|
|
return {"success": False, "error": f"HTTP {resp.status}", "tools_count": 0}
|
|
|
|
elif transport == "stdio":
|
|
# Test stdio by checking command exists
|
|
import shutil
|
|
command = srv.get("command", "")
|
|
if not command:
|
|
return {"success": False, "error": "No command configured", "tools_count": 0}
|
|
|
|
if shutil.which(command):
|
|
return {"success": True, "message": f"Command '{command}' found in PATH", "tools_count": 0}
|
|
else:
|
|
return {"success": False, "error": f"Command '{command}' not found in PATH", "tools_count": 0}
|
|
|
|
except asyncio.TimeoutError:
|
|
return {"success": False, "error": "Connection timed out (5s)", "tools_count": 0}
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e), "tools_count": 0}
|
|
|
|
|
|
@router.get("/servers/{name}/tools", response_model=List[MCPToolResponse])
|
|
async def list_server_tools(name: str):
|
|
"""List available tools from an MCP server.
|
|
|
|
For the built-in server, returns tools from the registry.
|
|
For external servers, attempts to connect and query.
|
|
"""
|
|
config = _read_config()
|
|
servers = _get_mcp_servers(config)
|
|
|
|
if name not in servers:
|
|
raise HTTPException(404, f"MCP server '{name}' not found")
|
|
|
|
# For builtin server, return tools from the MCP server module
|
|
if name == BUILTIN_SERVER:
|
|
try:
|
|
from core.mcp_server import TOOLS
|
|
return [
|
|
MCPToolResponse(
|
|
name=t["name"],
|
|
description=t.get("description", ""),
|
|
server_name=name,
|
|
)
|
|
for t in TOOLS
|
|
]
|
|
except ImportError:
|
|
return []
|
|
|
|
# For external servers, try to connect via MCPToolClient
|
|
try:
|
|
from core.mcp_client import MCPToolClient
|
|
|
|
# Build minimal config for this single server
|
|
client_config = {
|
|
"mcp_servers": {
|
|
"enabled": True,
|
|
"servers": {name: servers[name]}
|
|
}
|
|
}
|
|
client = MCPToolClient(client_config)
|
|
|
|
connected = await asyncio.wait_for(client.connect(name), timeout=10)
|
|
if not connected:
|
|
raise HTTPException(502, f"Failed to connect to MCP server '{name}'")
|
|
|
|
tools_dict = await client.list_tools(name)
|
|
tool_list = tools_dict.get(name, [])
|
|
|
|
await client.disconnect_all()
|
|
|
|
return [
|
|
MCPToolResponse(
|
|
name=t.get("name", ""),
|
|
description=t.get("description", ""),
|
|
server_name=name,
|
|
)
|
|
for t in tool_list
|
|
]
|
|
except ImportError:
|
|
raise HTTPException(501, "MCP client library not installed")
|
|
except asyncio.TimeoutError:
|
|
raise HTTPException(504, "Connection to MCP server timed out (10s)")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(502, f"Failed to list tools: {str(e)}")
|