import json import os import httpx from django.conf import settings from .models import MCPToolRequest, HumanResponse, SlackSettings from asgiref.sync import sync_to_async SLACK_API_URL = "https://slack.com/api/chat.postMessage" @sync_to_async def get_slack_token(): token = os.getenv("SLACK_BOT_TOKEN") if token: return token config = SlackSettings.objects.first() if config and config.bot_token: return config.bot_token return None async def send_slack_message(channel, text, thread_ts=None): token = await get_slack_token() if not token: return {"error": "SLACK_BOT_TOKEN not configured", "ok": False} headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json; charset=utf-8" } payload = { "channel": channel, "text": text, } if thread_ts: payload["thread_ts"] = thread_ts async with httpx.AsyncClient() as client: response = await client.post(SLACK_API_URL, headers=headers, json=payload) data = response.json() return data async def handle_mcp_request(body): """ Simple MCP JSON-RPC handler for HTTP/SSE. Supports tools/list and tools/call. """ try: request_data = json.loads(body) except json.JSONDecodeError: return {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None} method = request_data.get("method") params = request_data.get("params", {}) request_id = request_data.get("id") if method == "initialize": return { "jsonrpc": "2.0", "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "flatlogic-slack", "version": "1.0.0" } }, "id": request_id } elif method == "notifications/initialized": return None # No response for notifications elif method == "ping": return { "jsonrpc": "2.0", "result": {}, "id": request_id } elif method == "tools/list": return { "jsonrpc": "2.0", "result": { "tools": [ { "name": "send_slack_message", "description": "Sends a message to a Slack channel.", "inputSchema": { "type": "object", "properties": { "channel": {"type": "string", "description": "Channel ID or Name"}, "text": {"type": "string", "description": "Message content"} }, "required": ["channel", "text"] } }, { "name": "list_responses", "description": "Lists human responses for a specific request ID.", "inputSchema": { "type": "object", "properties": { "request_id": {"type": "string", "description": "The UUID of the original request"} }, "required": ["request_id"] } } ] }, "id": request_id } elif method == "tools/call": tool_name = params.get("name") arguments = params.get("arguments", {}) if tool_name == "send_slack_message": channel = arguments.get("channel") text = arguments.get("text") # Log request db_request = await MCPToolRequest.objects.acreate( tool_name=tool_name, arguments=arguments, slack_channel=channel or "" ) slack_result = await send_slack_message(channel, text) if slack_result.get("ok"): db_request.status = 'SENT' db_request.slack_ts = slack_result.get("ts", "") db_request.response_json = slack_result await db_request.asave() return { "jsonrpc": "2.0", "result": { "content": [ { "type": "text", "text": f"Message sent to Slack (TS: {db_request.slack_ts}). Request ID: {db_request.id}" } ] }, "id": request_id } else: db_request.status = 'ERROR' db_request.response_json = slack_result await db_request.asave() return { "jsonrpc": "2.0", "error": { "code": -32000, "message": f"Slack API Error: {slack_result.get('error')}" }, "id": request_id } elif tool_name == "list_responses": req_id = arguments.get("request_id") responses = [] async for r in HumanResponse.objects.filter(request_id=req_id): responses.append({ "text": r.text, "user": r.user_name, "file_url": r.file_url, "timestamp": str(r.created_at) }) return { "jsonrpc": "2.0", "result": { "content": [ { "type": "text", "text": json.dumps(responses, indent=2) if responses else "No responses yet." } ] }, "id": request_id } return { "jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": request_id }