Skip to main content
Glama

Zhihu MCP Server

by chemany
main.py14.2 kB
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import logging import os from typing import Optional, Any, Dict # 修正导入路径,确保它们相对于项目结构是正确的 # 假设 main.py 位于 zhihu_mcp_server/ 目录下 # 如果 main.py 在 zhihu_mcp_server/zhihu_mcp_server/ 内部,则需要调整 from zhihu_mcp_server.auth import ZhihuAuth # <--- 没有点,使用 ZhihuAuth from zhihu_mcp_server.publisher import ZhihuPublisher, ArticleData as PublisherArticleData # 重命名导入的 ArticleData from zhihu_mcp_server.utils import setup_logging # 日志设置 setup_logging() logger = logging.getLogger(__name__) # Pydantic模型定义移到前面,并且在 publisher.py 中的 ArticleData 基础上扩展或保持一致 class ArticleDataApi(PublisherArticleData): # 继承自 publisher 中的 ArticleData # ArticleDataApi 现在拥有 title, content, image_paths, tags # 如果需要API特定字段,可以在这里添加,或者直接使用 PublisherArticleData # 为了接收来自客户端的 cover_image,我们在这里确保它存在 cover_image: Optional[str] = None app = FastAPI() # 全局实例 auth_manager_instance: Optional[ZhihuAuth] = None # Ensure it's Optional for robustness publisher_instance: Optional[ZhihuPublisher] = None def initialize_service_components(): """Initializes or re-initializes service components.""" # 修正文档字符串 global auth_manager_instance, publisher_instance logger.info("开始服务组件初始化...") if auth_manager_instance: logger.info("检测到现有AuthManager实例,正在关闭其WebDriver...") auth_manager_instance.close_driver() # Setting to None 필수, to allow re-creation auth_manager_instance = None try: # 假设 main.py 在项目的根目录 zhihu_mcp_server/ 下 project_root_dir = os.path.dirname(os.path.abspath(__file__)) # cookies.json 存储在 项目根目录/zhihu_cookies.json cookies_file_path = os.path.join(project_root_dir, 'zhihu_cookies.json') logger.info(f"AuthManager将使用Cookie文件路径: {cookies_file_path}") # 确保包含cookie文件的目录存在(如果您的cookie文件在子目录中,则需要此步骤) # 如果cookie文件直接在项目根目录,则下面这部分可以省略或调整 # cookie_parent_dir = os.path.dirname(cookies_file_path) # if not os.path.exists(cookie_parent_dir): # os.makedirs(cookie_parent_dir, exist_ok=True) # logger.info(f"为Cookie文件创建了父目录: {cookie_parent_dir}") auth_manager_instance = ZhihuAuth(cookies_file_path=cookies_file_path) publisher_instance = ZhihuPublisher(auth_manager=auth_manager_instance) logger.info("服务组件初始化成功。") # Initial check, can be light or full based on preference. # For startup, a light check is often sufficient. initial_login_status = auth_manager_instance.check_login_status(require_browser_session=False) if initial_login_status is True: logger.info("用户已登录 (通过启动时检查)。") elif initial_login_status is False: logger.warning("用户未登录 (通过启动时检查)。") else: # None logger.info("用户登录状态未知 (通过启动时轻量级检查)。") except Exception as e: logger.error(f"服务组件初始化过程中发生严重错误: {e}", exc_info=True) # Reset instances on failure to prevent using partially initialized objects auth_manager_instance = None publisher_instance = None # We might not want to raise HTTPException here, as it could stop the server from starting # Or, if these components are critical, then it's appropriate. # For now, let's log and allow server to start in a degraded state if this fails. # raise HTTPException(status_code=500, detail=f"关键服务初始化失败: {e}") @app.on_event("startup") async def startup_event(): logger.info("MCP服务器正在启动生命周期事件...") initialize_service_components() logger.info("MCP服务器已启动并完成组件初始化尝试。") # No longer trying to close driver immediately after startup here # Let the /health or other endpoints manage browser session if needed @app.on_event("shutdown") async def shutdown_event(): global auth_manager_instance logger.info("MCP服务器正在关闭生命周期事件...") if auth_manager_instance: logger.info("正在关闭AuthManager持有的WebDriver...") auth_manager_instance.close_driver() logger.info("MCP服务器已成功关闭。") class ReinitializeResponse(BaseModel): status: str message: str login_status: Optional[bool] # Can be True, False, or None details: Optional[Dict[str, Any]] = None class HealthResponse(BaseModel): status: str login_status: Optional[bool] message: Optional[str] = None service_initialized: bool @app.get("/zhihu-mcp-server/health", response_model=HealthResponse) async def health_check(): """轻量级健康检查,主要检查服务是否初始化以及缓存的登录状态。不启动浏览器。""" logger.info("收到 /health (轻量级) 健康检查请求...") global auth_manager_instance service_initialized = False login_s = None msg = "" if auth_manager_instance and publisher_instance: service_initialized = True try: login_s = auth_manager_instance.check_login_status(require_browser_session=False) if login_s is True: msg = "服务已初始化,缓存状态为已登录。" elif login_s is False: msg = "服务已初始化,缓存状态为未登录。" else: # None msg = "服务已初始化,缓存的登录状态未知或已过期。" except Exception as e: logger.error(f"/health 检查登录状态时出错: {e}", exc_info=True) msg = f"检查登录状态时出错: {e}" # login_s remains None or its last value else: msg = "核心服务组件 (AuthManager 或 Publisher) 未初始化。" return HealthResponse( status="ok" if service_initialized else "error", login_status=login_s, message=msg, service_initialized=service_initialized ) @app.get("/zhihu-mcp-server/reinitialize", response_model=ReinitializeResponse) async def reinitialize_service_endpoint(): """强制重新初始化服务组件,并执行完整的登录状态检查 (可能启动浏览器)。""" logger.info("收到 /reinitialize (强制刷新与检查) 请求...") try: initialize_service_components() login_s: Optional[bool] = None message = "服务实例已成功重新初始化。" details_dict = {} if auth_manager_instance: # This is a full check, will start browser if needed login_s = auth_manager_instance.check_login_status(require_browser_session=True) if login_s is True: message += " 用户已登录。" details_dict = auth_manager_instance.get_user_info() # Get user info if logged in elif login_s is False: message += " 用户未登录。可能需要手动登录。" else: # login_status is None, should not happen with require_browser_session=True unless error message += " 登录状态检查未能明确确定 (可能发生错误)。" else: message = "服务实例重新初始化后,AuthManager未能成功创建。" logger.error(message) # Return 503 as service is not usable return ReinitializeResponse(status="error", message=message, login_status=None, details={"error_detail": "AuthManager is None after init"}) return ReinitializeResponse(status="success", message=message, login_status=login_s, details=details_dict) except Exception as e: logger.error(f"服务重新初始化过程中发生未知错误: {e}", exc_info=True) return ReinitializeResponse(status="error", message=f"重新初始化过程失败: {e}", login_status=None, details={"error_detail": str(e)}) class BrowserControlResponse(BaseModel): status: str message: str @app.post("/zhihu-mcp-server/close_browser", response_model=BrowserControlResponse) async def close_browser_session(): """尝试关闭由AuthManager管理的任何活动的浏览器会话。""" logger.info("收到 /close_browser 请求...") global auth_manager_instance if auth_manager_instance: try: auth_manager_instance.close_driver() logger.info("通过API请求关闭浏览器会话成功。") return BrowserControlResponse(status="success", message="浏览器会话已尝试关闭。") except Exception as e: logger.error(f"关闭浏览器会话时发生错误: {e}", exc_info=True) return BrowserControlResponse(status="error", message=f"关闭浏览器时出错: {e}") else: logger.warning("/close_browser 请求,但AuthManager未初始化。") return BrowserControlResponse(status="error", message="AuthManager未初始化,无法关闭浏览器。") class ArticleResponse(BaseModel): status: str message: str data: Optional[dict] = None # Made data optional @app.post("/zhihu-mcp-server/create_article", response_model=ArticleResponse) async def create_article_endpoint(article_data: ArticleDataApi): global publisher_instance, auth_manager_instance logger.info(f"收到文章发布请求: 标题: {article_data.title}, 内容长度: {len(article_data.content or '')}, 图片数量: {len(article_data.image_paths or [])}, 封面图: {article_data.cover_image}, 标签: {article_data.tags}") if not auth_manager_instance or not publisher_instance: logger.error("发布文章:核心服务未初始化。") # Try to reinitialize once if critical components are missing try: logger.info("发布文章:尝试自动重新初始化核心服务...") initialize_service_components() if not auth_manager_instance or not publisher_instance: logger.error("发布文章:自动重新初始化后核心服务仍然不可用。") raise HTTPException(status_code=503, detail="核心服务在自动重新初始化后仍不可用。") except Exception as e_init: logger.error(f"发布文章:自动重新初始化核心服务失败: {e_init}", exc_info=True) raise HTTPException(status_code=503, detail=f"核心服务自动重新初始化失败: {e_init}") try: # Perform a full login check (will use browser if needed) before attempting to publish current_login_status = auth_manager_instance.check_login_status(require_browser_session=True) if current_login_status is not True: logger.error("发布文章:用户未登录或登录状态无法确认。") return ArticleResponse(status="error", message="用户未登录或登录状态无法确认,请先通过 /reinitialize 确保登录。", data={"success": False}) except Exception as e_check: logger.error(f"发布文章:检查登录状态时发生错误: {e_check}", exc_info=True) return ArticleResponse(status="error", message=f"检查登录状态时出错: {e_check}", data={"success": False}) try: result = publisher_instance.create_article( title=article_data.title, content=article_data.content, image_paths=article_data.image_paths, tags=article_data.tags, cover_image=article_data.cover_image ) if result.get("success"): logger.info(f"文章 '{article_data.title}' 发布成功。") return ArticleResponse(status="success", message="文章发布成功", data=result) else: logger.error(f"文章 '{article_data.title}' 发布失败: {result.get('message')}") return ArticleResponse(status="error", message=result.get("message", "文章发布失败,未知原因。"), data=result) except Exception as e_publish: logger.error(f"发布文章 '{article_data.title}' 过程中发生未处理的异常: {e_publish}", exc_info=True) return ArticleResponse(status="error", message=f"发布文章时发生服务器内部错误: {e_publish}", data={"success": False, "detail": str(e_publish)}) @app.get("/zhihu-mcp-server/status") # This can be deprecated in favor of /health async def get_status_deprecated(): logger.warning("接口 /zhihu-mcp-server/status 已被弃用,请使用 /zhihu-mcp-server/health。") global auth_manager_instance, publisher_instance # Added publisher_instance login_s = None # Default to None service_initialized_properly = False if auth_manager_instance and publisher_instance: # Using light check for this deprecated status endpoint login_s = auth_manager_instance.check_login_status(require_browser_session=False) service_initialized_properly = True return { "service_status": "running" if service_initialized_properly else "degraded_or_uninitialized", "login_status": login_s, "service_components_initialized": service_initialized_properly, "deprecation_warning": "This endpoint is deprecated. Please use /zhihu-mcp-server/health for lightweight status or /zhihu-mcp-server/reinitialize for a full check and refresh." } if __name__ == "__main__": import uvicorn logger.info("以直接脚本方式启动Uvicorn服务器 (主要用于开发测试)...") # 推荐从命令行运行: uvicorn zhihu_mcp_server.main:app --reload --port 8000 uvicorn.run("main:app", host="0.0.0.0", port=8005, reload=True) # reload=True 方便开发

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chemany/zhihu_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server