113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
import json
|
|
import os
|
|
import platform
|
|
from django.http import JsonResponse, HttpResponse
|
|
from django.shortcuts import render, get_object_or_404
|
|
from django.utils import timezone
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from asgiref.sync import sync_to_async
|
|
from .models import MCPToolRequest, HumanResponse, SlackSettings
|
|
from .mcp import handle_mcp_request
|
|
|
|
@csrf_exempt
|
|
async def mcp_endpoint(request):
|
|
"""
|
|
Main entry point for MCP clients via HTTP.
|
|
Accepts JSON-RPC 2.0 payloads.
|
|
"""
|
|
if request.method != "POST":
|
|
return JsonResponse({"error": "Method not allowed"}, status=405)
|
|
|
|
body = request.body.decode("utf-8")
|
|
response_data = await handle_mcp_request(body)
|
|
|
|
if response_data is None:
|
|
return HttpResponse(status=204)
|
|
|
|
return JsonResponse(response_data)
|
|
|
|
def dashboard(request):
|
|
"""
|
|
Polished landing page showing active server status and recent logs.
|
|
"""
|
|
requests_list = MCPToolRequest.objects.all().order_by('-created_at')[:20]
|
|
|
|
# Slack config check
|
|
slack_token = os.getenv("SLACK_BOT_TOKEN")
|
|
if not slack_token:
|
|
config = SlackSettings.objects.first()
|
|
if config and config.bot_token:
|
|
slack_token = config.bot_token
|
|
|
|
is_slack_configured = bool(slack_token)
|
|
|
|
context = {
|
|
"is_slack_configured": is_slack_configured,
|
|
"requests": requests_list,
|
|
"django_version": platform.python_version(),
|
|
"now": timezone.now(),
|
|
"server_status": "ONLINE" if is_slack_configured else "CONFIG_PENDING"
|
|
}
|
|
return render(request, "core/index.html", context)
|
|
|
|
@csrf_exempt
|
|
def slack_webhook(request):
|
|
"""
|
|
Slack Event API Webhook.
|
|
Captures threaded replies and saves them as HumanResponse.
|
|
"""
|
|
if request.method == "POST":
|
|
try:
|
|
data = json.loads(request.body)
|
|
|
|
# 1. Verification Challenge
|
|
if data.get("type") == "url_verification":
|
|
return JsonResponse({"challenge": data.get("challenge")})
|
|
|
|
# 2. Event Callback
|
|
if data.get("type") == "event_callback":
|
|
event = data.get("event", {})
|
|
|
|
# We only care about user messages (no bot_id) in threads (has thread_ts)
|
|
if event.get("type") == "message" and not event.get("bot_id"):
|
|
thread_ts = event.get("thread_ts")
|
|
text = event.get("text", "")
|
|
user_id = event.get("user", "Unknown User")
|
|
|
|
# Basic extraction
|
|
user_name = user_id
|
|
|
|
files = event.get("files", [])
|
|
file_url = None
|
|
if files:
|
|
file_url = files[0].get("url_private", "")
|
|
|
|
if thread_ts:
|
|
# Find the corresponding tool request
|
|
mcp_request = MCPToolRequest.objects.filter(slack_ts=thread_ts).first()
|
|
if mcp_request:
|
|
# Avoid duplicate responses if Slack retries
|
|
# We can just check if we already have this text/user in last 10 seconds, or just save it
|
|
HumanResponse.objects.create(
|
|
request=mcp_request,
|
|
text=text,
|
|
user_name=user_name,
|
|
file_url=file_url
|
|
)
|
|
return HttpResponse("Created", status=201)
|
|
except Exception as e:
|
|
print("Webhook error:", e)
|
|
return HttpResponse("Error", status=400)
|
|
|
|
return HttpResponse("OK")
|
|
|
|
def request_detail(request, pk):
|
|
"""Detailed view of a single tool call."""
|
|
tool_request = get_object_or_404(MCPToolRequest, pk=pk)
|
|
responses = tool_request.responses.all().order_by('-created_at')
|
|
|
|
return render(request, "core/request_detail.html", {
|
|
"req": tool_request,
|
|
"responses": responses
|
|
})
|