Files
moss-ai/app/backend-python/services/dida_mcp_service.py
雷雨 8635b84b2d init
2025-12-15 22:05:56 +08:00

373 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
滴答清单 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