iflow-mcp-m507_ai-soc-agent 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
  2. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
  3. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
  7. src/__init__.py +8 -0
  8. src/ai_controller/README.md +139 -0
  9. src/ai_controller/__init__.py +12 -0
  10. src/ai_controller/agent_executor.py +596 -0
  11. src/ai_controller/cli/__init__.py +2 -0
  12. src/ai_controller/cli/main.py +243 -0
  13. src/ai_controller/session_manager.py +409 -0
  14. src/ai_controller/web/__init__.py +2 -0
  15. src/ai_controller/web/server.py +1181 -0
  16. src/ai_controller/web/static/css/README.md +102 -0
  17. src/api/__init__.py +13 -0
  18. src/api/case_management.py +271 -0
  19. src/api/edr.py +187 -0
  20. src/api/kb.py +136 -0
  21. src/api/siem.py +308 -0
  22. src/core/__init__.py +10 -0
  23. src/core/config.py +242 -0
  24. src/core/config_storage.py +684 -0
  25. src/core/dto.py +50 -0
  26. src/core/errors.py +36 -0
  27. src/core/logging.py +128 -0
  28. src/integrations/__init__.py +8 -0
  29. src/integrations/case_management/__init__.py +5 -0
  30. src/integrations/case_management/iris/__init__.py +11 -0
  31. src/integrations/case_management/iris/iris_client.py +885 -0
  32. src/integrations/case_management/iris/iris_http.py +274 -0
  33. src/integrations/case_management/iris/iris_mapper.py +263 -0
  34. src/integrations/case_management/iris/iris_models.py +128 -0
  35. src/integrations/case_management/thehive/__init__.py +8 -0
  36. src/integrations/case_management/thehive/thehive_client.py +193 -0
  37. src/integrations/case_management/thehive/thehive_http.py +147 -0
  38. src/integrations/case_management/thehive/thehive_mapper.py +190 -0
  39. src/integrations/case_management/thehive/thehive_models.py +125 -0
  40. src/integrations/cti/__init__.py +6 -0
  41. src/integrations/cti/local_tip/__init__.py +10 -0
  42. src/integrations/cti/local_tip/local_tip_client.py +90 -0
  43. src/integrations/cti/local_tip/local_tip_http.py +110 -0
  44. src/integrations/cti/opencti/__init__.py +10 -0
  45. src/integrations/cti/opencti/opencti_client.py +101 -0
  46. src/integrations/cti/opencti/opencti_http.py +418 -0
  47. src/integrations/edr/__init__.py +6 -0
  48. src/integrations/edr/elastic_defend/__init__.py +6 -0
  49. src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
  50. src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
  51. src/integrations/eng/__init__.py +10 -0
  52. src/integrations/eng/clickup/__init__.py +8 -0
  53. src/integrations/eng/clickup/clickup_client.py +513 -0
  54. src/integrations/eng/clickup/clickup_http.py +156 -0
  55. src/integrations/eng/github/__init__.py +8 -0
  56. src/integrations/eng/github/github_client.py +169 -0
  57. src/integrations/eng/github/github_http.py +158 -0
  58. src/integrations/eng/trello/__init__.py +8 -0
  59. src/integrations/eng/trello/trello_client.py +207 -0
  60. src/integrations/eng/trello/trello_http.py +162 -0
  61. src/integrations/kb/__init__.py +12 -0
  62. src/integrations/kb/fs_kb_client.py +313 -0
  63. src/integrations/siem/__init__.py +6 -0
  64. src/integrations/siem/elastic/__init__.py +6 -0
  65. src/integrations/siem/elastic/elastic_client.py +3319 -0
  66. src/integrations/siem/elastic/elastic_http.py +165 -0
  67. src/mcp/README.md +183 -0
  68. src/mcp/TOOLS.md +2827 -0
  69. src/mcp/__init__.py +13 -0
  70. src/mcp/__main__.py +18 -0
  71. src/mcp/agent_profiles.py +408 -0
  72. src/mcp/flow_agent_profiles.py +424 -0
  73. src/mcp/mcp_server.py +4086 -0
  74. src/mcp/rules_engine.py +487 -0
  75. src/mcp/runbook_manager.py +264 -0
  76. src/orchestrator/__init__.py +11 -0
  77. src/orchestrator/incident_workflow.py +244 -0
  78. src/orchestrator/tools_case.py +1085 -0
  79. src/orchestrator/tools_cti.py +359 -0
  80. src/orchestrator/tools_edr.py +315 -0
  81. src/orchestrator/tools_eng.py +378 -0
  82. src/orchestrator/tools_kb.py +156 -0
  83. src/orchestrator/tools_siem.py +1709 -0
  84. src/web/__init__.py +8 -0
  85. src/web/config_server.py +511 -0
