mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-24 20:06:21 +02:00
263 lines
8.6 KiB
Go
263 lines
8.6 KiB
Go
package mcpserver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
|
|
appsoc "github.com/syntrex-lab/gomcp/internal/application/soc"
|
|
"github.com/syntrex-lab/gomcp/internal/application/tools"
|
|
domsoc "github.com/syntrex-lab/gomcp/internal/domain/soc"
|
|
)
|
|
|
|
// SetSOCService enables SOC tools (soc_ingest, soc_events, soc_incidents, soc_sensors, soc_dashboard).
|
|
func (s *Server) SetSOCService(svc *appsoc.Service) {
|
|
s.socSvc = svc
|
|
}
|
|
|
|
// registerSOCTools registers SENTINEL AI SOC MCP tools.
|
|
func (s *Server) registerSOCTools() {
|
|
if s.socSvc == nil {
|
|
log.Printf("SOC Service not configured — skipping SOC tools registration")
|
|
return
|
|
}
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_ingest",
|
|
mcp.WithDescription("Ingest a security event into the SOC pipeline. Runs Secret Scanner (Step 0), rate limits, decision logging, correlation, and playbook matching."),
|
|
mcp.WithString("source", mcp.Description("Event source: sentinel_core, shield, immune, gomcp, lattice, external"), mcp.Required()),
|
|
mcp.WithString("severity", mcp.Description("Severity: INFO, LOW, MEDIUM, HIGH, CRITICAL"), mcp.Required()),
|
|
mcp.WithString("category", mcp.Description("Event category: jailbreak, injection, exfiltration, tool_abuse, auth_bypass, etc."), mcp.Required()),
|
|
mcp.WithString("description", mcp.Description("Event description"), mcp.Required()),
|
|
mcp.WithString("payload", mcp.Description("Raw payload for Secret Scanner Step 0 (optional)")),
|
|
mcp.WithString("sensor_id", mcp.Description("Sensor ID (auto-assigns from source if empty)")),
|
|
mcp.WithString("sensor_key", mcp.Description("Sensor API key for authentication (§17.3 T-01)")),
|
|
mcp.WithNumber("confidence", mcp.Description("Confidence score 0.0-1.0 (default 0.5)")),
|
|
mcp.WithBoolean("zero_g_mode", mcp.Description("Tag as Zero-G mode (Strike Force operation, §13.4)")),
|
|
),
|
|
s.handleSOCIngest,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_events",
|
|
mcp.WithDescription("List recent SOC events. Returns events sorted by timestamp descending."),
|
|
mcp.WithNumber("limit", mcp.Description("Max events to return (default 20)")),
|
|
),
|
|
s.handleSOCEvents,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_incidents",
|
|
mcp.WithDescription("List SOC incidents with optional status filter."),
|
|
mcp.WithString("status", mcp.Description("Filter by status: OPEN, INVESTIGATING, RESOLVED (empty = all)")),
|
|
mcp.WithNumber("limit", mcp.Description("Max incidents to return (default 20)")),
|
|
),
|
|
s.handleSOCIncidents,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_verdict",
|
|
mcp.WithDescription("Update an incident status (manual analyst verdict)."),
|
|
mcp.WithString("incident_id", mcp.Description("Incident ID"), mcp.Required()),
|
|
mcp.WithString("status", mcp.Description("New status: INVESTIGATING, RESOLVED"), mcp.Required()),
|
|
),
|
|
s.handleSOCVerdict,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_sensors",
|
|
mcp.WithDescription("List all registered SOC sensors with status and heartbeat info."),
|
|
),
|
|
s.handleSOCSensors,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_dashboard",
|
|
mcp.WithDescription("Get SOC dashboard KPIs: events, incidents, sensor health, decision chain integrity (§12.2)."),
|
|
),
|
|
s.handleSOCDashboard,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_compliance",
|
|
mcp.WithDescription("Generate EU AI Act Article 15 compliance report with requirement status and evidence (§12.3)."),
|
|
),
|
|
s.handleSOCCompliance,
|
|
)
|
|
|
|
s.mcp.AddTool(
|
|
mcp.NewTool("soc_playbook_run",
|
|
mcp.WithDescription("Manually execute a playbook against an incident (§10, §12.1)."),
|
|
mcp.WithString("playbook_id", mcp.Description("Playbook ID (e.g. pb-auto-block-jailbreak)"), mcp.Required()),
|
|
mcp.WithString("incident_id", mcp.Description("Target incident ID"), mcp.Required()),
|
|
),
|
|
s.handleSOCPlaybookRun,
|
|
)
|
|
}
|
|
|
|
// --- SOC Handlers ---
|
|
|
|
func (s *Server) handleSOCIngest(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
source := domsoc.EventSource(req.GetString("source", "external"))
|
|
severity := domsoc.EventSeverity(req.GetString("severity", "MEDIUM"))
|
|
category := req.GetString("category", "")
|
|
description := req.GetString("description", "")
|
|
|
|
if category == "" || description == "" {
|
|
return errorResult(fmt.Errorf("'category' and 'description' are required")), nil
|
|
}
|
|
|
|
event := domsoc.NewSOCEvent(source, severity, category, description)
|
|
event.Payload = req.GetString("payload", "")
|
|
event.SensorID = req.GetString("sensor_id", "")
|
|
event.SensorKey = req.GetString("sensor_key", "")
|
|
event.ZeroGMode = req.GetBool("zero_g_mode", false)
|
|
|
|
args := req.GetArguments()
|
|
if v, ok := args["confidence"]; ok {
|
|
if n, ok := v.(float64); ok {
|
|
updated := event.WithConfidence(n)
|
|
event = updated
|
|
}
|
|
}
|
|
|
|
id, incident, err := s.socSvc.IngestEvent(event)
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"event_id": id,
|
|
"status": "INGESTED",
|
|
}
|
|
if incident != nil {
|
|
result["incident_created"] = true
|
|
result["incident_id"] = incident.ID
|
|
result["incident_severity"] = incident.Severity
|
|
result["correlation_rule"] = incident.CorrelationRule
|
|
}
|
|
|
|
return textResult(tools.ToJSON(result)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCEvents(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
limit := req.GetInt("limit", 20)
|
|
events, err := s.socSvc.ListEvents("", limit) // MCP: global view
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(tools.ToJSON(events)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCIncidents(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
status := req.GetString("status", "")
|
|
limit := req.GetInt("limit", 20)
|
|
incidents, err := s.socSvc.ListIncidents("", status, limit) // MCP: global view
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(tools.ToJSON(incidents)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCVerdict(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
incidentID := req.GetString("incident_id", "")
|
|
statusStr := req.GetString("status", "")
|
|
if incidentID == "" || statusStr == "" {
|
|
return errorResult(fmt.Errorf("'incident_id' and 'status' are required")), nil
|
|
}
|
|
|
|
status := domsoc.IncidentStatus(statusStr)
|
|
if status != domsoc.StatusInvestigating && status != domsoc.StatusResolved {
|
|
return errorResult(fmt.Errorf("invalid status: must be INVESTIGATING or RESOLVED")), nil
|
|
}
|
|
|
|
if err := s.socSvc.UpdateVerdict(incidentID, status); err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"incident_id": incidentID,
|
|
"new_status": statusStr,
|
|
"updated": true,
|
|
}
|
|
return textResult(toJSON(result)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCSensors(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
sensors, err := s.socSvc.ListSensors("") // MCP: global view
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(tools.ToJSON(sensors)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCDashboard(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
dashboard, err := s.socSvc.Dashboard("") // MCP: global view
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(dashboard.JSON()), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCCompliance(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
report, err := s.socSvc.ComplianceReport()
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(tools.ToJSON(report)), nil
|
|
}
|
|
|
|
func (s *Server) handleSOCPlaybookRun(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
if s.socSvc == nil {
|
|
return errorResult(fmt.Errorf("soc service not configured")), nil
|
|
}
|
|
|
|
playbookID := req.GetString("playbook_id", "")
|
|
incidentID := req.GetString("incident_id", "")
|
|
if playbookID == "" || incidentID == "" {
|
|
return errorResult(fmt.Errorf("'playbook_id' and 'incident_id' are required")), nil
|
|
}
|
|
|
|
result, err := s.socSvc.RunPlaybook(playbookID, incidentID)
|
|
if err != nil {
|
|
return errorResult(err), nil
|
|
}
|
|
return textResult(tools.ToJSON(result)), nil
|
|
}
|
|
|
|
// toJSON marshals to JSON string (local helper).
|
|
func toJSON(v interface{}) string {
|
|
data, _ := json.MarshalIndent(v, "", " ")
|
|
return string(data)
|
|
}
|