Skip to main content
Glama
panther-labs

Panther MCP Server

Official

bulk_update_alerts

Update multiple alerts simultaneously by modifying status, assignee, or adding comments to streamline security incident management workflows.

Instructions

Bulk update multiple alerts with status, assignee, and/or comment changes.

This tool allows you to efficiently update multiple alerts at once by setting their status, assignee, and adding a comment. At least one of status, assignee_id, or comment must be provided.

Returns: Dict containing: - success: Boolean indicating overall success - results: Dict with operation results: - status_updates: List of alert IDs successfully updated with new status - assignee_updates: List of alert IDs successfully updated with new assignee - comments_added: List of alert IDs that successfully received comments - failed_operations: List of failed operations with error details - summary: Dict with counts of successful and failed operations - message: Error message if unsuccessful

Permissions:{'all_of': ['Manage Alerts']}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
alert_idsYesList of alert IDs to update (maximum 25)
statusNoOptional new status for the alerts
assignee_idNoOptional ID of the user to assign the alerts to
commentNoOptional comment to add to all alerts

Implementation Reference

  • Primary handler implementation for the 'bulk_update_alerts' MCP tool. Decorated with @mcp_tool, defines input schema via Annotated/Pydantic Fields and validators, implements bulk update logic using REST client for status/assignee PATCH operations and individual comment POSTs, handles partial failures, returns structured results and summary.
        }
    )
    async def bulk_update_alerts(
        alert_ids: Annotated[
            list[str],
            Field(description="List of alert IDs to update (maximum 25)"),
        ],
        status: Annotated[
            str | None,
            BeforeValidator(_validate_alert_status),
            Field(
                description="Optional new status for the alerts",
                examples=["OPEN", "TRIAGED", "RESOLVED", "CLOSED"],
            ),
        ] = None,
        assignee_id: Annotated[
            str | None,
            Field(
                min_length=1,
                description="Optional ID of the user to assign the alerts to",
            ),
        ] = None,
        comment: Annotated[
            str | None,
            Field(
                min_length=1,
                description="Optional comment to add to all alerts",
            ),
        ] = None,
    ) -> dict[str, Any]:
        """Bulk update multiple alerts with status, assignee, and/or comment changes.
    
        This tool allows you to efficiently update multiple alerts at once by setting their status,
        assignee, and adding a comment. At least one of status, assignee_id, or comment must be provided.
    
        Returns:
            Dict containing:
            - success: Boolean indicating overall success
            - results: Dict with operation results:
                - status_updates: List of alert IDs successfully updated with new status
                - assignee_updates: List of alert IDs successfully updated with new assignee
                - comments_added: List of alert IDs that successfully received comments
                - failed_operations: List of failed operations with error details
            - summary: Dict with counts of successful and failed operations
            - message: Error message if unsuccessful
        """
        logger.info(f"Bulk updating {len(alert_ids)} alerts")
    
        if not alert_ids:
            return {
                "success": False,
                "message": "At least one alert ID must be provided",
            }
    
        if len(alert_ids) > 25:
            return {
                "success": False,
                "message": "Cannot bulk update more than 25 alerts at once",
            }
    
        if not any([status, assignee_id, comment]):
            return {
                "success": False,
                "message": "At least one of status, assignee_id, or comment must be provided",
            }
    
        try:
            results = {
                "status_updates": [],
                "assignee_updates": [],
                "comments_added": [],
                "failed_operations": [],
            }
    
            async with get_rest_client() as client:
                # Update status if provided
                if status:
                    try:
                        logger.info(
                            f"Updating status for {len(alert_ids)} alerts to {status}"
                        )
                        body = {"ids": alert_ids, "status": status}
                        _, status_code = await client.patch(
                            "/alerts", json_data=body, expected_codes=[204, 400, 404]
                        )
    
                        if status_code == 204:
                            results["status_updates"] = alert_ids.copy()
                            logger.info(
                                f"Successfully updated status for {len(alert_ids)} alerts"
                            )
                        else:
                            results["failed_operations"].append(
                                {
                                    "operation": "status_update",
                                    "alert_ids": alert_ids,
                                    "error": f"HTTP {status_code} - Failed to update status",
                                }
                            )
                            logger.error(f"Failed to update status: HTTP {status_code}")
    
                    except Exception as e:
                        results["failed_operations"].append(
                            {
                                "operation": "status_update",
                                "alert_ids": alert_ids,
                                "error": str(e),
                            }
                        )
                        logger.error(f"Exception updating status: {str(e)}")
    
                # Update assignee if provided
                if assignee_id:
                    try:
                        logger.info(
                            f"Updating assignee for {len(alert_ids)} alerts to {assignee_id}"
                        )
                        body = {"ids": alert_ids, "assignee": assignee_id}
                        _, status_code = await client.patch(
                            "/alerts", json_data=body, expected_codes=[204, 400, 404]
                        )
    
                        if status_code == 204:
                            results["assignee_updates"] = alert_ids.copy()
                            logger.info(
                                f"Successfully updated assignee for {len(alert_ids)} alerts"
                            )
                        else:
                            results["failed_operations"].append(
                                {
                                    "operation": "assignee_update",
                                    "alert_ids": alert_ids,
                                    "error": f"HTTP {status_code} - Failed to update assignee",
                                }
                            )
                            logger.error(f"Failed to update assignee: HTTP {status_code}")
    
                    except Exception as e:
                        results["failed_operations"].append(
                            {
                                "operation": "assignee_update",
                                "alert_ids": alert_ids,
                                "error": str(e),
                            }
                        )
                        logger.error(f"Exception updating assignee: {str(e)}")
    
                # Add comment if provided
                if comment:
                    successful_comments = []
                    for alert_id in alert_ids:
                        try:
                            logger.debug(f"Adding comment to alert {alert_id}")
                            body = {
                                "alertId": alert_id,
                                "body": comment,
                                "format": "PLAIN_TEXT",
                            }
                            _, status_code = await client.post(
                                "/alert-comments",
                                json_data=body,
                                expected_codes=[200, 400, 404],
                            )
    
                            if status_code == 200:
                                successful_comments.append(alert_id)
                            else:
                                results["failed_operations"].append(
                                    {
                                        "operation": "add_comment",
                                        "alert_ids": [alert_id],
                                        "error": f"HTTP {status_code} - Failed to add comment",
                                    }
                                )
                                logger.error(
                                    f"Failed to add comment to {alert_id}: HTTP {status_code}"
                                )
    
                        except Exception as e:
                            results["failed_operations"].append(
                                {
                                    "operation": "add_comment",
                                    "alert_ids": [alert_id],
                                    "error": str(e),
                                }
                            )
                            logger.error(
                                f"Exception adding comment to {alert_id}: {str(e)}"
                            )
    
                    results["comments_added"] = successful_comments
                    logger.info(
                        f"Successfully added comments to {len(successful_comments)} alerts"
                    )
    
            # Calculate summary statistics
            total_operations = (
                len(results["status_updates"])
                + len(results["assignee_updates"])
                + len(results["comments_added"])
            )
            total_failed = len(results["failed_operations"])
    
            summary = {
                "total_alerts": len(alert_ids),
                "successful_operations": total_operations,
                "failed_operations": total_failed,
                "status_updates_count": len(results["status_updates"]),
                "assignee_updates_count": len(results["assignee_updates"]),
                "comments_added_count": len(results["comments_added"]),
            }
    
            logger.info(
                f"Bulk update completed: {total_operations} successful, {total_failed} failed"
            )
    
            return {
                "success": True,
                "results": results,
                "summary": summary,
            }
    
        except Exception as e:
            logger.error(f"Failed to bulk update alerts: {str(e)}")
            return {
                "success": False,
                "message": f"Failed to bulk update alerts: {str(e)}",
            }
  • Global registration point where register_all_tools is called on the MCP server instance, automatically registering all @mcp_tool-decorated functions including bulk_update_alerts via the tool registry.
    register_all_tools(mcp)
  • The register_all_tools function that iterates over all functions decorated with @mcp_tool (including bulk_update_alerts), extracts metadata, and registers them with the FastMCP instance using mcp.tool().
    def register_all_tools(mcp_instance) -> None:
        """
        Register all tools marked with @mcp_tool with the given MCP instance.
    
        Args:
            mcp_instance: The FastMCP instance to register tools with
        """
        logger.info(f"Registering {len(_tool_registry)} tools with MCP")
    
        # Sort tools by name
        sorted_funcs = sorted(_tool_registry, key=lambda f: f.__name__)
        for tool in sorted_funcs:
            logger.debug(f"Registering tool: {tool.__name__}")
    
            # Get tool metadata if it exists
            metadata = getattr(tool, "_mcp_tool_metadata", {})
    
            annotations = metadata.get("annotations", {})
            # Create tool decorator with metadata
            tool_decorator = mcp_instance.tool(
                name=metadata.get("name"),
                description=metadata.get("description"),
                annotations=annotations,
            )
    
            if annotations and annotations.get("permissions"):
                if not tool.__doc__:
                    tool.__doc__ = ""
                tool.__doc__ += f"\n\n Permissions:{annotations.get('permissions')}"
    
            # Register the tool
            tool_decorator(tool)
    
        logger.info("All tools registered successfully")
  • Pydantic schema definition for bulk_update_alerts inputs using Annotated types with Field descriptions, constraints (e.g., max_length on lists implicitly via validation, min_length=1), BeforeValidator for status enum validation, and examples.
        alert_ids: Annotated[
            list[str],
            Field(description="List of alert IDs to update (maximum 25)"),
        ],
        status: Annotated[
            str | None,
            BeforeValidator(_validate_alert_status),
            Field(
                description="Optional new status for the alerts",
                examples=["OPEN", "TRIAGED", "RESOLVED", "CLOSED"],
            ),
        ] = None,
        assignee_id: Annotated[
            str | None,
            Field(
                min_length=1,
                description="Optional ID of the user to assign the alerts to",
            ),
        ] = None,
        comment: Annotated[
            str | None,
            Field(
                min_length=1,
                description="Optional comment to add to all alerts",
            ),
        ] = None,
    ) -> dict[str, Any]:

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/panther-labs/mcp-panther'

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