@@ -0,0 +1,487 @@
1
+ """
2
+ Rules engine for executing investigation workflows.
3
+
4
+ This module provides a rules engine that can chain together multiple
5
+ investigation skills to perform automated investigations.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from dataclasses import dataclass
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from ..api.case_management import CaseManagementClient
15
+ from ..api.edr import EDRClient
16
+ from ..api.siem import SIEMClient
17
+ from ..core.errors import IntegrationError
18
+ from ..orchestrator import incident_workflow
19
+
20
+
21
+ @dataclass
22
+ class Rule:
23
+ """
24
+ Represents an investigation rule/workflow.
25
+ """
26
+
27
+ name: str
28
+ description: Optional[str] = None
29
+ trigger: Optional[str] = None # Condition that triggers the rule
30
+ actions: List[str] = None # type: ignore
31
+ enabled: bool = True
32
+
33
+ def __post_init__(self):
34
+ if self.actions is None:
35
+ self.actions = []
36
+
37
+
38
+ @dataclass
39
+ class RuleExecutionContext:
40
+ """
41
+ Execution context for a rule, containing variables and state.
42
+ """
43
+
44
+ rule_name: str
45
+ variables: Dict[str, Any]
46
+ results: Dict[str, Any]
47
+
48
+ def __init__(self, rule_name: str):
49
+ self.rule_name = rule_name
50
+ self.variables = {}
51
+ self.results = {}
52
+
53
+
54
+ class RulesEngine:
55
+ """
56
+ Engine for executing investigation rules/workflows.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ case_client: Optional[CaseManagementClient] = None,
62
+ siem_client: Optional[SIEMClient] = None,
63
+ edr_client: Optional[EDRClient] = None,
64
+ ):
65
+ """
66
+ Initialize the rules engine with clients.
67
+
68
+ Args:
69
+ case_client: Case management client.
70
+ siem_client: SIEM client.
71
+ edr_client: EDR client.
72
+ """
73
+ self.case_client = case_client
74
+ self.siem_client = siem_client
75
+ self.edr_client = edr_client
76
+ self.rules: Dict[str, Rule] = {}
77
+
78
+ def load_rule(self, rule_dict: Dict[str, Any]) -> Rule:
79
+ """
80
+ Load a rule from a dictionary.
81
+
82
+ Args:
83
+ rule_dict: Dictionary containing rule definition.
84
+
85
+ Returns:
86
+ Rule object.
87
+
88
+ Raises:
89
+ IntegrationError: If rule definition is invalid.
90
+ """
91
+ try:
92
+ rule = Rule(
93
+ name=rule_dict["name"],
94
+ description=rule_dict.get("description"),
95
+ trigger=rule_dict.get("trigger"),
96
+ actions=rule_dict.get("actions", []),
97
+ enabled=rule_dict.get("enabled", True),
98
+ )
99
+ return rule
100
+ except KeyError as e:
101
+ raise IntegrationError(f"Invalid rule definition: missing {e}") from e
102
+
103
+ def add_rule(self, rule: Rule) -> None:
104
+ """
105
+ Add a rule to the engine.
106
+
107
+ Args:
108
+ rule: Rule to add.
109
+ """
110
+ self.rules[rule.name] = rule
111
+
112
+ def list_rules(self) -> List[Dict[str, Any]]:
113
+ """
114
+ List all registered rules.
115
+
116
+ Returns:
117
+ List of rule metadata dictionaries.
118
+ """
119
+ return [
120
+ {
121
+ "name": rule.name,
122
+ "description": rule.description,
123
+ "enabled": rule.enabled,
124
+ "action_count": len(rule.actions),
125
+ }
126
+ for rule in self.rules.values()
127
+ ]
128
+
129
+ def execute_rule(
130
+ self,
131
+ rule_name: str,
132
+ context: Optional[Dict[str, Any]] = None,
133
+ ) -> Dict[str, Any]:
134
+ """
135
+ Execute a rule by name.
136
+
137
+ Args:
138
+ rule_name: Name of the rule to execute.
139
+ context: Optional context variables to pass to the rule.
140
+
141
+ Returns:
142
+ Dictionary containing execution results.
143
+
144
+ Raises:
145
+ IntegrationError: If rule not found or execution fails.
146
+ """
147
+ if rule_name not in self.rules:
148
+ raise IntegrationError(f"Rule not found: {rule_name}")
149
+
150
+ rule = self.rules[rule_name]
151
+ if not rule.enabled:
152
+ raise IntegrationError(f"Rule is disabled: {rule_name}")
153
+
154
+ # Create execution context
155
+ exec_context = RuleExecutionContext(rule_name)
156
+ if context:
157
+ exec_context.variables.update(context)
158
+
159
+ # Check trigger condition if present
160
+ if rule.trigger:
161
+ if not self._evaluate_trigger(rule.trigger, exec_context):
162
+ # Return a human-readable explanation that the rule was not executed
163
+ message = (
164
+ f"Automated rule '{rule_name}' was **not executed** because the "
165
+ f"trigger condition was not met.\n\n"
166
+ f"- Trigger condition: `{rule.trigger}`\n"
167
+ f"- Context variables at evaluation time: "
168
+ f"{json.dumps(exec_context.variables, default=str)}\n\n"
169
+ "You can adjust the context variables or the trigger condition and "
170
+ "call `execute_rule` again if you want this workflow to run."
171
+ )
172
+ return {
173
+ "success": False,
174
+ "rule_name": rule_name,
175
+ "summary": message,
176
+ "details": {
177
+ "reason": "trigger_not_met",
178
+ "trigger": rule.trigger,
179
+ "context": exec_context.variables,
180
+ },
181
+ }
182
+
183
+ # Execute actions and capture low-level results
184
+ results: List[Dict[str, Any]] = []
185
+ for action in rule.actions:
186
+ try:
187
+ result = self._execute_action(action, exec_context)
188
+ results.append({"action": action, "success": True, "result": result})
189
+ except Exception as e:
190
+ results.append(
191
+ {
192
+ "action": action,
193
+ "success": False,
194
+ "error": str(e),
195
+ }
196
+ )
197
+ # Optionally continue or stop on error
198
+ # For now, we'll continue but log the error
199
+
200
+ # Build an AI-friendly, playbook-style narrative around the execution
201
+ narrative = self._format_execution_narrative(
202
+ rule=rule,
203
+ exec_context=exec_context,
204
+ results=results,
205
+ )
206
+
207
+ # Return both the human-readable narrative and the structured data
208
+ return {
209
+ "success": True,
210
+ "rule_name": rule_name,
211
+ "playbook": narrative,
212
+ "summary": narrative.split("\n\n")[0] if "\n\n" in narrative else narrative,
213
+ "actions": results,
214
+ "final_context": exec_context.variables,
215
+ }
216
+
217
+ def _evaluate_trigger(self, trigger: str, context: RuleExecutionContext) -> bool:
218
+ """
219
+ Evaluate a trigger condition.
220
+
221
+ Args:
222
+ trigger: Trigger condition string.
223
+ context: Execution context.
224
+
225
+ Returns:
226
+ True if trigger condition is met, False otherwise.
227
+ """
228
+ # Simple trigger evaluation - can be extended with more sophisticated logic
229
+ # For now, support simple variable comparisons
230
+ try:
231
+ # Replace variables in trigger with values from context
232
+ evaluated = trigger
233
+ for key, value in context.variables.items():
234
+ evaluated = evaluated.replace(f"${key}", str(value))
235
+ evaluated = evaluated.replace(f"{{key}}", str(value))
236
+
237
+ # Simple evaluation (can be made more sophisticated)
238
+ # For now, just check if it's a boolean expression
239
+ return eval(evaluated, {"__builtins__": {}}, {})
240
+ except Exception:
241
+ # If evaluation fails, assume trigger is not met
242
+ return False
243
+
244
+ def _execute_action(self, action: str, context: RuleExecutionContext) -> Any:
245
+ """
246
+ Execute a single action.
247
+
248
+ Args:
249
+ action: Action name or action definition.
250
+ context: Execution context.
251
+
252
+ Returns:
253
+ Action result.
254
+ """
255
+ # Parse action (can be a string name or a dict with parameters)
256
+ if isinstance(action, dict):
257
+ action_name = action.get("name") or action.get("action")
258
+ action_params = action.get("params", {})
259
+ else:
260
+ action_name = action
261
+ action_params = {}
262
+
263
+ # Map action names to functions
264
+ action_map = {
265
+ "create_case_from_alert": self._create_case_from_alert,
266
+ "search_siem_for_related_events": self._search_siem_for_related_events,
267
+ "enrich_case_from_siem": self._enrich_case_from_siem,
268
+ "enrich_case_from_edr": self._enrich_case_from_edr,
269
+ "close_incident": self._close_incident,
270
+ "assign_case": self._assign_case,
271
+ }
272
+
273
+ if action_name not in action_map:
274
+ raise IntegrationError(f"Unknown action: {action_name}")
275
+
276
+ # Execute the action
277
+ return action_map[action_name](action_params, context)
278
+
279
+ def _format_execution_narrative(
280
+ self,
281
+ rule: Rule,
282
+ exec_context: RuleExecutionContext,
283
+ results: List[Dict[str, Any]],
284
+ ) -> str:
285
+ """
286
+ Build a human-readable, playbook-style narrative of a rule execution.
287
+
288
+ This is optimized for MCP/LLM usage: it reads like a mini runbook the
289
+ AI agent can follow, while still reflecting the concrete actions taken
290
+ and their outcomes.
291
+ """
292
+
293
+ lines: List[str] = []
294
+
295
+ # Header / objective
296
+ lines.append(f"Automated investigation rule executed: **{rule.name}**")
297
+ if rule.description:
298
+ lines.append(f"Description: {rule.description}")
299
+
300
+ # Context snapshot
301
+ if exec_context.variables:
302
+ lines.append("")
303
+ lines.append("Initial context provided to this workflow:")
304
+ for key, value in exec_context.variables.items():
305
+ try:
306
+ encoded = json.dumps(value, default=str)
307
+ except TypeError:
308
+ encoded = str(value)
309
+ # Keep very long values readable by truncating
310
+ if len(encoded) > 300:
311
+ encoded = encoded[:297] + "..."
312
+ lines.append(f"- **{key}**: {encoded}")
313
+
314
+ # Executed steps
315
+ lines.append("")
316
+ lines.append("Execution steps and outcomes:")
317
+ if not results:
318
+ lines.append("- No actions were defined for this rule.")
319
+ else:
320
+ for idx, step in enumerate(results, start=1):
321
+ action = step.get("action")
322
+ success = step.get("success", False)
323
+ status = "SUCCESS" if success else "FAILED"
324
+
325
+ # Map internal action names to friendly labels
326
+ friendly_names = {
327
+ "create_case_from_alert": "Create case from alert",
328
+ "search_siem_for_related_events": "Search SIEM for related events",
329
+ "enrich_case_from_siem": "Enrich case with SIEM data",
330
+ "enrich_case_from_edr": "Enrich case with EDR data",
331
+ "close_incident": "Close incident in case management",
332
+ "assign_case": "Assign case to analyst",
333
+ }
334
+ friendly_action = friendly_names.get(str(action), str(action))
335
+
336
+ lines.append(f"{idx}. **{friendly_action}** — {status}")
337
+
338
+ if success and "result" in step:
339
+ result = step["result"]
340
+ if isinstance(result, dict):
341
+ for k, v in result.items():
342
+ try:
343
+ v_str = json.dumps(v, default=str)
344
+ except TypeError:
345
+ v_str = str(v)
346
+ if len(v_str) > 200:
347
+ v_str = v_str[:197] + "..."
348
+ lines.append(f" - {k}: {v_str}")
349
+ else:
350
+ lines.append(f" - Result: {result}")
351
+ elif not success:
352
+ error = step.get("error", "Unknown error")
353
+ lines.append(f" - Error: {error}")
354
+
355
+ # Final state / follow-up guidance
356
+ lines.append("")
357
+ lines.append("Final investigation state and guidance for the AI agent:")
358
+
359
+ # Highlight a few commonly important context variables if present
360
+ key_hints = []
361
+ if "case_id" in exec_context.variables:
362
+ key_hints.append(
363
+ f"- Review and continue work on case **{exec_context.variables['case_id']}** "
364
+ "in the case management system."
365
+ )
366
+ if "siem_search_results" in exec_context.variables:
367
+ key_hints.append(
368
+ "- Use the stored `siem_search_results` (in context) to reason about "
369
+ "related events, time ranges, and entities."
370
+ )
371
+
372
+ if key_hints:
373
+ lines.extend(key_hints)
374
+ else:
375
+ lines.append(
376
+ "- Use the context variables and step outcomes above to decide on the "
377
+ "next manual investigation or response actions."
378
+ )
379
+
380
+ lines.append(
381
+ "- Treat this output as a high-level playbook: you can reference the "
382
+ "steps and their results when planning further tool calls."
383
+ )
384
+
385
+ return "\n".join(lines)
386
+
387
+ def _create_case_from_alert(
388
+ self, params: Dict[str, Any], context: RuleExecutionContext
389
+ ) -> Any:
390
+ """Create a case from an alert."""
391
+ if not self.case_client:
392
+ raise IntegrationError("Case management client not configured")
393
+ if not self.siem_client:
394
+ raise IntegrationError("SIEM client not configured")
395
+
396
+ # This would need the actual alert object - simplified for now
397
+ # In practice, you'd get the alert from context or params
398
+ raise IntegrationError("create_case_from_alert requires alert object")
399
+
400
+ def _search_siem_for_related_events(
401
+ self, params: Dict[str, Any], context: RuleExecutionContext
402
+ ) -> Any:
403
+ """Search SIEM for related events."""
404
+ if not self.siem_client:
405
+ raise IntegrationError("SIEM client not configured")
406
+
407
+ query = params.get("query") or context.variables.get("search_query", "")
408
+ limit = params.get("limit", 100)
409
+
410
+ result = self.siem_client.search_security_events(query=query, limit=limit)
411
+ context.variables["siem_search_results"] = result
412
+ return {"event_count": len(result.events), "total_count": result.total_count}
413
+
414
+ def _enrich_case_from_siem(
415
+ self, params: Dict[str, Any], context: RuleExecutionContext
416
+ ) -> Any:
417
+ """Enrich a case from SIEM data."""
418
+ if not self.case_client:
419
+ raise IntegrationError("Case management client not configured")
420
+ if not self.siem_client:
421
+ raise IntegrationError("SIEM client not configured")
422
+
423
+ case_id = params.get("case_id") or context.variables.get("case_id")
424
+ if not case_id:
425
+ raise IntegrationError("case_id required for enrich_case_from_siem")
426
+
427
+ observables = incident_workflow.enrich_case_from_siem(
428
+ case_id=case_id,
429
+ case_client=self.case_client,
430
+ siem_client=self.siem_client,
431
+ )
432
+ return {"observables_added": len(observables)}
433
+
434
+ def _enrich_case_from_edr(
435
+ self, params: Dict[str, Any], context: RuleExecutionContext
436
+ ) -> Any:
437
+ """Enrich a case from EDR data."""
438
+ if not self.case_client:
439
+ raise IntegrationError("Case management client not configured")
440
+ if not self.edr_client:
441
+ raise IntegrationError("EDR client not configured")
442
+
443
+ case_id = params.get("case_id") or context.variables.get("case_id")
444
+ endpoint_id = params.get("endpoint_id")
445
+
446
+ observables = incident_workflow.enrich_case_from_edr(
447
+ case_id=case_id,
448
+ case_client=self.case_client,
449
+ edr_client=self.edr_client,
450
+ endpoint_id=endpoint_id,
451
+ )
452
+ return {"observables_added": len(observables)}
453
+
454
+ def _close_incident(
455
+ self, params: Dict[str, Any], context: RuleExecutionContext
456
+ ) -> Any:
457
+ """Close an incident."""
458
+ if not self.case_client:
459
+ raise IntegrationError("Case management client not configured")
460
+
461
+ case_id = params.get("case_id") or context.variables.get("case_id")
462
+ if not case_id:
463
+ raise IntegrationError("case_id required for close_incident")
464
+
465
+ resolution_notes = params.get("resolution_notes")
466
+ case = incident_workflow.close_incident(
467
+ case_id=case_id,
468
+ case_client=self.case_client,
469
+ resolution_notes=resolution_notes,
470
+ )
471
+ return {"case_id": case.id, "status": case.status.value}
472
+
473
+ def _assign_case(
474
+ self, params: Dict[str, Any], context: RuleExecutionContext
475
+ ) -> Any:
476
+ """Assign a case to an analyst."""
477
+ if not self.case_client:
478
+ raise IntegrationError("Case management client not configured")
479
+
480
+ case_id = params.get("case_id") or context.variables.get("case_id")
481
+ assignee = params.get("assignee")
482
+ if not case_id or not assignee:
483
+ raise IntegrationError("case_id and assignee required for assign_case")
484
+
485
+ assignment = self.case_client.assign_case(case_id, assignee)
486
+ return {"case_id": assignment.case_id, "assignee": assignment.assignee}
487
+