""" MCP Server for Financial Data Search 基于FastAPI的MCP服务端,整合多个金融数据搜索API 支持LLM调用和Web聊天功能 """ from fastapi import FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from pydantic import BaseModel, Field from typing import List, Dict, Any, Optional, Literal from datetime import datetime, date import logging import httpx from enum import Enum import mcp_database as db # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 创建FastAPI应用 app = FastAPI( title="Financial Data MCP Server", description="Model Context Protocol server for financial data search and analysis", version="1.0.0" ) # 添加CORS中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ==================== 配置 ==================== class ServiceEndpoints: """API服务端点配置""" NEWS_API = "http://222.128.1.157:21891" # 新闻API ROADSHOW_API = "http://222.128.1.157:19800" # 路演API CONCEPT_API = "http://localhost:6801" # 概念API(本地) STOCK_ANALYSIS_API = "http://222.128.1.157:8811" # 涨停分析+研报API # HTTP客户端配置 HTTP_CLIENT = httpx.AsyncClient(timeout=60.0) # ==================== MCP协议数据模型 ==================== class ToolParameter(BaseModel): """工具参数定义""" type: str description: str enum: Optional[List[str]] = None default: Optional[Any] = None class ToolDefinition(BaseModel): """工具定义""" name: str description: str parameters: Dict[str, Dict[str, Any]] required: List[str] = [] class ToolCallRequest(BaseModel): """工具调用请求""" tool: str arguments: Dict[str, Any] = {} class ToolCallResponse(BaseModel): """工具调用响应""" success: bool data: Optional[Any] = None error: Optional[str] = None metadata: Optional[Dict[str, Any]] = None # ==================== MCP工具定义 ==================== TOOLS: List[ToolDefinition] = [ ToolDefinition( name="search_news", description="搜索全球新闻,支持关键词搜索和日期过滤。适用于查找国际新闻、行业动态等。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,例如:'人工智能'、'新能源汽车'" }, "source": { "type": "string", "description": "新闻来源筛选,可选" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "top_k": { "type": "integer", "description": "返回结果数量,默认20", "default": 20 } }, "required": ["query"] } ), ToolDefinition( name="search_china_news", description="搜索中国新闻,使用KNN语义搜索。支持精确匹配模式,适合查找股票、公司相关新闻。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" }, "exact_match": { "type": "boolean", "description": "是否精确匹配(用于股票代码、公司名称等),默认false", "default": False }, "source": { "type": "string", "description": "新闻来源筛选" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "top_k": { "type": "integer", "description": "返回结果数量,默认20", "default": 20 } }, "required": ["query"] } ), ToolDefinition( name="search_medical_news", description="搜索医疗健康类新闻,包括医药、医疗设备、生物技术等领域。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" }, "source": { "type": "string", "description": "新闻来源" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "top_k": { "type": "integer", "description": "返回结果数量", "default": 10 } }, "required": ["query"] } ), ToolDefinition( name="search_roadshows", description="搜索上市公司路演、投资者交流活动记录。可按公司代码、日期范围搜索。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,可以是公司名称、主题等" }, "company_code": { "type": "string", "description": "公司股票代码,例如:'600519.SH'" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS" }, "size": { "type": "integer", "description": "返回结果数量", "default": 10 } }, "required": ["query"] } ), ToolDefinition( name="search_concepts", description="搜索股票概念板块,支持按涨跌幅、股票数量排序。返回概念详情及相关股票列表。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,例如:'新能源'、'人工智能'" }, "size": { "type": "integer", "description": "每页结果数量", "default": 10 }, "page": { "type": "integer", "description": "页码", "default": 1 }, "sort_by": { "type": "string", "description": "排序方式:change_pct(涨跌幅), _score(相关度), stock_count(股票数), concept_name(名称)", "enum": ["change_pct", "_score", "stock_count", "concept_name"], "default": "change_pct" }, "trade_date": { "type": "string", "description": "交易日期,格式:YYYY-MM-DD,默认最新" } }, "required": ["query"] } ), ToolDefinition( name="get_concept_details", description="根据概念ID获取详细信息,包括描述、相关股票、涨跌幅数据等。", parameters={ "type": "object", "properties": { "concept_id": { "type": "string", "description": "概念ID" }, "trade_date": { "type": "string", "description": "交易日期,格式:YYYY-MM-DD" } }, "required": ["concept_id"] } ), ToolDefinition( name="get_stock_concepts", description="查询指定股票的所有相关概念板块,包括涨跌幅信息。", parameters={ "type": "object", "properties": { "stock_code": { "type": "string", "description": "股票代码或名称" }, "size": { "type": "integer", "description": "返回概念数量", "default": 50 }, "sort_by": { "type": "string", "description": "排序方式", "enum": ["stock_count", "concept_name", "recent"], "default": "stock_count" }, "trade_date": { "type": "string", "description": "交易日期,格式:YYYY-MM-DD" } }, "required": ["stock_code"] } ), ToolDefinition( name="get_concept_statistics", description="获取概念板块统计数据,包括涨幅榜、跌幅榜、活跃榜、波动榜、连涨榜。", parameters={ "type": "object", "properties": { "days": { "type": "integer", "description": "统计天数(与start_date/end_date互斥)" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "min_stock_count": { "type": "integer", "description": "最少股票数量过滤", "default": 3 } }, "required": [] } ), ToolDefinition( name="search_limit_up_stocks", description="搜索涨停股票,支持按日期、关键词、板块等条件搜索。包括混合语义搜索。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词(涨停原因、公司名称等)" }, "date": { "type": "string", "description": "日期,格式:YYYYMMDD" }, "mode": { "type": "string", "description": "搜索模式", "enum": ["hybrid", "text", "vector"], "default": "hybrid" }, "sectors": { "type": "array", "items": {"type": "string"}, "description": "板块筛选" }, "page_size": { "type": "integer", "description": "每页结果数", "default": 20 } }, "required": ["query"] } ), ToolDefinition( name="get_daily_stock_analysis", description="获取指定日期的涨停股票分析,包括板块分析、词云、趋势图表等。", parameters={ "type": "object", "properties": { "date": { "type": "string", "description": "日期,格式:YYYYMMDD" } }, "required": ["date"] } ), ToolDefinition( name="search_research_reports", description="搜索研究报告,支持文本和语义混合搜索。可按作者、证券、日期等筛选。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" }, "mode": { "type": "string", "description": "搜索模式", "enum": ["hybrid", "text", "vector"], "default": "hybrid" }, "exact_match": { "type": "string", "description": "是否精确匹配:0=模糊,1=精确", "enum": ["0", "1"], "default": "0" }, "security_code": { "type": "string", "description": "证券代码筛选" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "size": { "type": "integer", "description": "返回结果数量", "default": 10 } }, "required": ["query"] } ), ToolDefinition( name="get_stock_basic_info", description="获取股票基本信息,包括公司名称、行业、地址、主营业务、高管等基础数据。", parameters={ "type": "object", "properties": { "seccode": { "type": "string", "description": "股票代码,例如:600519" } }, "required": ["seccode"] } ), ToolDefinition( name="get_stock_financial_index", description="获取股票财务指标,包括每股收益、净资产收益率、营收增长率等关键财务数据。", parameters={ "type": "object", "properties": { "seccode": { "type": "string", "description": "股票代码" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "limit": { "type": "integer", "description": "返回条数,默认10", "default": 10 } }, "required": ["seccode"] } ), ToolDefinition( name="get_stock_trade_data", description="获取股票交易数据,包括价格、成交量、涨跌幅、换手率等日线行情数据。", parameters={ "type": "object", "properties": { "seccode": { "type": "string", "description": "股票代码" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "limit": { "type": "integer", "description": "返回条数,默认30", "default": 30 } }, "required": ["seccode"] } ), ToolDefinition( name="get_stock_balance_sheet", description="获取股票资产负债表,包括资产、负债、所有者权益等财务状况数据。", parameters={ "type": "object", "properties": { "seccode": { "type": "string", "description": "股票代码" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "limit": { "type": "integer", "description": "返回条数,默认8", "default": 8 } }, "required": ["seccode"] } ), ToolDefinition( name="get_stock_cashflow", description="获取股票现金流量表,包括经营、投资、筹资活动现金流数据。", parameters={ "type": "object", "properties": { "seccode": { "type": "string", "description": "股票代码" }, "start_date": { "type": "string", "description": "开始日期,格式:YYYY-MM-DD" }, "end_date": { "type": "string", "description": "结束日期,格式:YYYY-MM-DD" }, "limit": { "type": "integer", "description": "返回条数,默认8", "default": 8 } }, "required": ["seccode"] } ), ToolDefinition( name="search_stocks_by_criteria", description="按条件搜索股票,支持按行业、地区、市值等条件筛选股票列表。", parameters={ "type": "object", "properties": { "industry": { "type": "string", "description": "行业名称,支持模糊匹配" }, "province": { "type": "string", "description": "省份名称" }, "min_market_cap": { "type": "number", "description": "最小市值(亿元)" }, "max_market_cap": { "type": "number", "description": "最大市值(亿元)" }, "limit": { "type": "integer", "description": "返回条数,默认50", "default": 50 } }, "required": [] } ), ToolDefinition( name="get_stock_comparison", description="股票对比分析,支持多只股票的财务指标或交易数据对比。", parameters={ "type": "object", "properties": { "seccodes": { "type": "array", "items": {"type": "string"}, "description": "股票代码列表,至少2个" }, "metric": { "type": "string", "description": "对比指标类型", "enum": ["financial", "trade"], "default": "financial" } }, "required": ["seccodes"] } ), ] # ==================== MCP协议端点 ==================== @app.get("/") async def root(): """服务根端点""" return { "name": "Financial Data MCP Server", "version": "1.0.0", "protocol": "MCP", "description": "Model Context Protocol server for financial data search and analysis" } @app.get("/tools") async def list_tools(): """列出所有可用工具""" return { "tools": [tool.dict() for tool in TOOLS] } @app.get("/tools/{tool_name}") async def get_tool(tool_name: str): """获取特定工具的定义""" tool = next((t for t in TOOLS if t.name == tool_name), None) if not tool: raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") return tool.dict() @app.post("/tools/call") async def call_tool(request: ToolCallRequest): """调用工具""" logger.info(f"Tool call: {request.tool} with args: {request.arguments}") try: # 路由到对应的工具处理函数 handler = TOOL_HANDLERS.get(request.tool) if not handler: raise HTTPException(status_code=404, detail=f"Tool '{request.tool}' not found") result = await handler(request.arguments) return ToolCallResponse( success=True, data=result, metadata={ "tool": request.tool, "timestamp": datetime.now().isoformat() } ) except Exception as e: logger.error(f"Tool call error: {str(e)}", exc_info=True) return ToolCallResponse( success=False, error=str(e), metadata={ "tool": request.tool, "timestamp": datetime.now().isoformat() } ) # ==================== 工具处理函数 ==================== async def handle_search_news(args: Dict[str, Any]) -> Any: """处理新闻搜索""" params = { "query": args.get("query"), "source": args.get("source"), "start_date": args.get("start_date"), "end_date": args.get("end_date"), "top_k": args.get("top_k", 20) } # 移除None值 params = {k: v for k, v in params.items() if v is not None} response = await HTTP_CLIENT.get(f"{ServiceEndpoints.NEWS_API}/search_news", params=params) response.raise_for_status() return response.json() async def handle_search_china_news(args: Dict[str, Any]) -> Any: """处理中国新闻搜索""" params = { "query": args.get("query"), "exact_match": args.get("exact_match", False), "source": args.get("source"), "start_date": args.get("start_date"), "end_date": args.get("end_date"), "top_k": args.get("top_k", 20) } params = {k: v for k, v in params.items() if v is not None} response = await HTTP_CLIENT.get(f"{ServiceEndpoints.NEWS_API}/search_china_news", params=params) response.raise_for_status() return response.json() async def handle_search_medical_news(args: Dict[str, Any]) -> Any: """处理医疗新闻搜索""" params = { "query": args["query"], "source": args.get("source"), "start_date": args.get("start_date"), "end_date": args.get("end_date"), "top_k": args.get("top_k", 10) } params = {k: v for k, v in params.items() if v is not None} response = await HTTP_CLIENT.get(f"{ServiceEndpoints.NEWS_API}/search_medical_news", params=params) response.raise_for_status() return response.json() async def handle_search_roadshows(args: Dict[str, Any]) -> Any: """处理路演搜索""" params = { "query": args["query"], "company_code": args.get("company_code"), "start_date": args.get("start_date"), "end_date": args.get("end_date"), "size": args.get("size", 10) } params = {k: v for k, v in params.items() if v is not None} response = await HTTP_CLIENT.get(f"{ServiceEndpoints.ROADSHOW_API}/search", params=params) response.raise_for_status() return response.json() async def handle_search_concepts(args: Dict[str, Any]) -> Any: """处理概念搜索""" payload = { "query": args["query"], "size": args.get("size", 10), "page": args.get("page", 1), "search_size": 100, "sort_by": args.get("sort_by", "change_pct"), "use_knn": True } if args.get("trade_date"): payload["trade_date"] = args["trade_date"] response = await HTTP_CLIENT.post(f"{ServiceEndpoints.CONCEPT_API}/search", json=payload) response.raise_for_status() return response.json() async def handle_get_concept_details(args: Dict[str, Any]) -> Any: """处理概念详情获取""" concept_id = args["concept_id"] params = {} if args.get("trade_date"): params["trade_date"] = args["trade_date"] response = await HTTP_CLIENT.get( f"{ServiceEndpoints.CONCEPT_API}/concept/{concept_id}", params=params ) response.raise_for_status() return response.json() async def handle_get_stock_concepts(args: Dict[str, Any]) -> Any: """处理股票概念获取""" stock_code = args["stock_code"] params = { "size": args.get("size", 50), "sort_by": args.get("sort_by", "stock_count"), "include_description": True } if args.get("trade_date"): params["trade_date"] = args["trade_date"] response = await HTTP_CLIENT.get( f"{ServiceEndpoints.CONCEPT_API}/stock/{stock_code}/concepts", params=params ) response.raise_for_status() return response.json() async def handle_get_concept_statistics(args: Dict[str, Any]) -> Any: """处理概念统计获取""" params = {} if args.get("days"): params["days"] = args["days"] if args.get("start_date"): params["start_date"] = args["start_date"] if args.get("end_date"): params["end_date"] = args["end_date"] if args.get("min_stock_count"): params["min_stock_count"] = args["min_stock_count"] response = await HTTP_CLIENT.get(f"{ServiceEndpoints.CONCEPT_API}/statistics", params=params) response.raise_for_status() return response.json() async def handle_search_limit_up_stocks(args: Dict[str, Any]) -> Any: """处理涨停股票搜索""" payload = { "query": args["query"], "mode": args.get("mode", "hybrid"), "page_size": args.get("page_size", 20) } if args.get("date"): payload["date"] = args["date"] if args.get("sectors"): payload["sectors"] = args["sectors"] response = await HTTP_CLIENT.post( f"{ServiceEndpoints.STOCK_ANALYSIS_API}/api/v1/stocks/search/hybrid", json=payload ) response.raise_for_status() return response.json() async def handle_get_daily_stock_analysis(args: Dict[str, Any]) -> Any: """处理每日股票分析获取""" date = args["date"] response = await HTTP_CLIENT.get( f"{ServiceEndpoints.STOCK_ANALYSIS_API}/api/v1/analysis/daily/{date}" ) response.raise_for_status() return response.json() async def handle_search_research_reports(args: Dict[str, Any]) -> Any: """处理研报搜索""" params = { "query": args["query"], "mode": args.get("mode", "hybrid"), "exact_match": args.get("exact_match", "0"), "size": args.get("size", 10) } if args.get("security_code"): params["security_code"] = args["security_code"] if args.get("start_date"): params["start_date"] = args["start_date"] if args.get("end_date"): params["end_date"] = args["end_date"] response = await HTTP_CLIENT.get(f"{ServiceEndpoints.STOCK_ANALYSIS_API}/search", params=params) response.raise_for_status() return response.json() async def handle_get_stock_basic_info(args: Dict[str, Any]) -> Any: """处理股票基本信息查询""" seccode = args["seccode"] result = await db.get_stock_basic_info(seccode) if result: return {"success": True, "data": result} else: return {"success": False, "error": f"未找到股票代码 {seccode} 的信息"} async def handle_get_stock_financial_index(args: Dict[str, Any]) -> Any: """处理股票财务指标查询""" seccode = args["seccode"] start_date = args.get("start_date") end_date = args.get("end_date") limit = args.get("limit", 10) result = await db.get_stock_financial_index(seccode, start_date, end_date, limit) return { "success": True, "data": result, "count": len(result) } async def handle_get_stock_trade_data(args: Dict[str, Any]) -> Any: """处理股票交易数据查询""" seccode = args["seccode"] start_date = args.get("start_date") end_date = args.get("end_date") limit = args.get("limit", 30) result = await db.get_stock_trade_data(seccode, start_date, end_date, limit) return { "success": True, "data": result, "count": len(result) } async def handle_get_stock_balance_sheet(args: Dict[str, Any]) -> Any: """处理资产负债表查询""" seccode = args["seccode"] start_date = args.get("start_date") end_date = args.get("end_date") limit = args.get("limit", 8) result = await db.get_stock_balance_sheet(seccode, start_date, end_date, limit) return { "success": True, "data": result, "count": len(result) } async def handle_get_stock_cashflow(args: Dict[str, Any]) -> Any: """处理现金流量表查询""" seccode = args["seccode"] start_date = args.get("start_date") end_date = args.get("end_date") limit = args.get("limit", 8) result = await db.get_stock_cashflow(seccode, start_date, end_date, limit) return { "success": True, "data": result, "count": len(result) } async def handle_search_stocks_by_criteria(args: Dict[str, Any]) -> Any: """处理按条件搜索股票""" industry = args.get("industry") province = args.get("province") min_market_cap = args.get("min_market_cap") max_market_cap = args.get("max_market_cap") limit = args.get("limit", 50) result = await db.search_stocks_by_criteria( industry, province, min_market_cap, max_market_cap, limit ) return { "success": True, "data": result, "count": len(result) } async def handle_get_stock_comparison(args: Dict[str, Any]) -> Any: """处理股票对比分析""" seccodes = args["seccodes"] metric = args.get("metric", "financial") result = await db.get_stock_comparison(seccodes, metric) return { "success": True, "data": result } # 工具处理函数映射 TOOL_HANDLERS = { "search_news": handle_search_news, "search_china_news": handle_search_china_news, "search_medical_news": handle_search_medical_news, "search_roadshows": handle_search_roadshows, "search_concepts": handle_search_concepts, "get_concept_details": handle_get_concept_details, "get_stock_concepts": handle_get_stock_concepts, "get_concept_statistics": handle_get_concept_statistics, "search_limit_up_stocks": handle_search_limit_up_stocks, "get_daily_stock_analysis": handle_get_daily_stock_analysis, "search_research_reports": handle_search_research_reports, "get_stock_basic_info": handle_get_stock_basic_info, "get_stock_financial_index": handle_get_stock_financial_index, "get_stock_trade_data": handle_get_stock_trade_data, "get_stock_balance_sheet": handle_get_stock_balance_sheet, "get_stock_cashflow": handle_get_stock_cashflow, "search_stocks_by_criteria": handle_search_stocks_by_criteria, "get_stock_comparison": handle_get_stock_comparison, } # ==================== Web聊天接口 ==================== class ChatMessage(BaseModel): """聊天消息""" role: Literal["user", "assistant", "system"] content: str class ChatRequest(BaseModel): """聊天请求""" messages: List[ChatMessage] stream: bool = False @app.post("/chat") async def chat(request: ChatRequest): """ Web聊天接口 这是一个简化的接口,实际应该集成LLM API(如OpenAI、Claude等) 这里只是演示如何使用工具 """ # TODO: 集成实际的LLM API # 1. 将消息发送给LLM # 2. LLM返回需要调用的工具 # 3. 调用工具并获取结果 # 4. 将工具结果返回给LLM # 5. LLM生成最终回复 return { "message": "Chat endpoint placeholder - integrate with your LLM provider", "available_tools": len(TOOLS), "hint": "Use POST /tools/call to invoke tools" } # ==================== 健康检查 ==================== @app.get("/health") async def health_check(): """健康检查""" # 检查各个后端服务的健康状态 services_status = {} try: response = await HTTP_CLIENT.get(f"{ServiceEndpoints.NEWS_API}/search_news?query=test&top_k=1", timeout=5.0) services_status["news_api"] = "healthy" if response.status_code == 200 else "unhealthy" except: services_status["news_api"] = "unhealthy" try: response = await HTTP_CLIENT.get(f"{ServiceEndpoints.CONCEPT_API}/", timeout=5.0) services_status["concept_api"] = "healthy" if response.status_code == 200 else "unhealthy" except: services_status["concept_api"] = "unhealthy" try: response = await HTTP_CLIENT.get(f"{ServiceEndpoints.STOCK_ANALYSIS_API}/api/v1/health", timeout=5.0) services_status["stock_analysis_api"] = "healthy" if response.status_code == 200 else "unhealthy" except: services_status["stock_analysis_api"] = "unhealthy" return { "status": "healthy", "timestamp": datetime.now().isoformat(), "services": services_status } # ==================== 错误处理 ==================== @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """HTTP异常处理""" return JSONResponse( status_code=exc.status_code, content={ "success": False, "error": exc.detail, "timestamp": datetime.now().isoformat() } ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """通用异常处理""" logger.error(f"Unexpected error: {str(exc)}", exc_info=True) return JSONResponse( status_code=500, content={ "success": False, "error": "Internal server error", "detail": str(exc), "timestamp": datetime.now().isoformat() } ) # ==================== 应用启动/关闭 ==================== @app.on_event("startup") async def startup_event(): """应用启动""" logger.info("MCP Server starting up...") logger.info(f"Registered {len(TOOLS)} tools") # 初始化数据库连接池 try: await db.get_pool() logger.info("MySQL connection pool initialized") except Exception as e: logger.error(f"Failed to initialize MySQL pool: {e}") @app.on_event("shutdown") async def shutdown_event(): """应用关闭""" logger.info("MCP Server shutting down...") await HTTP_CLIENT.aclose() # 关闭数据库连接池 try: await db.close_pool() logger.info("MySQL connection pool closed") except Exception as e: logger.error(f"Failed to close MySQL pool: {e}") # ==================== 主程序 ==================== if __name__ == "__main__": import uvicorn uvicorn.run( "mcp_server:app", host="0.0.0.0", port=8900, reload=True, log_level="info" )