""" Slack MCP Tool - FREE (Webhook-based) ====================================== Send messages to Slack using incoming webhooks. FREE - only requires creating a webhook URL in Slack settings. No paid API access needed! Setup: 1. Go to your Slack workspace 2. Create an app at https://api.slack.com/apps 3. Enable "Incoming Webhooks" 4. Create a webhook URL for your channel 5. Set SLACK_WEBHOOK_URL environment variable This is the FREE tier approach - no Bot Token needed. """ import os import json import logging from typing import Dict, List, Any, Optional from dataclasses import dataclass import requests logger = logging.getLogger(__name__) @dataclass class SlackMessage: """A Slack message""" text: str blocks: Optional[List[Dict[str, Any]]] = None attachments: Optional[List[Dict[str, Any]]] = None class SlackWebhookMCP: """ Slack integration using FREE incoming webhooks. No paid API subscription required - just create a webhook URL. """ def __init__(self, webhook_url: str = None): self.webhook_url = webhook_url or os.getenv("SLACK_WEBHOOK_URL", "") def is_configured(self) -> bool: """Check if Slack webhook is configured""" return bool(self.webhook_url) def send_message( self, text: str, blocks: List[Dict[str, Any]] = None ) -> Dict[str, Any]: """ Send a message to Slack via webhook. Args: text: Plain text message (required as fallback) blocks: Optional Block Kit blocks for rich formatting Returns: {"success": bool, "error": str if failed} """ if not self.is_configured(): logger.warning("Slack webhook not configured") return { "success": False, "error": "SLACK_WEBHOOK_URL not set. Please configure it to use Slack integration." } payload = {"text": text} if blocks: payload["blocks"] = blocks try: response = requests.post( self.webhook_url, json=payload, timeout=10 ) if response.status_code == 200: logger.info(f"Slack message sent successfully") return {"success": True} else: logger.error(f"Slack error: {response.status_code} - {response.text}") return {"success": False, "error": response.text} except Exception as e: logger.error(f"Slack request error: {e}") return {"success": False, "error": str(e)} def send_alert( self, title: str, message: str, severity: str = "info", details: Dict[str, Any] = None ) -> Dict[str, Any]: """ Send a formatted alert to Slack. Args: title: Alert title message: Alert message severity: "info", "warning", "error", "success" details: Optional key-value details to include """ # Color based on severity color_map = { "info": "#2196F3", "warning": "#FFC107", "error": "#F44336", "success": "#4CAF50" } color = color_map.get(severity, "#2196F3") # Emoji based on severity emoji_map = { "info": "â„šī¸", "warning": "âš ī¸", "error": "🚨", "success": "✅" } emoji = emoji_map.get(severity, "â„šī¸") # Build blocks blocks = [ { "type": "header", "text": { "type": "plain_text", "text": f"{emoji} {title}", "emoji": True } }, { "type": "section", "text": { "type": "mrkdwn", "text": message } } ] # Add details if provided if details: fields = [] for key, value in list(details.items())[:10]: # Limit to 10 fields fields.append({ "type": "mrkdwn", "text": f"*{key}:* {value}" }) blocks.append({ "type": "section", "fields": fields }) # Add divider blocks.append({"type": "divider"}) return self.send_message(f"{emoji} {title}: {message}", blocks) def send_insight( self, insight_title: str, insight_text: str, metric_name: str = None, metric_value: str = None, change_percent: float = None, chart_url: str = None ) -> Dict[str, Any]: """ Send a business insight to Slack with rich formatting. """ blocks = [ { "type": "header", "text": { "type": "plain_text", "text": f"📊 DataVision Insight", "emoji": True } }, { "type": "section", "text": { "type": "mrkdwn", "text": f"*{insight_title}*\n\n{insight_text}" } } ] # Add metric if provided if metric_name and metric_value: change_emoji = "" if change_percent: if change_percent > 0: change_emoji = f"📈 +{change_percent:.1f}%" else: change_emoji = f"📉 {change_percent:.1f}%" blocks.append({ "type": "section", "fields": [ { "type": "mrkdwn", "text": f"*{metric_name}*\n{metric_value}" }, { "type": "mrkdwn", "text": f"*Change*\n{change_emoji}" if change_emoji else "" } ] }) # Add chart if provided if chart_url: blocks.append({ "type": "image", "image_url": chart_url, "alt_text": insight_title }) blocks.append({"type": "divider"}) return self.send_message(f"📊 {insight_title}", blocks) def send_daily_summary( self, metrics: List[Dict[str, Any]], highlights: List[str], alerts: List[str] = None ) -> Dict[str, Any]: """ Send a daily business summary to Slack. Args: metrics: List of {"name": str, "value": str, "change": str} highlights: List of key highlights alerts: Optional list of alerts/warnings """ blocks = [ { "type": "header", "text": { "type": "plain_text", "text": "📈 Daily Business Summary", "emoji": True } }, { "type": "section", "text": { "type": "mrkdwn", "text": "*Key Metrics*" } } ] # Add metrics for metric in metrics[:6]: # Limit to 6 blocks.append({ "type": "section", "fields": [ { "type": "mrkdwn", "text": f"*{metric.get('name', '')}*" }, { "type": "mrkdwn", "text": f"{metric.get('value', '')} ({metric.get('change', '')})" } ] }) # Add highlights if highlights: highlight_text = "\n".join([f"â€ĸ {h}" for h in highlights[:5]]) blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*✨ Highlights*\n{highlight_text}" } }) # Add alerts if alerts: alert_text = "\n".join([f"â€ĸ {a}" for a in alerts[:5]]) blocks.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*âš ī¸ Alerts*\n{alert_text}" } }) blocks.append({"type": "divider"}) return self.send_message("📈 Daily Business Summary from DataVision", blocks) # MCP Tool Interface class SlackMCPTool: """ MCP-compatible interface for Slack. """ name = "slack" description = "Send messages to Slack channels using webhooks (FREE)" def __init__(self, webhook_url: str = None): self.slack = SlackWebhookMCP(webhook_url) async def execute(self, action: str, params: Dict[str, Any]) -> Dict[str, Any]: """ Execute a Slack action. Actions: - send: Send a simple message - alert: Send an alert - insight: Send a business insight - summary: Send daily summary """ if not self.slack.is_configured(): return { "success": False, "error": "Slack not configured. Set SLACK_WEBHOOK_URL environment variable.", "setup_help": "Go to https://api.slack.com/apps, create an app, enable Incoming Webhooks, and copy the URL." } try: if action == "send": return self.slack.send_message( text=params.get("text", ""), blocks=params.get("blocks") ) elif action == "alert": return self.slack.send_alert( title=params.get("title", "Alert"), message=params.get("message", ""), severity=params.get("severity", "info"), details=params.get("details") ) elif action == "insight": return self.slack.send_insight( insight_title=params.get("title", ""), insight_text=params.get("text", ""), metric_name=params.get("metric_name"), metric_value=params.get("metric_value"), change_percent=params.get("change_percent") ) elif action == "summary": return self.slack.send_daily_summary( metrics=params.get("metrics", []), highlights=params.get("highlights", []), alerts=params.get("alerts", []) ) else: return {"error": f"Unknown action: {action}"} except Exception as e: return {"error": str(e)} # Convenience functions def send_to_slack(message: str, webhook_url: str = None) -> bool: """Quick send to Slack""" slack = SlackWebhookMCP(webhook_url) result = slack.send_message(message) return result.get("success", False) def send_slack_alert( title: str, message: str, severity: str = "info", webhook_url: str = None ) -> bool: """Send an alert to Slack""" slack = SlackWebhookMCP(webhook_url) result = slack.send_alert(title, message, severity) return result.get("success", False) # Test if __name__ == "__main__": # Test (requires SLACK_WEBHOOK_URL to be set) slack = SlackWebhookMCP() if slack.is_configured(): # Test simple message result = slack.send_message("🚀 DataVision test message!") print(f"Simple message: {result}") # Test alert result = slack.send_alert( title="Test Alert", message="This is a test alert from DataVision", severity="info", details={"Source": "Test Script", "Time": "Now"} ) print(f"Alert: {result}") else: print("Slack not configured. Set SLACK_WEBHOOK_URL to test.") print("Note: This is FREE - just create an incoming webhook in Slack!")