diff --git a/app/core/logger.py b/app/core/logger.py index 5fded40..cdbbb6d 100644 --- a/app/core/logger.py +++ b/app/core/logger.py @@ -7,6 +7,27 @@ from logging.handlers import RotatingFileHandler from app.core.config import setting +class MCPLogFilter(logging.Filter): + """MCP 日志过滤器 - 过滤掉包含大量数据的 DEBUG 日志""" + + def filter(self, record): + # 过滤掉包含原始字节数据的 SSE 日志 + if record.name == "sse_starlette.sse" and "chunk: b'" in record.getMessage(): + return False + + # 过滤掉 SSE 的一些冗余日志 + if record.name == "sse_starlette.sse" and record.levelno == logging.DEBUG: + msg = record.getMessage() + if any(x in msg for x in ["Got event:", "Closing", "chunk:"]): + return False + + # 过滤掉 MCP streamable_http 的一些 DEBUG 日志 + if "mcp.server.streamable_http" in record.name and record.levelno == logging.DEBUG: + return False + + return True + + class LoggerManager: """日志管理器""" @@ -35,10 +56,14 @@ class LoggerManager: # 创建格式器 formatter = logging.Formatter(log_format) + # 创建日志过滤器 + mcp_filter = MCPLogFilter() + # 控制台处理器 console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(log_level) console_handler.setFormatter(formatter) + console_handler.addFilter(mcp_filter) # 文件处理器 file_handler = RotatingFileHandler( @@ -46,6 +71,7 @@ class LoggerManager: ) file_handler.setLevel(log_level) file_handler.setFormatter(formatter) + file_handler.addFilter(mcp_filter) # 添加处理器到根日志器 self.logger.addHandler(console_handler) @@ -57,6 +83,10 @@ class LoggerManager: logging.getLogger("fastapi").setLevel(logging.INFO) logging.getLogger("aiomysql").setLevel(logging.WARNING) + # FastMCP 相关日志 - 关闭 + logging.getLogger("mcp").setLevel(logging.CRITICAL) + logging.getLogger("fastmcp").setLevel(logging.CRITICAL) + LoggerManager._initialized = True def debug(self, msg: str) -> None: diff --git a/app/mcp/__init__.py b/app/services/mcp/__init__.py similarity index 63% rename from app/mcp/__init__.py rename to app/services/mcp/__init__.py index bb72a95..6951deb 100644 --- a/app/mcp/__init__.py +++ b/app/services/mcp/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """MCP模块初始化""" -from app.mcp.server import mcp +from app.services.mcp.server import mcp __all__ = ["mcp"] diff --git a/app/mcp/server.py b/app/services/mcp/server.py similarity index 51% rename from app/mcp/server.py rename to app/services/mcp/server.py index 88ef690..38d7719 100644 --- a/app/mcp/server.py +++ b/app/services/mcp/server.py @@ -2,13 +2,39 @@ """FastMCP服务器实例""" from fastmcp import FastMCP -from app.mcp.tools import ask_grok_impl +from fastmcp.server.auth.providers.jwt import StaticTokenVerifier +from app.services.mcp.tools import ask_grok_impl +from app.core.config import setting -# 创建FastMCP实例 - 不在这里设置stateless_http -mcp = FastMCP( - name="Grok2API-MCP", - instructions="MCP server providing Grok AI chat capabilities. Use ask_grok tool to interact with Grok AI models." -) + +def create_mcp_server() -> FastMCP: + """创建MCP服务器实例,如果配置了API密钥则启用认证""" + # 检查是否配置了API密钥 + api_key = setting.grok_config.get("api_key") + + # 如果配置了API密钥,则启用静态token验证 + auth = None + if api_key: + auth = StaticTokenVerifier( + tokens={ + api_key: { + "client_id": "grok2api-client", + "scopes": ["read", "write", "admin"] + } + }, + required_scopes=["read"] + ) + + # 创建FastMCP实例 + return FastMCP( + name="Grok2API-MCP", + instructions="MCP server providing Grok AI chat capabilities. Use ask_grok tool to interact with Grok AI models.", + auth=auth + ) + + +# 创建全局MCP实例 +mcp = create_mcp_server() # 注册ask_grok工具 diff --git a/app/mcp/tools.py b/app/services/mcp/tools.py similarity index 93% rename from app/mcp/tools.py rename to app/services/mcp/tools.py index 460bfee..abedfae 100644 --- a/app/mcp/tools.py +++ b/app/services/mcp/tools.py @@ -38,7 +38,7 @@ async def ask_grok_impl( "stream": True } - logger.info(f"[MCP] ask_grok调用 - 模型:{model}, 问题长度:{len(query)}") + logger.info(f"[MCP] ask_grok 调用, 模型: {model}") # 调用Grok客户端(流式) response_iterator = await GrokClient.openai_to_grok(request_data) @@ -66,7 +66,7 @@ async def ask_grok_impl( continue result = "".join(content_parts) - logger.info(f"[MCP] ask_grok完成 - 响应长度:{len(result)}") + logger.info(f"[MCP] ask_grok 完成, 响应长度: {len(result)}") return result except GrokApiException as e: diff --git a/main.py b/main.py index 1b2429a..9bc39a1 100644 --- a/main.py +++ b/main.py @@ -13,11 +13,12 @@ from app.api.v1.models import router as models_router from app.api.v1.images import router as images_router from app.api.admin.manage import router as admin_router -# 导入MCP服务器 -from app.mcp import mcp +# 导入MCP服务器(认证配置在server.py中完成) +from app.services.mcp import mcp # 创建MCP的FastAPI应用实例 -mcp_app = mcp.http_app(stateless_http=True) +# 使用流式HTTP传输,支持高效的双向流式通信 +mcp_app = mcp.http_app(stateless_http=True, transport="streamable-http") # 2. 定义应用生命周期 @asynccontextmanager @@ -40,7 +41,7 @@ async def lifespan(app: FastAPI): setting.set_storage(storage) token_manager.set_storage(storage) - # 重新加载配置和token数据(从存储同步) + # 重新加载配置和token数据 await setting.reload() token_manager._load_data() logger.info("[Grok2API] 核心服务初始化完成") @@ -48,9 +49,9 @@ async def lifespan(app: FastAPI): # 2. 管理MCP服务的生命周期 mcp_lifespan_context = mcp_app.lifespan(app) await mcp_lifespan_context.__aenter__() - + logger.info("[MCP] MCP服务初始化完成") + logger.info("[Grok2API] 应用启动成功") - logger.info("[MCP] MCP服务端点: /mcp") try: yield @@ -71,8 +72,8 @@ logger.info("[Grok2API] 应用正在启动...") # 创建FastAPI应用 app = FastAPI( title="Grok2API", - description="Grok API 转换服务 (包含MCP端点)", - version="1.0.3", + description="Grok API 转换服务", + version="1.3.1", lifespan=lifespan ) @@ -85,12 +86,12 @@ app.include_router(models_router, prefix="/v1") app.include_router(images_router) app.include_router(admin_router) -# 挂载静态文件(注意:这个应该在API路由之后,避免拦截API请求) +# 挂载静态文件 app.mount("/static", StaticFiles(directory="app/template"), name="template") @app.get("/") async def root(): - """根路径 - 重定向到登录页""" + """根路径""" from fastapi.responses import RedirectResponse return RedirectResponse(url="/login") @@ -104,7 +105,7 @@ async def health_check(): "version": "1.0.3" } -# 挂载MCP服务器 (最后挂载,避免覆盖其他路由) +# 挂载MCP服务器 app.mount("", mcp_app) diff --git a/requirements.txt b/requirements.txt index ac45f06..ce2cb4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,4 @@ pydantic==2.12.2 aiofiles==25.1.0 aiomysql==0.2.0 redis==6.4.0 -fastmcp>=2.3.0 \ No newline at end of file +fastmcp==2.12.4 \ No newline at end of file