Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/modules/mcp/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,8 @@
{"name": "tenant_config.list", "status": "planned", "module": "eventprocessing", "title": "List tenant configs", "permission": "eventprocessing.read", "annotations": {"readOnlyHint": true}, "input": {"type": "object", "properties": {"asset_name": {"type": "string"}, "page": {"type": "integer"}, "size": {"type": "integer"}}}, "output": "{ items: TenantConfigResponse[], total: int64 }", "maps_to": "eventprocessing.TenantConfigUsecase.List", "rest_equivalent": "GET /api/v1/eventprocessing/tenant-config"},
{"name": "tenant_config.get", "status": "planned", "module": "eventprocessing", "title": "Get tenant config", "permission": "eventprocessing.read", "annotations": {"readOnlyHint": true}, "input": {"type": "object", "properties": {"asset_name": {"type": "string"}}, "required": ["asset_name"]}, "output": "TenantConfigResponse", "maps_to": "eventprocessing.TenantConfigUsecase.GetByID", "rest_equivalent": "GET /api/v1/eventprocessing/tenant-config/:id"},
{"name": "tenant_config.delete", "status": "planned", "module": "eventprocessing", "title": "Delete tenant config", "permission": "eventprocessing.write", "annotations": {"destructiveHint": true}, "input": {"type": "object", "properties": {"asset_name": {"type": "string"}}, "required": ["asset_name"]}, "output": "{ asset_name: string, deleted: true }", "maps_to": "eventprocessing.TenantConfigUsecase.Delete", "rest_equivalent": "DELETE /api/v1/eventprocessing/tenant-config/:id"},
{"name": "correlation_rule.create", "status": "planned", "module": "eventprocessing", "title": "Create correlation rule", "permission": "eventprocessing.write", "input": {"type": "object", "properties": {"rule_name": {"type": "string"}, "adversary": {"type": "string"}, "category": {"type": "string"}, "technique": {"type": "string"}, "description": {"type": "string"}, "definition": {"type": "object"}, "confidentiality": {"type": "integer"}, "integrity": {"type": "integer"}, "availability": {"type": "integer"}}, "required": ["rule_name", "definition"]}, "output": "CorrelationRuleResponse", "maps_to": "eventprocessing.CorrelationRuleUsecase.Create", "rest_equivalent": "POST /api/v1/eventprocessing/correlation-rule"},
{"name": "correlation_rule.update", "status": "planned", "module": "eventprocessing", "title": "Update correlation rule", "permission": "eventprocessing.write", "input": {"type": "object", "properties": {"rel_path": {"type": "string"}, "rule_name": {"type": "string"}, "definition": {"type": "object"}}, "required": ["rel_path"]}, "output": "CorrelationRuleResponse", "maps_to": "eventprocessing.CorrelationRuleUsecase.Update", "rest_equivalent": "PUT /api/v1/eventprocessing/correlation-rule"},
{"name": "correlation_rule.create", "status": "planned", "module": "eventprocessing", "title": "Create correlation rule", "permission": "eventprocessing.write", "input": {"type": "object", "properties": {"name": {"type": "string"}, "adversary": {"type": "string"}, "confidentiality": {"type": "integer"}, "integrity": {"type": "integer"}, "availability": {"type": "integer"}, "category": {"type": "string"}, "technique": {"type": "string"}, "description": {"type": "string"}, "references": {"type": "array"}, "definition": {"type": "string", "description": "CEL expression"}, "groupBy": {"type": "array", "items": {"type": "string"}}, "deduplicateBy": {"type": "array", "items": {"type": "string"}}, "correlation": {"type": "object"}, "ruleActive": {"type": "boolean"}, "dataTypes": {"type": "array"}}, "required": ["name", "definition", "dataTypes"]}, "output": "CorrelationRuleResponse", "maps_to": "eventprocessing.CorrelationRuleUsecase.Create", "rest_equivalent": "POST /api/v1/eventprocessing/correlation-rule"},
{"name": "correlation_rule.update", "status": "planned", "module": "eventprocessing", "title": "Update correlation rule", "permission": "eventprocessing.write", "input": {"type": "object", "properties": {"relPath": {"type": "string"}, "name": {"type": "string"}, "adversary": {"type": "string"}, "confidentiality": {"type": "integer"}, "integrity": {"type": "integer"}, "availability": {"type": "integer"}, "category": {"type": "string"}, "technique": {"type": "string"}, "description": {"type": "string"}, "references": {"type": "array"}, "definition": {"type": "string", "description": "CEL expression"}, "groupBy": {"type": "array", "items": {"type": "string"}}, "deduplicateBy": {"type": "array", "items": {"type": "string"}}, "correlation": {"type": "object"}, "ruleActive": {"type": "boolean"}, "dataTypes": {"type": "array"}}, "required": ["relPath"]}, "output": "CorrelationRuleResponse", "maps_to": "eventprocessing.CorrelationRuleUsecase.Update", "rest_equivalent": "PUT /api/v1/eventprocessing/correlation-rule"},
{"name": "correlation_rule.list", "status": "planned", "module": "eventprocessing", "title": "List correlation rules", "permission": "eventprocessing.read", "annotations": {"readOnlyHint": true}, "input": {"type": "object", "properties": {"rule_name": {"type": "string"}, "adversary": {"type": "string"}, "active": {"type": "boolean"}, "page": {"type": "integer"}, "size": {"type": "integer"}}}, "output": "{ items: CorrelationRuleResponse[], total: int64 }", "maps_to": "eventprocessing.CorrelationRuleUsecase.List", "rest_equivalent": "GET /api/v1/eventprocessing/correlation-rule/search-by-filters"},
{"name": "correlation_rule.get", "status": "planned", "module": "eventprocessing", "title": "Get correlation rule", "permission": "eventprocessing.read", "annotations": {"readOnlyHint": true}, "input": {"type": "object", "properties": {"rel_path": {"type": "string"}}, "required": ["rel_path"]}, "output": "CorrelationRuleResponse", "maps_to": "eventprocessing.CorrelationRuleUsecase.GetByRelPath", "rest_equivalent": "GET /api/v1/eventprocessing/correlation-rule/find"},
{"name": "correlation_rule.delete", "status": "planned", "module": "eventprocessing", "title": "Delete correlation rule", "permission": "eventprocessing.write", "annotations": {"destructiveHint": true}, "input": {"type": "object", "properties": {"rel_path": {"type": "string"}}, "required": ["rel_path"]}, "output": "{ rel_path: string, deleted: true }", "maps_to": "eventprocessing.CorrelationRuleUsecase.Delete", "rest_equivalent": "DELETE /api/v1/eventprocessing/correlation-rule"},
Expand Down
108 changes: 102 additions & 6 deletions backend/modules/mcp/tools_eventprocessing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package mcp

