Skip to main content
Glama
dstreefkerk

ms-sentinel-mcp-server

by dstreefkerk

sentinel_analytics_rule_templates_count_by_tactic

Count Microsoft Sentinel analytics rule templates by MITRE ATT&CK tactic to identify coverage gaps and prioritize security monitoring.

Instructions

Count Sentinel analytics rule templates by tactic.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
kwargsYes

Implementation Reference

  • MCPToolBase subclass that defines the tool name and implements the core logic in the `run` method: fetches Sentinel analytics rule templates, extracts tactics using the helper function, groups and counts them by tactic, returning a structured result dictionary.
    class SentinelAnalyticsRuleTemplatesCountByTacticTool(MCPToolBase):
        """
        Count Sentinel analytics rule templates by tactic.
        Extracts tactics from each template and returns a mapping of tactic to count
        and template summaries.
        """
    
        name = "sentinel_analytics_rule_templates_count_by_tactic"
        description = "Count Sentinel analytics rule templates by tactic."
    
        async def run(self, ctx: Context, **kwargs) -> Dict:
            """
            Count analytics rule templates by tactic.
            Returns a dict: {tactic: {count: int, templates: [{id, display_name}]}}
            """
            logger = self.logger
            # Extract Azure context
            workspace, resource_group, subscription_id = self.get_azure_context(ctx)
            client = self.get_securityinsight_client(subscription_id)
            tactic_map = {}
            try:
                templates = client.alert_rule_templates.list(resource_group, workspace)
                for template in templates:
                    template_dict = (
                        template.as_dict()
                        if hasattr(template, "as_dict")
                        else dict(template)
                    )
                    # pylint: disable=unused-variable
                    tags, tactics, _ = extract_tags_tactics_techniques_from_dict(
                        template_dict
                    )
                    display_name = (
                        template_dict.get("display_name")
                        or template_dict.get("displayName")
                        or template_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, "templates": []}
                        tactic_map[tkey]["count"] += 1
                        tactic_map[tkey]["templates"].append(
                            {
                                "id": template_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)],
                }
  • Registers the SentinelAnalyticsRuleTemplatesCountByTacticTool class with the MCP server instance inside the `register_tools` function.
    SentinelAnalyticsRuleTemplatesCountByTacticTool.register(mcp)
  • Helper utility function called within the tool handler to parse tags, tactics, and techniques from rule template dictionaries, enabling the tactic-based grouping and counting.
    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
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It only states the purpose without any information on permissions required, rate limits, side effects, or output format. For a tool with no annotation coverage, this is inadequate as it fails to describe how the tool behaves beyond its basic function.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with no wasted words, making it easy to parse. It is appropriately sized for the tool's apparent simplicity, though this conciseness comes at the cost of missing important details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a counting operation with one parameter and no output schema or annotations, the description is incomplete. It lacks details on parameter usage, behavioral traits, and expected results, making it insufficient for an AI agent to invoke the tool correctly without additional context or trial-and-error.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has one parameter ('kwargs') with 0% description coverage, and the tool description does not mention parameters at all. This leaves the parameter undocumented in both schema and description, failing to compensate for the low schema coverage. The agent has no guidance on what 'kwargs' should contain or how to use it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Count') and the resource ('Sentinel analytics rule templates by tactic'), making the purpose evident. However, it does not differentiate from sibling tools like 'sentinel_analytics_rule_templates_count_by_technique' or 'sentinel_analytics_rules_count_by_tactic', which have similar naming patterns but target different scopes (e.g., techniques vs. rules vs. templates).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, such as 'sentinel_analytics_rule_templates_list' for listing templates or 'sentinel_analytics_rule_templates_count_by_technique' for counting by technique. There is no mention of prerequisites, context, or exclusions, leaving usage ambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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