Files
agent_management/agent_templates/agents/facebook_agent/mcp_http_server.py
T

328 lines
9.5 KiB
Python

"""
MCP HTTP/SSE 服务器 - 支持远程调用
实现 MCP 协议的 HTTP 和 SSE 传输方式,供 Cursor 等客户端远程调用
"""
import json
import os
import uuid
from typing import Dict, Any, Optional, AsyncGenerator
from fastapi import FastAPI, Request, HTTPException, Header
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from loguru import logger
# 导入 MCP 服务器和工具函数
from .mcp_server import (
server as mcp_server,
search_facebook,
initialize_agent
)
# 工具映射
TOOL_MAP = {
'search_facebook': search_facebook,
}
# 创建 FastAPI 应用
app = FastAPI(
title="MCP HTTP/SSE Server - Facebook搜索Agent",
description="MCP 协议的 HTTP 和 SSE 传输实现",
version="1.0.0"
)
# 配置 CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Session 管理
sessions: Dict[str, Dict[str, Any]] = {}
class MCPRequest(BaseModel):
"""MCP JSON-RPC 请求"""
jsonrpc: str = "2.0"
id: Optional[str] = None
method: str
params: Optional[Dict[str, Any]] = None
class MCPResponse(BaseModel):
"""MCP JSON-RPC 响应"""
jsonrpc: str = "2.0"
id: Optional[str] = None
result: Optional[Any] = None
error: Optional[Dict[str, Any]] = None
async def handle_mcp_request(request_data: Dict[str, Any], session_id: Optional[str] = None) -> Dict[str, Any]:
"""处理 MCP 请求"""
method = request_data.get("method")
params = request_data.get("params", {})
request_id = request_data.get("id")
try:
if method == "initialize":
# 初始化会话
if not session_id:
session_id = str(uuid.uuid4())
sessions[session_id] = {
"initialized": True,
"capabilities": {}
}
return {
"jsonrpc": "2.0",
"id": request_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": {}
},
"serverInfo": {
"name": "Facebook搜索Agent",
"version": "1.0.0"
}
}
}
elif method == "tools/list":
# 列出所有工具
tools = [
{
"name": "search_facebook",
"description": "搜索Facebook内容,返回相关帖子和AI生成的总结。支持搜索关键词,返回帖子标题、链接、摘要、作者、点赞数等信息,并提供AI生成的总结。",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,例如:technology news、travel tips、food recipes等"
},
"limit": {
"type": "integer",
"description": "返回结果数量,默认5,最大20",
"default": 5,
"minimum": 1,
"maximum": 20
}
},
"required": ["query"]
}
}
]
return {
"jsonrpc": "2.0",
"id": request_id,
"result": {
"tools": tools
}
}
elif method == "tools/call":
# 调用工具
tool_name = params.get("name")
arguments = params.get("arguments", {})
if tool_name not in TOOL_MAP:
raise ValueError(f"Tool '{tool_name}' not found")
# 获取工具函数
tool_func = TOOL_MAP[tool_name]
# 调用工具(异步)
result = await tool_func(**arguments)
return {
"jsonrpc": "2.0",
"id": request_id,
"result": {
"content": [
{
"type": "text",
"text": str(result)
}
]
}
}
elif method == "ping":
return {
"jsonrpc": "2.0",
"id": request_id,
"result": {}
}
else:
raise ValueError(f"Unknown method: {method}")
except Exception as e:
return {
"jsonrpc": "2.0",
"id": request_id,
"error": {
"code": -32603,
"message": str(e)
}
}
@app.on_event("startup")
async def startup_event():
"""应用启动时初始化"""
try:
# 初始化Agent
initialize_agent()
logger.info("=" * 60)
logger.info("Facebook搜索Agent MCP HTTP服务器启动")
logger.info("=" * 60)
logger.info("MCP服务器已就绪,支持HTTP/SSE传输")
except Exception as e:
logger.error(f"启动失败: {e}")
raise
@app.post("/mcp")
async def mcp_http_endpoint(request: Request):
"""MCP HTTP 端点 - Streamable HTTP"""
try:
body = await request.json()
session_id = request.headers.get("x-mcp-session-id")
response = await handle_mcp_request(body, session_id)
# 如果创建了新会话,返回 session ID
if "result" in response and isinstance(response["result"], dict):
if "sessionId" not in response["result"] and session_id:
response["result"]["sessionId"] = session_id
return JSONResponse(
content=response,
headers={"x-mcp-session-id": session_id or ""}
)
except Exception as e:
return JSONResponse(
status_code=400,
content={
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": f"Parse error: {str(e)}"
}
}
)
@app.get("/mcp/sse")
async def mcp_sse_endpoint(request: Request):
"""MCP SSE 端点 - Server-Sent Events"""
session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4())
async def event_stream() -> AsyncGenerator[str, None]:
# 发送初始连接消息
yield f"data: {json.dumps({'type': 'connection', 'sessionId': session_id})}\n\n"
# 发送工具列表
tools = list(TOOL_MAP.keys())
yield f"data: {json.dumps({'type': 'tools', 'tools': tools})}\n\n"
# 保持连接
import asyncio
while True:
await asyncio.sleep(30) # 心跳
yield f"data: {json.dumps({'type': 'ping'})}\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"x-mcp-session-id": session_id
}
)
@app.post("/mcp/sse")
async def mcp_sse_post(request: Request):
"""MCP SSE POST 端点 - 处理 SSE 请求"""
try:
body = await request.json()
session_id = request.headers.get("x-mcp-session-id") or str(uuid.uuid4())
async def response_stream() -> AsyncGenerator[str, None]:
response = await handle_mcp_request(body, session_id)
yield f"data: {json.dumps(response)}\n\n"
return StreamingResponse(
response_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"x-mcp-session-id": session_id
}
)
except Exception as e:
return JSONResponse(
status_code=400,
content={
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": f"Parse error: {str(e)}"
}
}
)
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"service": "MCP HTTP/SSE Server - Facebook搜索Agent",
"tools_count": len(TOOL_MAP)
}
@app.get("/")
async def root():
"""根端点"""
return {
"service": "MCP HTTP/SSE Server - Facebook搜索Agent",
"version": "1.0.0",
"endpoints": {
"mcp_http": "/mcp",
"mcp_sse": "/mcp/sse",
"health": "/health"
},
"tools": list(TOOL_MAP.keys())
}
if __name__ == "__main__":
import uvicorn
host = os.getenv("MCP_HOST", "0.0.0.0")
port = int(os.getenv("MCP_PORT", "8001"))
print(f"🚀 MCP HTTP/SSE Server 启动中...")
print(f"📡 HTTP 端点: http://{host}:{port}/mcp")
print(f"📡 SSE 端点: http://{host}:{port}/mcp/sse")
print(f"📚 健康检查: http://{host}:{port}/health")
uvicorn.run(app, host=host, port=port)