Fix: robust MCP handler with proper logging and error handling
This commit is contained in:
+110
-75
@@ -24,10 +24,19 @@ Auth:
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from typing import Any, Dict, Optional
|
||||
import requests
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
logger = logging.getLogger("mcp-summary")
|
||||
|
||||
API_KEY = os.environ.get("API_KEY", "").strip()
|
||||
|
||||
# Tool definitions
|
||||
@@ -99,6 +108,7 @@ def call_llm(text: str, system_prompt: str, max_tokens: int = 2000) -> str:
|
||||
"top_p": 0.9
|
||||
}
|
||||
|
||||
logger.info(f"Calling LLM: {url} model={model_name}")
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -244,8 +254,8 @@ class MCPSummaryHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP handler for MCP summary server."""
|
||||
|
||||
def log_message(self, format, *args):
|
||||
# Quiet logs by default
|
||||
pass
|
||||
# Use our logger instead of default stderr logging
|
||||
logger.info(format % args)
|
||||
|
||||
def _send_json(self, status: int, payload: Any):
|
||||
"""Send JSON response."""
|
||||
@@ -266,96 +276,116 @@ class MCPSummaryHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
"""Handle GET requests (health check)."""
|
||||
if self.path == "/":
|
||||
self._send_json(200, {
|
||||
"service": "mcp-summary",
|
||||
"transport": "streamable-http",
|
||||
"docs": "Use POST / with MCP JSON-RPC (initialize, tools/list, tools/call)."
|
||||
})
|
||||
return
|
||||
try:
|
||||
if self.path == "/":
|
||||
self._send_json(200, {
|
||||
"service": "mcp-summary",
|
||||
"transport": "streamable-http",
|
||||
"docs": "Use POST / with MCP JSON-RPC (initialize, tools/list, tools/call)."
|
||||
})
|
||||
return
|
||||
|
||||
self.send_error(404, "Not Found")
|
||||
self.send_error(404, "Not Found")
|
||||
except Exception as e:
|
||||
logger.error(f"GET error: {e}", exc_info=True)
|
||||
# Ensure we still send something
|
||||
try:
|
||||
self.send_error(500, "Internal Server Error")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def do_POST(self):
|
||||
"""Handle MCP JSON-RPC requests."""
|
||||
if self.path not in ("/", "/mcp"):
|
||||
self.send_error(404, "Not Found")
|
||||
return
|
||||
|
||||
if not self._auth_or_401():
|
||||
return
|
||||
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
if length == 0:
|
||||
self._send_json(400, {"error": "Empty body"})
|
||||
return
|
||||
|
||||
raw = self.rfile.read(length)
|
||||
try:
|
||||
req = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
self._send_json(400, {"error": "Invalid JSON"})
|
||||
return
|
||||
if self.path not in ("/", "/mcp"):
|
||||
self.send_error(404, "Not Found")
|
||||
return
|
||||
|
||||
method = req.get("method")
|
||||
params = req.get("params") or {}
|
||||
req_id = req.get("id")
|
||||
if not self._auth_or_401():
|
||||
return
|
||||
|
||||
# MCP: initialize
|
||||
if method == "initialize":
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": {
|
||||
"protocolVersion": "2025-11-25",
|
||||
"capabilities": {
|
||||
"tools": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "mcp-summary",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
if length == 0:
|
||||
self._send_json(400, {"error": "Empty body"})
|
||||
return
|
||||
|
||||
# MCP: tools/list
|
||||
if method == "tools/list":
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": TOOLS_LIST
|
||||
})
|
||||
return
|
||||
|
||||
# MCP: tools/call
|
||||
if method == "tools/call":
|
||||
tool_name = params.get("name")
|
||||
tool_args = params.get("arguments") or {}
|
||||
raw = self.rfile.read(length)
|
||||
try:
|
||||
result = self._call_tool(tool_name, tool_args)
|
||||
req = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
self._send_json(400, {"error": "Invalid JSON"})
|
||||
return
|
||||
|
||||
method = req.get("method")
|
||||
params = req.get("params") or {}
|
||||
req_id = req.get("id")
|
||||
|
||||
logger.info(f"MCP request: method={method}, id={req_id}")
|
||||
|
||||
# MCP: initialize
|
||||
if method == "initialize":
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": {
|
||||
"content": [
|
||||
{"type": "text", "text": json.dumps(result, ensure_ascii=False)}
|
||||
]
|
||||
"protocolVersion": "2025-11-25",
|
||||
"capabilities": {
|
||||
"tools": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "mcp-summary",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return
|
||||
|
||||
# MCP: tools/list
|
||||
if method == "tools/list":
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"error": {
|
||||
"code": -32000,
|
||||
"message": str(e)
|
||||
}
|
||||
"result": TOOLS_LIST
|
||||
})
|
||||
return
|
||||
return
|
||||
|
||||
# Unknown method
|
||||
self._send_json(400, {"error": "Unknown method: " + str(method)})
|
||||
# MCP: tools/call
|
||||
if method == "tools/call":
|
||||
tool_name = params.get("name")
|
||||
tool_args = params.get("arguments") or {}
|
||||
try:
|
||||
result = self._call_tool(tool_name, tool_args)
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": {
|
||||
"content": [
|
||||
{"type": "text", "text": json.dumps(result, ensure_ascii=False)}
|
||||
]
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Tool call error: {e}", exc_info=True)
|
||||
self._send_json(200, {
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"error": {
|
||||
"code": -32000,
|
||||
"message": str(e)
|
||||
}
|
||||
})
|
||||
return
|
||||
|
||||
# Unknown method
|
||||
self._send_json(400, {"error": "Unknown method: " + str(method)})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"POST error: {e}", exc_info=True)
|
||||
# Fallback response to avoid silent drop
|
||||
try:
|
||||
self.send_error(500, "Internal Server Error")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _call_tool(self, name: str, args: Dict[str, Any]) -> Any:
|
||||
"""Execute a tool call."""
|
||||
@@ -373,13 +403,18 @@ class MCPSummaryHandler(BaseHTTPRequestHandler):
|
||||
def main():
|
||||
"""Start the MCP summary server."""
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else int(os.environ.get("PORT", "8080"))
|
||||
|
||||
logger.info(f"Starting MCP Summary Server on 0.0.0.0:{port}")
|
||||
logger.info(f"Auth mode: {'Bearer (API_KEY set)' if API_KEY else 'none (API_KEY not set)'}")
|
||||
logger.info(f"LLM URL: {os.environ.get('OPENAPI_URL', 'http://localhost:8080/v1')}")
|
||||
logger.info(f"Model: {os.environ.get('MODEL_NAME', 'gpt-4o')}")
|
||||
|
||||
server = HTTPServer(("0.0.0.0", port), MCPSummaryHandler)
|
||||
mode = "auth enabled (Bearer)" if API_KEY else "no auth (API_KEY not set)"
|
||||
print(f"MCP Summary Server listening on 0.0.0.0:{port} [{mode}]")
|
||||
try:
|
||||
logger.info(f"MCP Summary Server listening on 0.0.0.0:{port}")
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down...")
|
||||
logger.info("Shutting down...")
|
||||
server.server_close()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user