Skip to main content
Glama
dstreefkerk

ms-sentinel-mcp-server

by dstreefkerk

sentinel_analytics_rules_count_by_tactic

Count Microsoft Sentinel analytics rules by MITRE ATT&CK tactic to assess security coverage and identify gaps in threat detection.

Instructions

Count Sentinel analytics rules by tactic.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
kwargsYes

Implementation Reference

  • The MCPToolBase subclass that implements the core logic of the tool. The `run` method fetches all Sentinel analytics rules, extracts MITRE tactics from each rule's tags, groups and counts rules by tactic (lowercased), and returns a structured dictionary with counts and rule lists per tactic.
    class SentinelAnalyticsRulesCountByTacticTool(MCPToolBase):
        """
        Count Sentinel analytics rules by tactic.
        Extracts tactics from each rule and returns a mapping of tactic to count and
        rule summaries.
        """
    
        name = "sentinel_analytics_rules_count_by_tactic"
        description = "Count Sentinel analytics rules by tactic."
    
        async def run(self, ctx: Context, **kwargs) -> Dict:
            """
            Count analytics rules by tactic.
            Returns a dict: {tactic: {count: int, rules: [{id, display_name}]}}
            """
            logger = self.logger
            workspace, resource_group, subscription_id = self.get_azure_context(ctx)
            if not (workspace and resource_group and subscription_id):
                logger.error("Missing Azure Sentinel context for analytics rule listing.")
                return {"error": "Missing Azure Sentinel context."}
            client = self.get_securityinsight_client(subscription_id)
            tactic_map = {}
            try:
                rules = client.alert_rules.list(
                    resource_group_name=resource_group,
                    workspace_name=workspace,
                )
                for rule in rules:
                    rule_dict = rule.as_dict() if hasattr(rule, "as_dict") else dict(rule)
                    # pylint: disable=unused-variable
                    tags, tactics, _ = extract_tags_tactics_techniques_from_dict(rule_dict)
                    display_name = (
                        rule_dict.get("display_name")
                        or rule_dict.get("displayName")
                        or rule_dict.get("name")
                    )
                    for tactic in tactics or ["Unknown"]:
                        tkey = tactic.lower() or "unknown"
                        if tkey not in tactic_map:
                            tactic_map[tkey] = {"count": 0, "rules": []}
                        tactic_map[tkey]["count"] += 1
                        tactic_map[tkey]["rules"].append(
                            {
                                "id": rule_dict.get("id"),
                                "display_name": display_name,
                            }
                        )
                return {
                    "valid": True,
                    "error": None,
                    "results": tactic_map,
                    "errors": [],
                }
            except Exception as e:
                logger.error("Error in %s: %s", self.__class__.__name__, str(e))
                return {
                    "valid": False,
                    "error": str(e),
                    "results": None,
                    "errors": [str(e)],
                }
  • The call to register this tool class with the MCP server instance.
    SentinelAnalyticsRulesCountByTacticTool.register(mcp)
  • Helper utility function to parse tags, tactics, and techniques from Sentinel analytics rule or template dictionaries. Called within the handler to extract tactics for grouping.
    def extract_tags_tactics_techniques_from_dict(obj):
        """
          Extract tags, tactics, and techniques from an analytics rule/template dict.
    
        Args:
            obj (dict): Analytics rule or template dictionary.
    
        Returns:
            tuple: (tags, tactics, techniques)
                tags (list[dict]): All tags as {name, value} pairs.
                tactics (list[str]): List of tactics (from tags or legacy fields).
                techniques (list[str]): List of techniques (from tags or legacy fields).
    
        Extraction precedence:
            - Tactics/techniques: Prefer tags with name 'tactics'/'techniques'
              (case-insensitive, split on comma). Fallback to legacy fields.
            - Tags: All tags as {name, value} pairs (robust to SDK object, dict, or string).
        """
        tags = []
        tactics = []
        techniques = []
        raw_tags = obj.get("tags")
        if raw_tags:
            for tag in raw_tags:
                tag_name = None
                tag_value = None
                if isinstance(tag, dict):
                    tag_name = tag.get("name") or tag.get("Name")
                    tag_value = tag.get("value") or tag.get("Value")
                elif hasattr(tag, "name") and hasattr(tag, "value"):
                    tag_name = getattr(tag, "name", None)
                    tag_value = getattr(tag, "value", None)
                elif isinstance(tag, str):
                    tag_name = tag
                    tag_value = None
                else:
                    try:
                        tag_name = str(tag)
                    except Exception:
                        continue
                if tag_name is not None:
                    tags.append({"name": tag_name, "value": tag_value})
        for tag in tags:
            if tag["name"] and isinstance(tag["name"], str):
                if tag["name"].lower() == "tactics" and tag["value"]:
                    tactics += [t.strip() for t in tag["value"].split(",") if t.strip()]
                elif tag["name"].lower() == "techniques" and tag["value"]:
                    techniques += [t.strip() for t in tag["value"].split(",") if t.strip()]
        legacy_tactics = obj.get("tactics")
        if legacy_tactics:
            tactics += [
                t.strip() for t in legacy_tactics if isinstance(t, str) and t.strip()
            ]
        legacy_techniques = obj.get("techniques")
        if legacy_techniques:
            techniques += [
                t.strip() for t in legacy_techniques if isinstance(t, str) and t.strip()
            ]
        tactics = list(dict.fromkeys([t for t in tactics if t]))
        techniques = list(dict.fromkeys([t for t in techniques if t]))
        return tags, tactics, techniques

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dstreefkerk/ms-sentinel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server