import (
"context"
"encoding/json"
"reflect"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/utmstack/utmstack/backend/modules/eventprocessing/dto"
Expand Down Expand Up @@ -188,27 +190,121 @@ type epRulePropertyValuesInput struct {
Value string `json:"value,omitempty"`
}

// MCP-only input shapes: the underlying DTO uses json.RawMessage for these
// fields, which the SDK reflects as []byte → array<integer> — wrong for the
// automation client. These wrappers carry the real per-field shape.
type epCorrelationRuleCreateInput struct {
RuleName string `json:"name" jsonschema:"Rule name"`
RuleAdversary string `json:"adversary" jsonschema:"origin | target"`
RuleConfidentiality int `json:"confidentiality" jsonschema:"0-3"`
RuleIntegrity int `json:"integrity" jsonschema:"0-3"`
RuleAvailability int `json:"availability" jsonschema:"0-3"`
RuleCategory string `json:"category"`
RuleTechnique string `json:"technique"`
RuleDescription string `json:"description"`
RuleReferences []any `json:"references,omitempty" jsonschema:"Reference URLs or objects"`
RuleDefinition string `json:"definition" jsonschema:"CEL expression, e.g. equals(\"log.channel\",\"Security\")"`
RuleGroupBy []string `json:"groupBy,omitempty"`
DeduplicateBy []string `json:"deduplicateBy,omitempty"`
Correlation any `json:"correlation,omitempty" jsonschema:"Optional multi-step correlation object"`
RuleActive bool `json:"ruleActive"`
DataTypes []dto.DataTypeRef `json:"dataTypes"`
}

type epCorrelationRuleUpdateInput struct {
RelPath string `json:"relPath" jsonschema:"Rule identity (YAML relative path)"`
RuleName string `json:"name"`
RuleAdversary string `json:"adversary" jsonschema:"origin | target"`
RuleConfidentiality int `json:"confidentiality" jsonschema:"0-3"`
RuleIntegrity int `json:"integrity" jsonschema:"0-3"`
RuleAvailability int `json:"availability" jsonschema:"0-3"`
RuleCategory string `json:"category"`
RuleTechnique string `json:"technique"`
RuleDescription string `json:"description"`
RuleReferences []any `json:"references,omitempty"`
RuleDefinition string `json:"definition" jsonschema:"CEL expression"`
RuleGroupBy []string `json:"groupBy,omitempty"`
DeduplicateBy []string `json:"deduplicateBy,omitempty"`
Correlation any `json:"correlation,omitempty"`
RuleActive bool `json:"ruleActive"`
DataTypes []dto.DataTypeRef `json:"dataTypes"`
}

func mustRaw(v any) json.RawMessage {
if v == nil || reflect.ValueOf(v).IsZero() {
return nil
}
b, err := json.Marshal(v)
if err != nil {
return nil
}
return b
}

func (in epCorrelationRuleCreateInput) toDTO() dto.CreateCorrelationRuleRequest {
return dto.CreateCorrelationRuleRequest{
RuleName: in.RuleName,
RuleAdversary: in.RuleAdversary,
RuleConfidentiality: in.RuleConfidentiality,
RuleIntegrity: in.RuleIntegrity,
RuleAvailability: in.RuleAvailability,
RuleCategory: in.RuleCategory,
RuleTechnique: in.RuleTechnique,
RuleDescription: in.RuleDescription,
RuleReferencesDef: mustRaw(in.RuleReferences),
RuleDefinitionDef: mustRaw(in.RuleDefinition),
RuleGroupByDef: mustRaw(in.RuleGroupBy),
DeduplicateByDef: mustRaw(in.DeduplicateBy),
CorrelationDef: mustRaw(in.Correlation),
RuleActive: in.RuleActive,
DataTypes: in.DataTypes,
}
}

func (in epCorrelationRuleUpdateInput) toDTO() dto.UpdateCorrelationRuleRequest {
return dto.UpdateCorrelationRuleRequest{
RelPath: in.RelPath,
RuleName: in.RuleName,
RuleAdversary: in.RuleAdversary,
RuleConfidentiality: in.RuleConfidentiality,
RuleIntegrity: in.RuleIntegrity,
RuleAvailability: in.RuleAvailability,
RuleCategory: in.RuleCategory,
RuleTechnique: in.RuleTechnique,
RuleDescription: in.RuleDescription,
RuleReferencesDef: mustRaw(in.RuleReferences),
RuleDefinitionDef: mustRaw(in.RuleDefinition),
RuleGroupByDef: mustRaw(in.RuleGroupBy),
DeduplicateByDef: mustRaw(in.DeduplicateBy),
CorrelationDef: mustRaw(in.Correlation),
RuleActive: in.RuleActive,
DataTypes: in.DataTypes,
}
}

func registerEPCorrelationRules(m *Module) {
uc := m.deps.EventProcessing.GetCorrelationRuleUsecase()

Add(m, &mcp.Tool{
Name: "correlation_rule.create", Title: "Create correlation rule",
}, Gate{Permission: "eventprocessing.write"},
func(ctx context.Context, _ *authz.Actor, in dto.CreateCorrelationRuleRequest) (any, error) {
if err := uc.Create(ctx, in); err != nil {
func(ctx context.Context, _ *authz.Actor, in epCorrelationRuleCreateInput) (any, error) {
req := in.toDTO()
if err := uc.Create(ctx, req); err != nil {
return nil, err
}
return map[string]any{"name": in.RuleName, "created": true}, nil
return map[string]any{"name": req.RuleName, "created": true}, nil
})

Add(m, &mcp.Tool{
Name: "correlation_rule.update", Title: "Update correlation rule",
}, Gate{Permission: "eventprocessing.write"},
func(ctx context.Context, _ *authz.Actor, in dto.UpdateCorrelationRuleRequest) (any, error) {
if err := uc.Update(ctx, in); err != nil {
func(ctx context.Context, _ *authz.Actor, in epCorrelationRuleUpdateInput) (any, error) {
req := in.toDTO()
if err := uc.Update(ctx, req); err != nil {
return nil, err
}
return map[string]any{"rel_path": in.RelPath, "updated": true}, nil
return map[string]any{"rel_path": req.RelPath, "updated": true}, nil
})

Add(m, &mcp.Tool{
Expand Down
Loading