mirror of
https://github.com/FunnyWolf/agentic-soc-platform.git
synced 2026-08-22 13:12:56 +02:00
refactor: simplify MCP tools — merge attach into create, clean up agents
- Merge attach_enrichment_to_target into create_enrichment (target_id required) - Merge attach_ticket_to_case into create_ticket (case_id required) - Merge get_case_discussions into list_cases (include_discussions param) - Remove comment_ai/summary_ai from update_case, add comment/summary - Remove unused agent files (agent_cmdb, agent_report, simpler/agents) - Update all skill files to match new MCP interfaces
This commit is contained in:
@@ -1,169 +0,0 @@
|
||||
import os
|
||||
from typing import Annotated, List, Literal, Any
|
||||
|
||||
from langchain.agents import create_agent
|
||||
from langchain_core.messages import HumanMessage, AIMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.graph.state import CompiledStateGraph
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from Lib.baseplaybook import LanggraphPlaybook
|
||||
from Lib.configs import DATA_DIR
|
||||
from Lib.llmapi import load_system_prompt_template
|
||||
from PLUGINS.LLM.llmapi import LLMAPI
|
||||
# Modify the following functions to the actual CMDB API
|
||||
from PLUGINS.Mock.CMDB.CMDB import get_ci_context_tool, fuzzy_search_ci_tool, get_cis_by_software_tool, get_cis_by_port_tool, get_cis_by_service_tool, \
|
||||
get_cis_by_user_tool
|
||||
|
||||
AGENT_NODE = "AGENT_NODE"
|
||||
TOOL_NODE = "TOOL_NODE"
|
||||
MAX_ITERATIONS = 2
|
||||
|
||||
|
||||
class AgentState(BaseModel):
|
||||
messages: Annotated[List[Any], add_messages] = Field(default_factory=list)
|
||||
loop_count: int = Field(default=0, description="Count of agent iterations")
|
||||
|
||||
|
||||
class AgentCMDB(object):
|
||||
|
||||
@staticmethod
|
||||
def cmdb_query_asset(
|
||||
query: Annotated[str, "The CMDB query in natural language (e.g., 'Find asset with IP 10.10.10.10')"]
|
||||
) -> Annotated[str, "A JSON containing asset details"]:
|
||||
"""
|
||||
Query internal asset information from CMDB.
|
||||
"""
|
||||
agent = AgentGraphCMDB()
|
||||
result = agent.cmdb_query(query)
|
||||
return result
|
||||
|
||||
|
||||
# Use langgraph to create a CMDB query agent for finer-grained control
|
||||
class AgentGraphCMDB(LanggraphPlaybook):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__() # do not delete this code
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
tools = [
|
||||
get_ci_context_tool,
|
||||
fuzzy_search_ci_tool,
|
||||
get_cis_by_software_tool,
|
||||
get_cis_by_port_tool,
|
||||
get_cis_by_service_tool,
|
||||
get_cis_by_user_tool,
|
||||
]
|
||||
|
||||
tool_node = ToolNode(tools, name=TOOL_NODE)
|
||||
|
||||
def route_after_agent(state: AgentState) -> Literal[TOOL_NODE, END]:
|
||||
if state.loop_count >= MAX_ITERATIONS:
|
||||
self.logger.debug(f"Max iterations ({MAX_ITERATIONS}) reached, ending agent.")
|
||||
return END
|
||||
last_message = state.messages[-1]
|
||||
if last_message.tool_calls:
|
||||
return TOOL_NODE
|
||||
return END
|
||||
|
||||
def agent_node(state: AgentState):
|
||||
self.logger.debug(f"Agent Node Invoked (Loop: {state.loop_count})")
|
||||
|
||||
system_prompt_template = self.load_system_prompt_template(f"system")
|
||||
system_message = system_prompt_template.format()
|
||||
|
||||
messages = [
|
||||
system_message,
|
||||
*state.messages
|
||||
]
|
||||
|
||||
if state.loop_count >= MAX_ITERATIONS - 1:
|
||||
self.logger.warning("Approaching max iterations, forcing agent to provide final answer.")
|
||||
|
||||
stop_instruction = (
|
||||
"\n\n[SYSTEM NOTICE]: You have reached the search limit. "
|
||||
"Do not call any more tools. Please provide your final conclusion "
|
||||
"based ONLY on the information gathered above."
|
||||
)
|
||||
messages.append(HumanMessage(content=stop_instruction))
|
||||
|
||||
llm_api = LLMAPI()
|
||||
base_llm = llm_api.get_model(tag=["fast"])
|
||||
response: AIMessage = base_llm.invoke(messages)
|
||||
else:
|
||||
llm_api = LLMAPI()
|
||||
llm = llm_api.get_model(tag=["fast", "function_calling"])
|
||||
llm_with_tools = llm.bind_tools(tools)
|
||||
response: AIMessage = llm_with_tools.invoke(messages)
|
||||
|
||||
if state.loop_count >= MAX_ITERATIONS - 1:
|
||||
if response.tool_calls:
|
||||
self.logger.info("Stripping hallucinated tool calls in final round.")
|
||||
response.tool_calls = []
|
||||
|
||||
return {"messages": [response], "loop_count": state.loop_count + 1}
|
||||
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
workflow.add_node(AGENT_NODE, agent_node)
|
||||
workflow.add_node(TOOL_NODE, tool_node)
|
||||
|
||||
workflow.set_entry_point(AGENT_NODE)
|
||||
workflow.add_conditional_edges(AGENT_NODE, route_after_agent)
|
||||
workflow.add_edge(TOOL_NODE, AGENT_NODE)
|
||||
|
||||
self.graph: CompiledStateGraph = workflow.compile(checkpointer=self.get_checkpointer())
|
||||
|
||||
def cmdb_query(self, query):
|
||||
self.graph.checkpointer.delete_thread(self.module_name)
|
||||
config = RunnableConfig()
|
||||
config["configurable"] = {"thread_id": self.module_name}
|
||||
self.agent_state = AgentState(messages=[HumanMessage(content=query)], loop_count=0)
|
||||
response = self.graph.invoke(self.agent_state, config)
|
||||
return response['messages'][-1].content
|
||||
|
||||
|
||||
# Use the create_agent method to create a CMDB query agent for a simpler implementation
|
||||
def cmdb_query(
|
||||
query: Annotated[str, "The CMDB query in natural language (e.g., 'Find asset with IP 10.10.10.10')"]
|
||||
) -> Annotated[str, "The query result in JSON format"]:
|
||||
"""
|
||||
Query internal asset information from CMDB using natural language.
|
||||
"""
|
||||
llm_api = LLMAPI()
|
||||
|
||||
llm = llm_api.get_model(tag=["fast", "function_calling"])
|
||||
|
||||
CMDB_AGENT_TOOLS = [
|
||||
get_ci_context_tool,
|
||||
fuzzy_search_ci_tool,
|
||||
get_cis_by_software_tool,
|
||||
get_cis_by_port_tool,
|
||||
get_cis_by_service_tool,
|
||||
get_cis_by_user_tool,
|
||||
]
|
||||
prompt_path = os.path.join(DATA_DIR, "Agent_CMDB", "system.md")
|
||||
system_prompt_template = load_system_prompt_template(prompt_path)
|
||||
agent = create_agent(
|
||||
model=llm,
|
||||
tools=CMDB_AGENT_TOOLS,
|
||||
system_prompt=system_prompt_template.format(),
|
||||
)
|
||||
|
||||
response = agent.invoke({"messages": [HumanMessage(content=query)]})
|
||||
|
||||
result = response['messages'][-1].content
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
query = "Find asset information for IP address 192.168.10.5"
|
||||
|
||||
# result = cmdb_query(query)
|
||||
|
||||
agent = AgentGraphCMDB()
|
||||
result = agent.cmdb_query(query)
|
||||
print(result)
|
||||
@@ -1,149 +0,0 @@
|
||||
from typing import Annotated, List, Any
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from langgraph.graph import END, StateGraph, add_messages
|
||||
from langgraph.graph.state import CompiledStateGraph
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
from Lib.api import get_current_time_str
|
||||
from Lib.baseplaybook import LanggraphPlaybook
|
||||
from PLUGINS.LLM.llmapi import LLMAPI
|
||||
|
||||
AGENT_NODE_SUMMARIZE = "SUMMARIZE"
|
||||
AGENT_NODE_REPORT = "REPORT"
|
||||
MAX_EVIDENCE_CHARS = 120000
|
||||
|
||||
_graph_agent_instance = None
|
||||
|
||||
|
||||
def get_report_graph() -> CompiledStateGraph:
|
||||
global _graph_agent_instance
|
||||
if _graph_agent_instance is None:
|
||||
_graph_agent_instance = GraphAgentReport()
|
||||
return _graph_agent_instance.graph
|
||||
|
||||
|
||||
def _normalize_content(content: Any) -> str:
|
||||
if content is None:
|
||||
return ""
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
return str(content)
|
||||
|
||||
|
||||
def _build_evidence_entries(messages: List[Any]) -> List[dict]:
|
||||
entries = []
|
||||
for idx, message in enumerate(messages, 1):
|
||||
role = getattr(message, "type", message.__class__.__name__)
|
||||
content = _normalize_content(getattr(message, "content", ""))
|
||||
tool_calls = getattr(message, "tool_calls", None)
|
||||
if tool_calls:
|
||||
content = f"{content}\n\nTool calls: {tool_calls}"
|
||||
entries.append({"id": f"E{idx}", "role": role, "content": content})
|
||||
return entries
|
||||
|
||||
|
||||
def _build_evidence_text(entries: List[dict]) -> str:
|
||||
lines = []
|
||||
for entry in entries:
|
||||
lines.append(f"{entry['id']} | role={entry['role']} | {entry['content']}")
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
def _build_citations(entries: List[dict]) -> List[dict]:
|
||||
citations = []
|
||||
for entry in entries:
|
||||
excerpt = entry["content"].replace("\n", " ").strip()
|
||||
if len(excerpt) > 500:
|
||||
excerpt = excerpt[:500] + "..."
|
||||
citations.append({"id": entry["id"], "role": entry["role"], "excerpt": excerpt})
|
||||
return citations
|
||||
|
||||
|
||||
def _split_entries(entries: List[dict], max_chars: int) -> List[List[dict]]:
|
||||
chunks = []
|
||||
current = []
|
||||
current_len = 0
|
||||
for entry in entries:
|
||||
entry_text = f"{entry['id']} | role={entry['role']} | {entry['content']}"
|
||||
entry_len = len(entry_text) + 2
|
||||
if current and current_len + entry_len > max_chars:
|
||||
chunks.append(current)
|
||||
current = []
|
||||
current_len = 0
|
||||
current.append(entry)
|
||||
current_len += entry_len
|
||||
if current:
|
||||
chunks.append(current)
|
||||
return chunks
|
||||
|
||||
|
||||
class AgentState(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
messages: Annotated[List[Any], add_messages] = Field(default_factory=list)
|
||||
summary_digest: str = Field(default="")
|
||||
report_markdown: str = Field(default="")
|
||||
citations: List[dict] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GraphAgentReport(LanggraphPlaybook):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._summary_prompt_template = self.load_system_prompt_template("summary")
|
||||
self._summary_merge_prompt_template = self.load_system_prompt_template("summary_merge")
|
||||
self._report_prompt_template = self.load_system_prompt_template("system")
|
||||
self._llm_api = LLMAPI()
|
||||
self._llm_base = self._llm_api.get_model(tag=["powerful"])
|
||||
self.graph = self._build_graph()
|
||||
|
||||
def _build_graph(self) -> CompiledStateGraph:
|
||||
def summarize_node(state: AgentState):
|
||||
entries = _build_evidence_entries(state.messages)
|
||||
citations = _build_citations(entries)
|
||||
evidence_text = _build_evidence_text(entries)
|
||||
if len(evidence_text) <= MAX_EVIDENCE_CHARS:
|
||||
system_message = self._summary_prompt_template.format(
|
||||
CURRENT_UTC_TIME=get_current_time_str(),
|
||||
REPORT_LANGUAGE="English"
|
||||
)
|
||||
human_message = HumanMessage(content=evidence_text)
|
||||
response: AIMessage = self._llm_base.invoke([system_message, human_message])
|
||||
return {"summary_digest": response.content, "citations": citations}
|
||||
chunks = _split_entries(entries, MAX_EVIDENCE_CHARS)
|
||||
partials = []
|
||||
for chunk in chunks:
|
||||
chunk_text = _build_evidence_text(chunk)
|
||||
system_message = self._summary_prompt_template.format(
|
||||
CURRENT_UTC_TIME=get_current_time_str(),
|
||||
REPORT_LANGUAGE="English"
|
||||
)
|
||||
human_message = HumanMessage(content=f"PARTIAL_DIGEST: true\n\n{chunk_text}")
|
||||
response: AIMessage = self._llm_base.invoke([system_message, human_message])
|
||||
partials.append(response.content)
|
||||
merge_system_message = self._summary_merge_prompt_template.format(
|
||||
CURRENT_UTC_TIME=get_current_time_str(),
|
||||
REPORT_LANGUAGE="English"
|
||||
)
|
||||
merge_human_message = HumanMessage(content="\n\n".join(partials))
|
||||
merged: AIMessage = self._llm_base.invoke([merge_system_message, merge_human_message])
|
||||
return {"summary_digest": merged.content, "citations": citations}
|
||||
|
||||
def report_node(state: AgentState):
|
||||
system_message = self._report_prompt_template.format(
|
||||
CURRENT_UTC_TIME=get_current_time_str(),
|
||||
REPORT_LANGUAGE="English"
|
||||
)
|
||||
human_message = HumanMessage(content=state.summary_digest)
|
||||
response: AIMessage = self._llm_base.invoke([system_message, human_message])
|
||||
return {
|
||||
"messages": [response],
|
||||
"report_markdown": response.content
|
||||
}
|
||||
|
||||
workflow = StateGraph(AgentState)
|
||||
workflow.add_node(AGENT_NODE_SUMMARIZE, summarize_node)
|
||||
workflow.add_node(AGENT_NODE_REPORT, report_node)
|
||||
workflow.set_entry_point(AGENT_NODE_SUMMARIZE)
|
||||
workflow.add_edge(AGENT_NODE_SUMMARIZE, AGENT_NODE_REPORT)
|
||||
workflow.add_edge(AGENT_NODE_REPORT, END)
|
||||
return workflow.compile()
|
||||
@@ -1,55 +0,0 @@
|
||||
from typing import Annotated
|
||||
|
||||
from langchain.agents import create_agent
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
from AGENTS.agent_siem import tools
|
||||
from Lib.api import get_current_time_str
|
||||
from Lib.configs import DATA_DIR
|
||||
from Lib.llmapi import load_system_prompt_template
|
||||
from Lib.log import logger
|
||||
from PLUGINS.LLM.llmapi import LLMAPI
|
||||
|
||||
|
||||
# Alternative, simpler agent implementation using create_agent
|
||||
def create_siem_agent(
|
||||
query: Annotated[str, "A natural language query for SIEM."]
|
||||
) -> Annotated[str, "A summary of the findings from the SIEM search."]:
|
||||
"""
|
||||
a simpler, stateless agent created using the create_agent factory function from langchain.agents.
|
||||
"""
|
||||
|
||||
logger.info(f"Creating SIEM agent with query: {query[:100]}...")
|
||||
prompt_path = os.path.join(DATA_DIR, "Agent_SIEM", "system_prompt.md")
|
||||
logger.debug(f"Loading system prompt from: {prompt_path}")
|
||||
system_prompt = load_system_prompt_template(prompt_path).format(CURRENT_UTC_TIME=get_current_time_str())
|
||||
logger.debug(f"System prompt loaded successfully")
|
||||
|
||||
llm_api = LLMAPI()
|
||||
llm = llm_api.get_model(tag=["fast", "function_calling"])
|
||||
logger.debug(f"LLM model obtained with tags: ['fast', 'function_calling']")
|
||||
|
||||
logger.debug(f"Creating agent with {len(tools)} tools")
|
||||
agent = create_agent(llm, tools, system_prompt=system_prompt)
|
||||
logger.debug(f"Agent created successfully")
|
||||
|
||||
logger.info(f"Invoking agent...")
|
||||
response = agent.invoke({"messages": [HumanMessage(content=query)]})
|
||||
logger.info(f"Agent invocation completed")
|
||||
|
||||
result = response['messages'][-1].content
|
||||
logger.info(f"Agent result extracted, result length: {len(result)} characters")
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ASP.settings")
|
||||
django.setup()
|
||||
|
||||
print("\n--- Using create_agent for Query ---")
|
||||
test_query = "Have there been any suspicious logins for the user 'admin' on Windows machines?"
|
||||
result_simple = create_siem_agent(test_query)
|
||||
print(result_simple)
|
||||
@@ -1,2 +0,0 @@
|
||||
You are a CMDB query assistant. You can call appropriate CMDB tools to query based on the user's natural language query request and return the results in JSON
|
||||
format.
|
||||
@@ -1,35 +0,0 @@
|
||||
You are a SOC summarization subgraph. Your task is to read the provided evidence list and produce a summary digest for report generation.
|
||||
|
||||
Hard rules:
|
||||
- Output language: {REPORT_LANGUAGE}.
|
||||
- Use ONLY information present in the evidence list. Do not invent facts.
|
||||
- Every material claim MUST be backed by a cited Evidence ID.
|
||||
- Do not drop information. If you must compress, keep all material facts and preserve all IOCs, assets, identities, alerts, and timestamps.
|
||||
- The evidence list is ordered and uses Evidence IDs (E1, E2, ...). Use these IDs in your output.
|
||||
- Output must be valid JSON only, no surrounding text.
|
||||
|
||||
If the input begins with "PARTIAL_DIGEST: true", produce a partial digest in the same JSON schema, preserving all items from the chunk.
|
||||
|
||||
JSON schema to output:
|
||||
{
|
||||
"evidence_index": [{"id": "E1", "role": "human|ai|tool|system|other", "excerpt": "..."}],
|
||||
"facts": [{"statement": "...", "evidence": ["E1", "E2"]}],
|
||||
"timeline": [{"time": "...", "event": "...", "evidence": ["E1"]}],
|
||||
"observations": [{"category": "Network|Endpoint|Identity|Email|Cloud|Other", "detail": "...", "evidence": ["E1"]}],
|
||||
"entities": {
|
||||
"assets": ["..."],
|
||||
"identities": ["..."],
|
||||
"iocs": ["..."],
|
||||
"alerts": ["..."],
|
||||
"tools_queries": ["..."],
|
||||
"files": ["..."],
|
||||
"processes": ["..."],
|
||||
"urls_domains": ["..."],
|
||||
"emails": ["..."],
|
||||
"ips": ["..."],
|
||||
"hashes": ["..."]
|
||||
},
|
||||
"decisions_actions": [{"action": "...", "evidence": ["E1"]}],
|
||||
"uncertainties": ["..."]
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
You are a SOC digest merger. Your task is to merge multiple JSON summary digests into one comprehensive digest.
|
||||
|
||||
Hard rules:
|
||||
- Output language: {REPORT_LANGUAGE}.
|
||||
- Use ONLY information present in the partial digests. Do not invent facts.
|
||||
- Do not drop information. If duplicates exist, consolidate but keep all unique facts and evidence IDs.
|
||||
- Output must be valid JSON only, no surrounding text.
|
||||
|
||||
Merge logic:
|
||||
- evidence_index: union by id; keep the most informative excerpt.
|
||||
- facts, timeline, observations, decisions_actions: union items; merge if statements are equivalent; combine evidence lists.
|
||||
- entities: union unique items per list.
|
||||
- uncertainties: union unique items.
|
||||
|
||||
Output JSON schema must match the summary digest schema exactly.
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
You are a SOC reporting subgraph. Your task is to read the provided summary digest and generate a cybersecurity analysis
|
||||
report in Markdown.
|
||||
|
||||
Hard rules:
|
||||
|
||||
- Output language: {REPORT_LANGUAGE}. If the user explicitly requests another language in the messages, follow the user
|
||||
request.
|
||||
- Use ONLY information present in the summary digest. Do not invent facts.
|
||||
- Every material claim MUST be backed by a cited Evidence ID.
|
||||
- If evidence is missing, write "Unknown" or "Not provided in messages".
|
||||
- The output must strictly follow the fixed template and headings below.
|
||||
- Evidence IDs must reference the order of the input messages, starting from E1 for the first message.
|
||||
- Provide a concise evidence excerpt in the Evidence Index.
|
||||
- Do not add extra sections or reorder headings.
|
||||
|
||||
Report time: {CURRENT_UTC_TIME}
|
||||
|
||||
# SOC Cybersecurity Analysis Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
- Overall assessment:
|
||||
- Severity:
|
||||
- Rationale (Evidence: )
|
||||
|
||||
## Detection and Alert Context
|
||||
|
||||
- Detection sources:
|
||||
- Rule/signature/alert IDs:
|
||||
- Initial trigger:
|
||||
- Confidence level:
|
||||
|
||||
## Scope and Assumptions
|
||||
|
||||
- Data sources: Summary digest only
|
||||
- Time window inferred:
|
||||
- Environment inferred:
|
||||
- Known limitations:
|
||||
|
||||
## Key Findings (TL;DR)
|
||||
-
|
||||
|
||||
## Timeline (Inferred)
|
||||
|
||||
| Time (UTC if available) | Event | Supporting Evidence IDs |
|
||||
|-------------------------|-------|-------------------------|
|
||||
| | | |
|
||||
|
||||
## Observations and Evidence
|
||||
|
||||
### Network
|
||||
-
|
||||
|
||||
### Endpoint
|
||||
-
|
||||
|
||||
### Identity and Access
|
||||
-
|
||||
|
||||
### Email and Collaboration
|
||||
-
|
||||
|
||||
### Cloud and SaaS
|
||||
-
|
||||
|
||||
### Other
|
||||
-
|
||||
|
||||
## Asset and Identity Context
|
||||
|
||||
- Affected assets (hostnames/IPs/roles):
|
||||
- Affected identities (users/roles/privilege):
|
||||
- Business criticality:
|
||||
|
||||
## Threat Assessment
|
||||
|
||||
- Likely attack path / kill chain:
|
||||
- MITRE ATT&CK mapping (Technique + rationale + Evidence IDs):
|
||||
- Adversary intent or goal (if inferable):
|
||||
|
||||
## Impact and Exposure Assessment
|
||||
|
||||
- Affected assets/users:
|
||||
- Potential impact types:
|
||||
- Exposure window:
|
||||
- Potential blast radius:
|
||||
|
||||
## Containment, Eradication, and Recovery
|
||||
|
||||
### Immediate (0-24h)
|
||||
-
|
||||
|
||||
### Short-term (1-7d)
|
||||
-
|
||||
|
||||
### Long-term Hardening
|
||||
-
|
||||
|
||||
## Validation Steps
|
||||
-
|
||||
|
||||
## Open Questions and Data Requests
|
||||
-
|
||||
|
||||
## Lessons Learned (Evidence-based only)
|
||||
-
|
||||
|
||||
## Appendix
|
||||
|
||||
### Indicators (IOCs)
|
||||
-
|
||||
|
||||
### Evidence Index
|
||||
|
||||
- E1:
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: asp-alert-en
|
||||
description: 'Review ASP alerts, update AI analysis fields, inspect alert discussions, or attach enrichment after analysis.'
|
||||
argument-hint: 'review alert <alert_id> | list alerts [filters] | update alert <alert_id> <fields>'
|
||||
description: 'Review ASP alerts for triage analysis.'
|
||||
argument-hint: 'review alert <alert_id> | list alerts [filters]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
author: Funnywolf
|
||||
@@ -21,17 +21,13 @@ An alert is secondary data in ASP. Each alert belongs to a case, and each alert
|
||||
|
||||
- The user gives an alert ID and wants a quick review, inspection, or summary.
|
||||
- The user wants to find alerts by status, severity, confidence, or correlation UID.
|
||||
- The user wants to inspect discussion context for an alert.
|
||||
- The user wants to update AI analysis fields on an alert.
|
||||
- The user wants to attach enrichment to an alert after analysis.
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Keep the response focused on triage value rather than repeating schema fields.
|
||||
- If the user is working on a specific alert, prefer `list_alerts(alert_id=<id>, limit=1)` because the current MCP surface does not expose a separate `get_alert` tool.
|
||||
- If the user wants to save structured analysis back onto the alert, use the `asp-enrichment-en` skill.
|
||||
|
||||
Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updates; verdict and summary fields belong to case updates.
|
||||
- Alerts are currently read-only. If the user wants to save structured analysis back onto the alert, use the `asp-enrichment-en` skill.
|
||||
|
||||
## Additional Information
|
||||
|
||||
@@ -41,10 +37,8 @@ Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updat
|
||||
## Decision Flow
|
||||
|
||||
1. If the user provides a specific alert ID or says "open", "show", "review", or "summarize" an alert, call `list_alerts(alert_id=<id>, limit=1)`.
|
||||
2. If the user wants discussion context, call `get_alert_discussions(alert_id)` after retrieving the alert.
|
||||
3. If the user wants to browse or compare alerts, use `list_alerts` with supported filters.
|
||||
4. If the user wants to update AI severity, AI confidence, or AI comment, call `update_alert`.
|
||||
5. If the user wants to attach analysis results, intelligence, or structured context to the alert, use the `asp-enrichment-en` skill.
|
||||
2. If the user wants to browse or compare alerts, use `list_alerts` with supported filters.
|
||||
3. If the user wants to attach analysis results, intelligence, or structured context to the alert, use the `asp-enrichment-en` skill.
|
||||
|
||||
## SOP
|
||||
|
||||
@@ -54,15 +48,13 @@ Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updat
|
||||
2. If the user only needs the basic alert information, call `list_alerts(alert_id=<id>, limit=1)`.
|
||||
3. If the result is empty, state that the alert was not found.
|
||||
4. Parse the first JSON record.
|
||||
5. If the user wants analyst discussion context, call `get_alert_discussions(alert_id)`.
|
||||
6. Present only the most useful triage fields.
|
||||
5. Present only the most useful triage fields.
|
||||
|
||||
Preferred response structure:
|
||||
|
||||
- `Alert`: alert ID, title or name, severity, status, confidence, correlation UID.
|
||||
- `Timeline`: created or updated time when present.
|
||||
- `Key Context`: source, rule, category, owner, or other high-signal fields.
|
||||
- `Discussions`: only the most relevant analyst or system notes when needed.
|
||||
- `Assessment`: short triage judgment.
|
||||
|
||||
### List Alerts
|
||||
@@ -80,31 +72,19 @@ Preferred response structure:
|
||||
|
||||
Then add one short explanation line when needed.
|
||||
|
||||
### Update Alert AI Fields
|
||||
|
||||
1. Require `alert_id`.
|
||||
2. Extract only supported AI fields: `severity_ai`, `confidence_ai`, and `comment_ai`.
|
||||
3. Call `update_alert` with only the changed fields.
|
||||
4. If the result is `None`, state that the alert was not found.
|
||||
5. Confirm only the fields that changed.
|
||||
|
||||
## Clarification Rules
|
||||
|
||||
- Ask for `alert_id` only when it is missing for alert-related actions.
|
||||
- Ask for enum clarification only when the requested value does not map cleanly to ASP values.
|
||||
- If the user says "lower confidence", "raise severity", or "leave a note", map it directly to the matching AI field when the intent is clear.
|
||||
|
||||
## Output Rules
|
||||
|
||||
- Be concise.
|
||||
- Do not output raw JSON unless the user explicitly asks for it.
|
||||
- Prefer triage wording over schema wording.
|
||||
- If alert data and discussion context are both used, merge them into one coherent view.
|
||||
- State blockers clearly: alert not found, unsupported filter, invalid enum value, or incomplete follow-up payload.
|
||||
- State blockers clearly: alert not found, unsupported filter, invalid enum value.
|
||||
|
||||
## Failure Handling
|
||||
|
||||
- If the alert does not exist, say so directly.
|
||||
- If filters return no results, say so directly and suggest the most useful refinement.
|
||||
- If the requested update field is unsupported, say which alert fields are writable.
|
||||
- If the enrichment input is incomplete, ask one focused follow-up instead of guessing.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: asp-alert-zh
|
||||
description: '审查 ASP 告警、更新 AI 分析字段、查看告警讨论。'
|
||||
argument-hint: 'review alert <alert_id> | list alerts [filters] | update alert <alert_id> <fields>'
|
||||
description: '查看 ASP 告警并进行分诊分析。'
|
||||
argument-hint: 'review alert <alert_id> | list alerts [filters]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
author: Funnywolf
|
||||
@@ -21,13 +21,13 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个
|
||||
|
||||
- 用户给出一个告警 ID,希望快速查看、审查或总结。
|
||||
- 用户希望按状态、严重级别、置信度或 correlation UID 查找告警。
|
||||
- 用户想查看某条告警的分析讨论内容。
|
||||
- 用户想更新告警上的 AI 分析字段。
|
||||
- 用户想在分析后把 enrichment 附加到告警。
|
||||
|
||||
## 运行规则
|
||||
|
||||
- 回复要聚焦于分诊价值,而不是原样回显 schema 字段。
|
||||
- Alert 当前为只读接口,如需更新分析结果请使用 enrichment。
|
||||
- 如果用户需要保存分析结果或结构化上下文到告警上,使用 `asp-enrichment-zh` skill。
|
||||
|
||||
## 补充信息
|
||||
|
||||
@@ -35,11 +35,9 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个
|
||||
|
||||
## 决策流程
|
||||
|
||||
1. 如果用户提供了具体告警 ID,或要求“open”“show”“review”“summarize”某条告警,调用 `list_alerts(alert_id=<id>, limit=1)`。
|
||||
2. 如果用户要求讨论上下文,在取回告警后调用 `get_alert_discussions(alert_id)`。
|
||||
3. 如果用户要浏览或对比多条告警,使用带支持过滤条件的 `list_alerts`。
|
||||
4. 如果用户要更新 AI severity、AI confidence 或 AI comment,调用 `update_alert`。
|
||||
5. 如果用户要附加分析结果、情报或结构化上下文,使用 `asp-enrichment-zh` skill。
|
||||
1. 如果用户提供了具体告警 ID,或要求"open""show""review""summarize"某条告警,调用 `list_alerts(alert_id=<id>, limit=1)`。
|
||||
2. 如果用户要浏览或对比多条告警,使用带支持过滤条件的 `list_alerts`。
|
||||
3. 如果用户要附加分析结果、情报或结构化上下文,使用 `asp-enrichment-zh` skill。
|
||||
|
||||
## SOP
|
||||
|
||||
@@ -49,15 +47,13 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个
|
||||
2. 如果只需要快速查看告警基本信息,调用 `list_alerts(alert_id=<id>, limit=1)` 即可。
|
||||
3. 如果结果为空,直接说明找不到该告警。
|
||||
4. 解析第一条 JSON 记录。
|
||||
5. 如果用户要求分析讨论上下文,调用 `get_alert_discussions(alert_id)`。
|
||||
6. 只呈现最有价值的分诊字段。
|
||||
5. 只呈现最有价值的分诊字段。
|
||||
|
||||
首选回复结构:
|
||||
|
||||
- `Alert`:alert ID、标题或名称、严重级别、状态、置信度、correlation UID。
|
||||
- `Timeline`:存在时给出创建或更新时间。
|
||||
- `Key Context`:来源、规则、类别、负责人或其他高信号字段。
|
||||
- `Discussions`:只在需要时给出最相关的分析或系统备注。
|
||||
- `Assessment`:简短分诊判断。
|
||||
|
||||
### 列出告警
|
||||
@@ -75,30 +71,19 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个
|
||||
|
||||
然后在需要时补一句简短解释。
|
||||
|
||||
### 更新告警 AI 字段
|
||||
|
||||
1. 要求提供 `alert_id`。
|
||||
2. 只提取支持的 AI 字段:`severity_ai`、`confidence_ai`、`comment_ai`。
|
||||
3. 仅带变更字段调用 `update_alert`。
|
||||
4. 如果结果为 `None`,说明找不到该告警。
|
||||
5. 只确认实际修改的字段。
|
||||
|
||||
## 澄清规则
|
||||
|
||||
- 只有在缺少告警相关操作所需参数时才询问 `alert_id`。
|
||||
- 只有当请求值不能清晰映射到 ASP 枚举时,才要求用户澄清枚举值。
|
||||
- 如果用户说“降低 confidence”“提高 severity”或“留个备注”,在意图明确时直接映射到对应 AI 字段。
|
||||
|
||||
## 输出规则
|
||||
|
||||
- 保持简洁。
|
||||
- 除非用户明确要求,否则不要输出原始 JSON。
|
||||
- 优先使用分诊语义,而不是 schema 语义。
|
||||
- 如果同时用了告警数据和讨论内容,要合并成一个连贯视图。
|
||||
- 清楚指出阻塞项:告警不存在、不支持的过滤条件、无效枚举值,或不完整的追加载荷。
|
||||
- 清楚指出阻塞项:告警不存在、不支持的过滤条件、无效枚举值。
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 如果告警不存在,直接说明。
|
||||
- 如果过滤无结果,直接说明并建议最有用的收敛方式。
|
||||
- 如果请求更新的字段不受支持,明确指出哪些告警字段是可写的。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: asp-artifact-en
|
||||
description: 'Find artifacts by IOC and attach enrichment to artifacts.'
|
||||
description: 'Find artifacts by IOC.'
|
||||
argument-hint: 'review artifact <artifact_id> | list artifacts [filters]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
@@ -19,15 +19,13 @@ Artifacts are created automatically by system processes. You can list and analyz
|
||||
|
||||
## When to Use
|
||||
|
||||
- The user wants to find artifacts by value, type, role, owner, or reputation.
|
||||
- The user wants to attach enrichment or structured analysis to an artifact.
|
||||
- The user wants to find artifacts by value, type, role, or owner.
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Treat artifacts as the smallest investigation object on the platform.
|
||||
- Use `list_artifacts` for lookup and review.
|
||||
- If the user wants to save analysis on the artifact itself, use `create_enrichment` plus `attach_enrichment_to_target`.
|
||||
- For the full enrichment persistence workflow, use the `asp-enrichment-en` skill.
|
||||
- If the user wants to save analysis on the artifact, use the `asp-enrichment-en` skill.
|
||||
|
||||
## Additional Information
|
||||
|
||||
@@ -51,8 +49,8 @@ Artifacts are created automatically by system processes. You can list and analyz
|
||||
|
||||
Preferred response structure:
|
||||
|
||||
| Artifact ID | Value | Type | Role | Owner | Reputation | Summary |
|
||||
|-------------|-------|------|------|-------|------------|---------|
|
||||
| Artifact ID | Value | Type | Role | Owner | Summary |
|
||||
|-------------|-------|------|------|-------|---------|
|
||||
|
||||
Then add one short explanation line when needed.
|
||||
|
||||
|
||||
@@ -19,15 +19,13 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact,
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 用户想按 value、type、role、owner 或 reputation 查找 artifact。
|
||||
- 用户想给 artifact 附加 enrichment 或结构化分析。
|
||||
- 用户想按 value、type、role、owner 查找 artifact。
|
||||
|
||||
## 运行规则
|
||||
|
||||
- 把 artifact 视为平台里的最小调查对象。
|
||||
- 查询和审查时使用 `list_artifacts`。
|
||||
- 如果用户想把分析结果保存到 artifact 本身,使用 `create_enrichment` 加 `attach_enrichment_to_target`。
|
||||
- 如需完整的 enrichment 持久化流程,使用 `asp-enrichment-zh` skill。
|
||||
- 如需保存分析结果到 artifact,使用 `asp-enrichment-zh` skill。
|
||||
|
||||
## 补充信息
|
||||
|
||||
@@ -50,8 +48,8 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact,
|
||||
|
||||
首选回复结构:
|
||||
|
||||
| Artifact ID | Value | Type | Role | Owner | Reputation | Summary |
|
||||
|-------------|-------|------|------|-------|------------|---------|
|
||||
| Artifact ID | Value | Type | Role | Owner | Summary |
|
||||
|-------------|-------|------|------|-------|---------|
|
||||
|
||||
然后在需要时补一句简短解释。
|
||||
|
||||
@@ -70,4 +68,3 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact,
|
||||
|
||||
- 如果没有匹配的 artifact,直接说明,并建议最有用的收敛方式。
|
||||
- 如果目标 artifact 不存在,直接说明。
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: asp-enrichment-en
|
||||
description: 'Save structured data as enrichment and attach it to a case, alert, or artifact.'
|
||||
argument-hint: 'create enrichment for <case|alert|artifact> <target_id> | attach enrichment to <case|alert|artifact> <target_id>'
|
||||
argument-hint: 'create enrichment <target_id> [fields]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
author: Funnywolf
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
mcp-server: asp
|
||||
category: cyber security
|
||||
tags: [ enrichment, analysis, context, investigation ]
|
||||
@@ -21,55 +21,37 @@ Use this skill when analysis results need to be saved back into ASP as structure
|
||||
- The user wants to save structured analysis, intelligence, or investigation conclusions.
|
||||
- The user wants to attach context to a case, alert, or artifact.
|
||||
- The user wants to persist SIEM findings, threat intel, asset context, or analyst conclusions.
|
||||
- The user already has an enrichment and wants to reuse it on a target object.
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Treat enrichment as the platform's structured result layer, not as a generic comment field.
|
||||
- When the goal is to persist analysis on a `case`, `alert`, or `artifact`, use this skill.
|
||||
- Separate creation from attachment.
|
||||
- Use `create_enrichment` for a new result record.
|
||||
- Use `attach_enrichment_to_target` only after you have the enrichment row_id.
|
||||
- Use `create_enrichment` to create the enrichment record and attach it to the target in one step.
|
||||
- Keep the payload compact and actionable.
|
||||
- Use the object-specific skill first when the user is still inspecting the object, and use this skill when saving the result.
|
||||
|
||||
## Additional Information
|
||||
|
||||
- `row_id` is the UUID for each enrichment record and is used for data association.
|
||||
- `enrichment_id` is the human-readable unique ID for each enrichment record.
|
||||
|
||||
## Decision Flow
|
||||
|
||||
1. If the user wants to save a new structured result, call `create_enrichment` first.
|
||||
2. If the user wants to attach the result to a case, alert, or artifact, call `attach_enrichment_to_target`.
|
||||
3. If the user already has an enrichment row_id, skip creation and attach it directly.
|
||||
4. If the user is still exploring the object rather than saving a result, use the corresponding object skill first.
|
||||
1. If the user wants to save a new structured result on a target object, call `create_enrichment(target_id=..., ...)`.
|
||||
2. If the user is still exploring the object rather than saving a result, use the corresponding object skill first.
|
||||
|
||||
## SOP
|
||||
|
||||
### Create And Attach New Enrichment
|
||||
### Create Enrichment
|
||||
|
||||
1. Require `target_id` such as `case_000001`, `alert_000001`, or `artifact_000001`.
|
||||
2. Convert the user's analysis into a compact structured enrichment payload.
|
||||
3. Call `create_enrichment` and keep the returned enrichment row_id.
|
||||
4. Call `attach_enrichment_to_target(target_id=<target_id>, enrichment_row_id=<created_row_id>)`.
|
||||
5. Confirm that the enrichment was created and attached successfully.
|
||||
3. Call `create_enrichment(target_id=<target_id>, name=..., type=..., ...)`.
|
||||
4. Confirm the created enrichment row_id and that it is attached to the target.
|
||||
|
||||
Preferred response structure:
|
||||
|
||||
- `Target ID`: target ID
|
||||
- `Enrichment`: created enrichment row_id
|
||||
|
||||
### Attach Existing Enrichment
|
||||
|
||||
1. Require `target_id` and `enrichment_row_id`.
|
||||
2. Call `attach_enrichment_to_target(target_id=<target_id>, enrichment_row_id=<enrichment_row_id>)`.
|
||||
3. Confirm that the enrichment was attached successfully.
|
||||
|
||||
## Clarification Rules
|
||||
|
||||
- Ask for `target_id` only when it is missing.
|
||||
- Ask for the enrichment row_id only when the user wants to reuse an existing enrichment and did not provide it.
|
||||
- If the user only says "save this result", infer the most obvious target object from the current request when it is clear, and prefer Case.
|
||||
|
||||
## Output Rules
|
||||
@@ -83,4 +65,3 @@ Preferred response structure:
|
||||
|
||||
- If the target object does not exist, say so directly.
|
||||
- If the enrichment payload is incomplete, ask one focused follow-up instead of guessing.
|
||||
- If attachment fails because the enrichment row_id is missing, ask for it or create a new enrichment first.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: asp-enrichment-zh
|
||||
description: '把结构化数据保存为 enrichment,并附加到 case、alert 或 artifact。'
|
||||
argument-hint: 'create enrichment for <case|alert|artifact> <target_id> | attach enrichment to <case|alert|artifact> <target_id>'
|
||||
argument-hint: 'create enrichment <target_id> [fields]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
author: Funnywolf
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
mcp-server: asp
|
||||
category: cyber security
|
||||
tags: [ enrichment, analysis, context, investigation ]
|
||||
@@ -14,64 +14,47 @@ metadata:
|
||||
|
||||
# ASP Enrichment
|
||||
|
||||
当数据需要以结构化上下文形式保存回 ASP 且挂载到对应 case , alert 或 artifact 时,使用这个 skill。
|
||||
当数据需要以结构化上下文形式保存回 ASP 且挂载到对应 case、alert 或 artifact 时,使用这个 skill。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 用户想保存结构化分析、情报或调查结论。
|
||||
- 用户想把上下文附加到 case、alert 或 artifact。
|
||||
- 用户想持久化 SIEM 发现、威胁情报、资产上下文或分析师结论。
|
||||
- 用户已经有 enrichment,希望把它复用并附加到目标对象。
|
||||
|
||||
## 运行规则
|
||||
|
||||
- 把 enrichment 视为平台的结构化结果层,而不是普通评论字段。
|
||||
- 当目标是把分析结果持久化到 `case`、`alert` 或 `artifact` 上时,使用这个 skill。
|
||||
- 区分“创建 enrichment”和“附加 enrichment”两个动作。
|
||||
- 新结果记录使用 `create_enrichment`。
|
||||
- 只有在已经拿到 enrichment row_id 后,才使用 `attach_enrichment_to_target`。
|
||||
- 使用 `create_enrichment` 创建 enrichment 记录并自动附加到目标对象。
|
||||
- enrichment payload 保持紧凑且可操作。
|
||||
- 查看对象本身时优先使用对象对应的 skill;保存结果时再使用本 skill。
|
||||
|
||||
## 补充信息
|
||||
|
||||
- row_id 为每条 enrichment 记录的UUID,用于数据关联. enrichment_id 是每条 enrichment 记录人类可读的唯一ID
|
||||
|
||||
## 决策流程
|
||||
|
||||
1. 如果用户想保存新的结构化结果,先调用 `create_enrichment`。
|
||||
2. 如果用户想把结果附加到 case、alert 或 artifact,调用 `attach_enrichment_to_target`。
|
||||
3. 如果用户已经有现成的 enrichment row_id,跳过创建,直接附加。
|
||||
4. 如果用户还处于对象探索阶段而不是保存结果,先使用对应对象 skill。
|
||||
1. 如果用户想在目标对象上保存新的结构化结果,调用 `create_enrichment(target_id=..., ...)`。
|
||||
2. 如果用户还处于对象探索阶段而不是保存结果,先使用对应对象 skill。
|
||||
|
||||
当你已经有明确的分析结论,例如 verdict、TTP 集合、风险评级或缓解建议,并且这些内容需要保存在目标对象上时,就切换到这个 skill。
|
||||
|
||||
## SOP
|
||||
|
||||
### 创建并附加新的 Enrichment
|
||||
### 创建 Enrichment
|
||||
|
||||
1. 要求提供`target_id` (比如 case_000001 / alert_000001 / artifact_000001)。
|
||||
1. 要求提供 `target_id`(如 case_000001 / alert_000001 / artifact_000001)。
|
||||
2. 把用户的分析整理成紧凑的结构化 enrichment payload。
|
||||
3. 调用 `create_enrichment` 并保留返回的 enrichment row_id。
|
||||
4. 调用`attach_enrichment_to_target(target_id=<target_id>, enrichment_row_id=<created_row_id>)`。
|
||||
5. 确认 enrichment 已创建并附加成功。
|
||||
3. 调用 `create_enrichment(target_id=<target_id>, name=..., type=..., ...)`。
|
||||
4. 确认创建后的 enrichment row_id 及其已附加到目标对象。
|
||||
|
||||
首选回复结构:
|
||||
|
||||
- `Target ID`:目标 ID
|
||||
- `Enrichment`:创建出的 enrichment row_id
|
||||
|
||||
### 附加已有 Enrichment
|
||||
|
||||
1. 要求提供 `target_id` 和 `enrichment_row_id`。
|
||||
2. 调用`attach_enrichment_to_target(target_id=<target_id>, enrichment_row_id=<enrichment_row_id>)`。
|
||||
3. 确认 enrichment 已附加成功。
|
||||
|
||||
## 澄清规则
|
||||
|
||||
- 只有在缺失时才询问 `target_id`。
|
||||
- 只有当用户要复用现有 enrichment 且未提供时,才询问 enrichment row_id。
|
||||
- 如果用户只说“把这个结果保存一下”,在上下文明确时推断最明显的目标对象,优先选择 Case。
|
||||
- 如果用户只说”把这个结果保存一下”,在上下文明确时推断最明显的目标对象,优先选择 Case。
|
||||
|
||||
## 输出规则
|
||||
|
||||
@@ -84,4 +67,3 @@ metadata:
|
||||
|
||||
- 如果目标对象不存在,直接说明。
|
||||
- 如果 enrichment payload 不完整,只问一个聚焦问题,不要猜测。
|
||||
- 如果附加失败是因为缺少 enrichment row_id,就要求用户提供,或先创建新的 enrichment。
|
||||
|
||||
+12
-30
@@ -193,6 +193,8 @@ def list_artifacts(
|
||||
|
||||
# Enrichment
|
||||
def create_enrichment(
|
||||
target_id: Annotated[str, Field(
|
||||
description="Target object ID to attach the enrichment to; must start with case_, alert_, or artifact_ (挂载富化的目标对象 ID,须以 case_、alert_ 或 artifact_ 开头)")],
|
||||
name: Annotated[str, Field(description="Enrichment name (富化名称)")] = "",
|
||||
type: Annotated[EnrichmentType, Field(description="Enrichment type (富化类型)")] = EnrichmentType.OTHER,
|
||||
provider: Annotated[EnrichmentProvider, Field(description="Enrichment provider (富化提供商)")] = EnrichmentProvider.OTHER,
|
||||
@@ -201,7 +203,7 @@ def create_enrichment(
|
||||
desc: Annotated[str, Field(description="Enrichment summary (富化摘要)")] = "",
|
||||
data: Annotated[str, Field(description="Detailed enrichment JSON string (详细富化 JSON 字符串)")] = ""
|
||||
) -> Annotated[str, Field(description="Created enrichment record row ID (创建的 Enrichment 行 ID)")]:
|
||||
"""Create one enrichment record. (创建一条富化记录)"""
|
||||
"""Create one enrichment record and attach it to a target case, alert, or artifact. (创建一条富化记录并挂载到目标 Case、Alert 或 Artifact)"""
|
||||
model = EnrichmentModel()
|
||||
model.name = name
|
||||
model.type = type
|
||||
@@ -210,38 +212,19 @@ def create_enrichment(
|
||||
model.src_url = src_url
|
||||
model.desc = desc
|
||||
model.data = data
|
||||
return Enrichment.create(model)
|
||||
enrichment_row_id = Enrichment.create(model)
|
||||
|
||||
|
||||
def attach_enrichment_to_target(
|
||||
target_id: Annotated[str, Field(
|
||||
description="Target object ID to receive the enrichment; must start with case_, alert_, or artifact_ (接收富化的目标对象 ID,须以 case_、alert_ 或 artifact_ 开头)")],
|
||||
enrichment_row_id: Annotated[
|
||||
str, Field(description="Enrichment record row ID returned by create_enrichment (由 create_enrichment 返回的 Enrichment 行 ID)")]
|
||||
) -> Annotated[
|
||||
Optional[str], Field(description="Attached enrichment record row ID, or None if target not found (挂载后的 Enrichment 行 ID,目标不存在时返回 None)")]:
|
||||
"""Attach one existing enrichment record to an existing case, alert, or artifact. (将已有富化记录挂载到 Case、Alert 或 Artifact)"""
|
||||
normalized_target_id = target_id.strip().lower()
|
||||
|
||||
if normalized_target_id.startswith("case_"):
|
||||
return Case.attach_enrichment(
|
||||
case_id=target_id,
|
||||
enrichment_row_id=enrichment_row_id
|
||||
)
|
||||
Case.attach_enrichment(case_id=target_id, enrichment_row_id=enrichment_row_id)
|
||||
elif normalized_target_id.startswith("alert_"):
|
||||
Alert.attach_enrichment(alert_id=target_id, enrichment_row_id=enrichment_row_id)
|
||||
elif normalized_target_id.startswith("artifact_"):
|
||||
Artifact.attach_enrichment(artifact_id=target_id, enrichment_row_id=enrichment_row_id)
|
||||
else:
|
||||
raise ValueError("target_id must start with one of: case_, alert_, artifact_")
|
||||
|
||||
if normalized_target_id.startswith("alert_"):
|
||||
return Alert.attach_enrichment(
|
||||
alert_id=target_id,
|
||||
enrichment_row_id=enrichment_row_id
|
||||
)
|
||||
|
||||
if normalized_target_id.startswith("artifact_"):
|
||||
return Artifact.attach_enrichment(
|
||||
artifact_id=target_id,
|
||||
enrichment_row_id=enrichment_row_id
|
||||
)
|
||||
|
||||
raise ValueError("target_id must start with one of: case_, alert_, artifact_")
|
||||
return enrichment_row_id
|
||||
|
||||
|
||||
# Ticket
|
||||
@@ -475,7 +458,6 @@ REGISTERED_MCP_TOOLS = [
|
||||
|
||||
# enrichment
|
||||
create_enrichment,
|
||||
attach_enrichment_to_target,
|
||||
|
||||
# playbook
|
||||
list_available_playbook_definitions,
|
||||
|
||||
Reference in New Issue
Block a user