feat: add configurable tool call blocking and monitoring

This commit is contained in:
Ed1s0nZ
2026-09-08 09:44:32 +08:00
parent c70da22de7
commit 6ad9ea2d13
54 changed files with 4635 additions and 152 deletions
+44 -8
View File
@@ -11,6 +11,7 @@ import (
"cyberstrike-ai/internal/authctx"
"cyberstrike-ai/internal/config"
"cyberstrike-ai/internal/toolguard"
"go.uber.org/zap"
)
@@ -74,6 +75,7 @@ type ExternalMCPManager struct {
reconnectLastTry map[string]time.Time
reconnectAttempts map[string]int
toolAuthorizer func(context.Context, string, map[string]interface{}) error
toolGuard *toolguard.Manager
executionService *ExecutionService
toolWaitTimeout time.Duration
toolResultMaxBytes int
@@ -96,6 +98,23 @@ func (m *ExternalMCPManager) SetToolAuthorizer(authorizer func(context.Context,
m.mu.Unlock()
}
// SetToolGuard installs safety rules evaluated before dispatch to external MCPs.
func (m *ExternalMCPManager) SetToolGuard(guard *toolguard.Manager) {
if m == nil {
return
}
m.mu.Lock()
m.toolGuard = guard
m.mu.Unlock()
}
func (m *ExternalMCPManager) checkToolGuard(toolName string, args map[string]interface{}) *ToolResult {
m.mu.RLock()
guard := m.toolGuard
m.mu.RUnlock()
return toolGuardBlockedResult(guard, toolName, args)
}
// NewExternalMCPManagerWithStorage 创建外部MCP管理器(带持久化存储)
func NewExternalMCPManagerWithStorage(logger *zap.Logger, storage MonitorStorage) *ExternalMCPManager {
manager := &ExternalMCPManager{
@@ -685,6 +704,7 @@ func (m *ExternalMCPManager) CallTool(ctx context.Context, toolName string, args
}
var mcpName, actualToolName string
var client ExternalMCPClient
var blockedByGuard bool
handle, err := m.executionService.Submit(ctx, ExecutionRequest{
ToolName: toolName,
Arguments: args,
@@ -702,6 +722,10 @@ func (m *ExternalMCPManager) CallTool(ctx context.Context, toolName string, args
} else if authenticated {
return nil, fmt.Errorf("external tool authorization policy is not configured")
}
if blocked := m.checkToolGuard(toolName, args); blocked != nil {
blockedByGuard = true
return nil, &toolGuardBlockError{result: blocked}
}
// 解析工具名称:name::toolName
if idx := findSubstring(toolName, "::"); idx > 0 {
@@ -741,6 +765,11 @@ func (m *ExternalMCPManager) CallTool(ctx context.Context, toolName string, args
return release, nil
},
Run: func(runCtx context.Context) (*ToolResult, error) {
// Rules may have changed while this execution waited for a slot.
if blocked := m.checkToolGuard(toolName, args); blocked != nil {
blockedByGuard = true
return blocked, nil
}
result, callErr := client.CallTool(runCtx, actualToolName, args)
if callErr != nil {
m.handleConnectionDead(mcpName, client, callErr)
@@ -748,11 +777,13 @@ func (m *ExternalMCPManager) CallTool(ctx context.Context, toolName string, args
return result, callErr
},
OnDone: func(exec *ToolExecution) {
failed := exec != nil && exec.Status != ToolExecutionStatusCompleted && exec.Status != ToolExecutionStatusCancelled
if mcpName != "" {
failed := exec != nil && executionStatusCountsAsFailed(exec.Status)
if mcpName != "" && !blockedByGuard && (exec == nil || exec.Status != ToolExecutionStatusBlocked) {
m.recordExternalMCPResult(mcpName, failed)
}
m.updateStats(toolName, failed)
if exec != nil {
m.updateStats(toolName, exec.Status)
}
},
})
if err != nil {
@@ -941,6 +972,9 @@ func (m *ExternalMCPManager) applyAbortUserNoteToCancelledToolResult(executionID
}
hasErr := err != nil && *err != nil
hasRes := result != nil && *result != nil
if hasRes && (*result).Blocked {
return false
}
if !hasErr && !hasRes {
return false
}
@@ -1098,15 +1132,15 @@ func (m *ExternalMCPManager) ActiveRunningExecutionIDs() map[string]struct{} {
}
// updateStats 更新统计信息
func (m *ExternalMCPManager) updateStats(toolName string, failed bool) {
func (m *ExternalMCPManager) updateStats(toolName string, status string) {
now := time.Now()
if m.storage != nil {
totalCalls := 1
successCalls := 0
failedCalls := 0
if failed {
if executionStatusCountsAsFailed(status) {
failedCalls = 1
} else {
} else if status == ToolExecutionStatusCompleted {
successCalls = 1
}
if err := m.storage.UpdateToolStats(toolName, totalCalls, successCalls, failedCalls, &now); err != nil {
@@ -1128,10 +1162,12 @@ func (m *ExternalMCPManager) updateStats(toolName string, failed bool) {
stats.TotalCalls++
stats.LastCallTime = &now
if failed {
if executionStatusCountsAsFailed(status) {
stats.FailedCalls++
} else {
} else if status == ToolExecutionStatusCompleted {
stats.SuccessCalls++
} else if status == ToolExecutionStatusBlocked {
stats.BlockedCalls++
}
}