373 lines
11 KiB
Python
373 lines
11 KiB
Python
"""
|
||
滴答清单 MCP 服务
|
||
封装滴答清单MCP服务的调用,供后端 API 和 Agent 使用
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import logging
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Optional, Dict, Any, List
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DidaMCPService:
|
||
"""滴答清单 MCP 服务类"""
|
||
|
||
def __init__(self):
|
||
"""初始化滴答清单 MCP 服务"""
|
||
# 计算 MCP 服务路径
|
||
# 当前文件: app/backend-python/services/dida_mcp_service.py
|
||
# 目标路径: mcp/didatodolist-mcp/main.py
|
||
current_dir = Path(__file__).parent # app/backend-python/services
|
||
backend_dir = current_dir.parent # app/backend-python
|
||
app_dir = backend_dir.parent # app
|
||
project_root = app_dir.parent # project root
|
||
self.mcp_path = project_root / "mcp" / "didatodolist-mcp" / "main.py"
|
||
|
||
logger.info(f"滴答清单 MCP 服务路径: {self.mcp_path}")
|
||
|
||
if not self.mcp_path.exists():
|
||
logger.warning(f"滴答清单 MCP 服务文件不存在: {self.mcp_path}")
|
||
|
||
# MCP 依赖检查
|
||
self.mcp_available = self._check_mcp_available()
|
||
|
||
def _check_mcp_available(self) -> bool:
|
||
"""检查 MCP 是否可用"""
|
||
# 1. 检查文件是否存在
|
||
if not self.mcp_path.exists():
|
||
logger.error(f"❌ 滴答清单 MCP 服务文件不存在: {self.mcp_path}")
|
||
return False
|
||
|
||
# 2. 检查依赖是否已安装
|
||
try:
|
||
from mcp import ClientSession, StdioServerParameters
|
||
from mcp.client.stdio import stdio_client
|
||
|
||
logger.info("✅ 滴答清单 MCP 依赖检查通过")
|
||
return True
|
||
except ImportError as e:
|
||
logger.error(f"❌ 滴答清单 MCP 依赖未安装: {e}")
|
||
return False
|
||
|
||
async def _call_mcp_tool(
|
||
self, tool_name: str, arguments: Dict[str, Any], env_vars: Optional[Dict[str, str]] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
调用滴答清单 MCP 工具
|
||
|
||
Args:
|
||
tool_name: 工具名称
|
||
arguments: 工具参数
|
||
env_vars: 环境变量(用于传递access_token等)
|
||
|
||
Returns:
|
||
工具返回的结果(JSON 格式),如果MCP不可用返回包含错误信息的字典
|
||
"""
|
||
# 预检查:MCP 是否可用
|
||
if not self.mcp_available:
|
||
if not self.mcp_path.exists():
|
||
logger.error(f"开发错误:滴答清单 MCP 服务文件不存在: {self.mcp_path}")
|
||
else:
|
||
logger.error("开发错误:滴答清单 MCP 依赖未安装(pip install fastmcp)")
|
||
|
||
return {"success": False, "message": "请先检查滴答清单MCP服务是否启动。"}
|
||
|
||
try:
|
||
from mcp import ClientSession, StdioServerParameters
|
||
from mcp.client.stdio import stdio_client
|
||
|
||
logger.info(f"✅ 调用滴答清单 MCP 工具: {tool_name}, 参数: {arguments}")
|
||
|
||
# 准备环境变量
|
||
env = os.environ.copy()
|
||
if env_vars:
|
||
env.update(env_vars)
|
||
|
||
# 创建 MCP 客户端
|
||
server_params = StdioServerParameters(
|
||
command="python",
|
||
args=[str(self.mcp_path)],
|
||
env=env,
|
||
)
|
||
|
||
async with stdio_client(server_params) as (read, write):
|
||
async with ClientSession(read, write) as session:
|
||
# 初始化
|
||
await session.initialize()
|
||
|
||
# 调用工具
|
||
result = await session.call_tool(tool_name, arguments=arguments)
|
||
|
||
# 解析结果
|
||
result_text = (
|
||
result.content[0].text if hasattr(result, "content") else str(result)
|
||
)
|
||
return json.loads(result_text)
|
||
|
||
except ImportError as e:
|
||
logger.error(f"开发错误:滴答清单 MCP 模块未安装: {e}")
|
||
return {"success": False, "message": "请先检查滴答清单MCP服务是否启动。"}
|
||
except Exception as e:
|
||
logger.error(f"滴答清单 MCP 服务调用失败: {e}", exc_info=True)
|
||
return {"success": False, "message": f"MCP服务调用失败: {str(e)}"}
|
||
|
||
def _call_mcp_tool_sync(
|
||
self, tool_name: str, arguments: Dict[str, Any], env_vars: Optional[Dict[str, str]] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
同步方式调用滴答清单 MCP 工具
|
||
|
||
Args:
|
||
tool_name: 工具名称
|
||
arguments: 工具参数
|
||
env_vars: 环境变量
|
||
|
||
Returns:
|
||
工具返回的结果(JSON 格式)
|
||
"""
|
||
try:
|
||
# 获取或创建事件循环
|
||
try:
|
||
loop = asyncio.get_event_loop()
|
||
except RuntimeError:
|
||
loop = asyncio.new_event_loop()
|
||
asyncio.set_event_loop(loop)
|
||
|
||
# 运行异步函数
|
||
return loop.run_until_complete(self._call_mcp_tool(tool_name, arguments, env_vars))
|
||
except Exception as e:
|
||
logger.error(f"同步调用滴答清单 MCP 工具失败: {e}")
|
||
return None
|
||
|
||
# ==================== 任务管理相关接口 ====================
|
||
|
||
async def create_task(
|
||
self, access_token: str, title: str, content: str = "", project_id: Optional[str] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
创建任务
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
title: 任务标题
|
||
content: 任务内容
|
||
project_id: 项目ID(可选)
|
||
|
||
Returns:
|
||
任务创建结果
|
||
"""
|
||
arguments = {"title": title}
|
||
if content:
|
||
arguments["content"] = content
|
||
if project_id:
|
||
arguments["project_id"] = project_id
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("create_task", arguments, env_vars)
|
||
|
||
async def get_tasks(
|
||
self, access_token: str, project_id: Optional[str] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取任务列表
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
project_id: 项目ID(可选,不传则获取所有任务)
|
||
|
||
Returns:
|
||
任务列表
|
||
"""
|
||
arguments = {}
|
||
if project_id:
|
||
arguments["project_id"] = project_id
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("get_tasks", arguments, env_vars)
|
||
|
||
async def update_task(
|
||
self, access_token: str, task_id: str, **kwargs
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
更新任务
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
task_id: 任务ID
|
||
**kwargs: 要更新的字段(title, content, status等)
|
||
|
||
Returns:
|
||
更新结果
|
||
"""
|
||
arguments = {"task_id": task_id}
|
||
arguments.update(kwargs)
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("update_task", arguments, env_vars)
|
||
|
||
async def delete_task(self, access_token: str, task_id: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
删除任务
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
task_id: 任务ID
|
||
|
||
Returns:
|
||
删除结果
|
||
"""
|
||
arguments = {"task_id": task_id}
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("delete_task", arguments, env_vars)
|
||
|
||
async def complete_task(self, access_token: str, task_id: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
完成任务
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
task_id: 任务ID
|
||
|
||
Returns:
|
||
完成结果
|
||
"""
|
||
arguments = {"task_id": task_id}
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("complete_task", arguments, env_vars)
|
||
|
||
# ==================== 项目管理相关接口 ====================
|
||
|
||
async def get_projects(self, access_token: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取项目列表
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
|
||
Returns:
|
||
项目列表
|
||
"""
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
return await self._call_mcp_tool("get_projects", {}, env_vars)
|
||
|
||
async def create_project(
|
||
self, access_token: str, name: str, color: Optional[str] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
创建项目
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
name: 项目名称
|
||
color: 项目颜色(可选)
|
||
|
||
Returns:
|
||
项目创建结果
|
||
"""
|
||
arguments = {"name": name}
|
||
if color:
|
||
arguments["color"] = color
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("create_project", arguments, env_vars)
|
||
|
||
async def update_project(
|
||
self, access_token: str, project_id: str, **kwargs
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
更新项目
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
project_id: 项目ID
|
||
**kwargs: 要更新的字段(name, color等)
|
||
|
||
Returns:
|
||
更新结果
|
||
"""
|
||
arguments = {"project_id": project_id}
|
||
arguments.update(kwargs)
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("update_project", arguments, env_vars)
|
||
|
||
async def delete_project(
|
||
self, access_token: str, project_id: str
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
删除项目
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
project_id: 项目ID
|
||
|
||
Returns:
|
||
删除结果
|
||
"""
|
||
arguments = {"project_id": project_id}
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("delete_project", arguments, env_vars)
|
||
|
||
# ==================== 标签管理相关接口 ====================
|
||
|
||
async def get_tags(self, access_token: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取标签列表
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
|
||
Returns:
|
||
标签列表
|
||
"""
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
return await self._call_mcp_tool("get_tags", {}, env_vars)
|
||
|
||
# ==================== 统计分析相关接口 ====================
|
||
|
||
async def get_task_statistics(
|
||
self, access_token: str, project_id: Optional[str] = None
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取任务统计
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
project_id: 项目ID(可选)
|
||
|
||
Returns:
|
||
任务统计结果
|
||
"""
|
||
arguments = {}
|
||
if project_id:
|
||
arguments["project_id"] = project_id
|
||
|
||
env_vars = {"DIDA_ACCESS_TOKEN": access_token}
|
||
|
||
return await self._call_mcp_tool("get_task_statistics", arguments, env_vars)
|
||
|
||
|
||
# 全局实例
|
||
_dida_mcp_service = None
|
||
|
||
|
||
def get_dida_mcp_service() -> DidaMCPService:
|
||
"""获取滴答清单 MCP 服务实例(单例模式)"""
|
||
global _dida_mcp_service
|
||
if _dida_mcp_service is None:
|
||
_dida_mcp_service = DidaMCPService()
|
||
return _dida_mcp_service
|
||
|