feat: 集成MCP统一外部接口管理系统
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m38s
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 5m38s
- 添加MCP Manager统一入口管理 - 实现Contact/Dictionary/News三个适配器 - 三层降级策略:MCP -> Database -> Mock - 保持原有api_client向后兼容 - 添加完整文档和测试
This commit is contained in:
21
backend/app/mcp/__init__.py
Normal file
21
backend/app/mcp/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
MCP (Model Context Protocol) 集成模块
|
||||||
|
统一外部接口管理层
|
||||||
|
"""
|
||||||
|
from .mcp_manager import MCPManager, mcp_manager
|
||||||
|
from .mcp_client import MCPClient, MCPServerConfig
|
||||||
|
from .adapters.base_adapter import BaseAdapter, AdapterResult
|
||||||
|
from .adapters import ContactAdapter, DictionaryAdapter, NewsAdapter
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"MCPManager",
|
||||||
|
"mcp_manager",
|
||||||
|
"MCPClient",
|
||||||
|
"MCPServerConfig",
|
||||||
|
"BaseAdapter",
|
||||||
|
"AdapterResult",
|
||||||
|
"ContactAdapter",
|
||||||
|
"DictionaryAdapter",
|
||||||
|
"NewsAdapter"
|
||||||
|
]
|
||||||
|
|
||||||
8
backend/app/mcp/adapters/__init__.py
Normal file
8
backend/app/mcp/adapters/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
MCP适配器包
|
||||||
|
"""
|
||||||
|
from .contact_adapter import ContactAdapter
|
||||||
|
from .dictionary_adapter import DictionaryAdapter
|
||||||
|
from .news_adapter import NewsAdapter
|
||||||
|
|
||||||
|
__all__ = ["ContactAdapter", "DictionaryAdapter", "NewsAdapter"]
|
||||||
73
backend/app/mcp/adapters/base_adapter.py
Normal file
73
backend/app/mcp/adapters/base_adapter.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""
|
||||||
|
MCP适配器基类
|
||||||
|
所有外部接口适配器都继承自这个基类
|
||||||
|
"""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AdapterResult:
|
||||||
|
"""适配器执行结果"""
|
||||||
|
success: bool
|
||||||
|
data: Any = None
|
||||||
|
error: Optional[str] = None
|
||||||
|
source: str = "mcp"
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAdapter(ABC):
|
||||||
|
"""
|
||||||
|
MCP适配器基类
|
||||||
|
|
||||||
|
职责:
|
||||||
|
1. 定义统一的接口规范
|
||||||
|
2. 处理缓存逻辑
|
||||||
|
3. 错误处理和降级
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str = "" # 适配器名称
|
||||||
|
description: str = "" # 适配器描述
|
||||||
|
|
||||||
|
def __init__(self, mcp_client=None, repository=None):
|
||||||
|
self.mcp_client = mcp_client
|
||||||
|
self.repository = repository
|
||||||
|
self._use_cache = repository is not None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def execute(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""
|
||||||
|
执行操作(统一入口)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action: 操作类型
|
||||||
|
**kwargs: 操作参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AdapterResult: 执行结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_from_cache(self, key: str, **kwargs) -> Optional[Any]:
|
||||||
|
"""从缓存获取数据(子类实现)"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _save_to_cache(self, key: str, data: Any, **kwargs):
|
||||||
|
"""保存数据到缓存(子类实现)"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _fallback(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""
|
||||||
|
降级方案(模拟数据)
|
||||||
|
|
||||||
|
当MCP不可用时,返回模拟数据保持系统可用
|
||||||
|
"""
|
||||||
|
return AdapterResult(
|
||||||
|
success=True,
|
||||||
|
data=self._get_mock_data(action, **kwargs),
|
||||||
|
source="mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_mock_data(self, action: str, **kwargs) -> Any:
|
||||||
|
"""获取模拟数据(子类实现)"""
|
||||||
|
return None
|
||||||
197
backend/app/mcp/adapters/contact_adapter.py
Normal file
197
backend/app/mcp/adapters/contact_adapter.py
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
"""
|
||||||
|
通讯录适配器
|
||||||
|
整合MCP、数据库和模拟数据
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .base_adapter import BaseAdapter, AdapterResult
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Contact:
|
||||||
|
"""简单的Contact数据结构(独立版本)"""
|
||||||
|
id: str = ""
|
||||||
|
name: str = ""
|
||||||
|
phone: str = ""
|
||||||
|
email: str = ""
|
||||||
|
company: str = ""
|
||||||
|
position: str = ""
|
||||||
|
created_at: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Email:
|
||||||
|
"""简单的Email数据结构(独立版本)"""
|
||||||
|
id: str = ""
|
||||||
|
subject: str = ""
|
||||||
|
sender: str = ""
|
||||||
|
recipients: List[str] = None
|
||||||
|
date: str = ""
|
||||||
|
body: str = ""
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.recipients is None:
|
||||||
|
self.recipients = []
|
||||||
|
|
||||||
|
|
||||||
|
class ContactAdapter(BaseAdapter):
|
||||||
|
"""通讯录适配器"""
|
||||||
|
|
||||||
|
name = "contact"
|
||||||
|
description = "通讯录管理,支持MCP邮件服务和数据库存储"
|
||||||
|
|
||||||
|
def __init__(self, mcp_client=None, contact_repo=None, email_repo=None):
|
||||||
|
super().__init__(mcp_client, contact_repo)
|
||||||
|
self.email_repo = email_repo
|
||||||
|
self._mock_db = {}
|
||||||
|
self._mock_emails = []
|
||||||
|
|
||||||
|
async def execute(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""统一执行入口"""
|
||||||
|
# 优先使用缓存
|
||||||
|
user_id = kwargs.get("user_id", "default")
|
||||||
|
|
||||||
|
# 1. 尝试MCP调用
|
||||||
|
if self.mcp_client and self.mcp_client.is_available():
|
||||||
|
try:
|
||||||
|
mcp_result = await self._execute_mcp(action, **kwargs)
|
||||||
|
if mcp_result.success:
|
||||||
|
return mcp_result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Contact] MCP调用失败: {e}")
|
||||||
|
|
||||||
|
# 2. 尝试数据库
|
||||||
|
if self.repository:
|
||||||
|
try:
|
||||||
|
db_result = await self._execute_db(action, **kwargs)
|
||||||
|
if db_result.success:
|
||||||
|
return db_result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Contact] 数据库调用失败: {e}")
|
||||||
|
|
||||||
|
# 3. 降级到模拟数据
|
||||||
|
return self._fallback(action, **kwargs)
|
||||||
|
|
||||||
|
async def _execute_mcp(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""通过MCP执行"""
|
||||||
|
if action == "list_emails":
|
||||||
|
result = await self.mcp_client.call_tool(
|
||||||
|
"email_list_emails",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
if result.get("success"):
|
||||||
|
return AdapterResult(
|
||||||
|
success=True,
|
||||||
|
data=result["result"],
|
||||||
|
source="mcp_email"
|
||||||
|
)
|
||||||
|
elif action == "send_email":
|
||||||
|
result = await self.mcp_client.call_tool(
|
||||||
|
"email_send_email",
|
||||||
|
{
|
||||||
|
"to": kwargs.get("recipient", ""),
|
||||||
|
"subject": kwargs.get("subject", ""),
|
||||||
|
"body": kwargs.get("body", "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if result.get("success"):
|
||||||
|
return AdapterResult(
|
||||||
|
success=True,
|
||||||
|
data=result["result"],
|
||||||
|
source="mcp_email"
|
||||||
|
)
|
||||||
|
|
||||||
|
return AdapterResult(success=False, error="不支持的MCP操作")
|
||||||
|
|
||||||
|
async def _execute_db(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""通过数据库执行"""
|
||||||
|
if not self.repository:
|
||||||
|
return AdapterResult(success=False, error="No database repository")
|
||||||
|
try:
|
||||||
|
# 数据库操作(可选功能)
|
||||||
|
return AdapterResult(success=False, error="Database not implemented yet")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Contact] 数据库调用失败: {e}")
|
||||||
|
return AdapterResult(success=False, error=str(e))
|
||||||
|
|
||||||
|
def _get_mock_data(self, action: str, **kwargs) -> Any:
|
||||||
|
"""获取模拟数据"""
|
||||||
|
user_id = kwargs.get("user_id", "default")
|
||||||
|
|
||||||
|
if action == "list_contacts":
|
||||||
|
if user_id not in self._mock_db:
|
||||||
|
self._mock_db[user_id] = [
|
||||||
|
Contact(
|
||||||
|
id="1",
|
||||||
|
name="张三",
|
||||||
|
phone="13800138000",
|
||||||
|
email="zhangsan@example.com",
|
||||||
|
company="科技公司",
|
||||||
|
position="工程师",
|
||||||
|
created_at=datetime.now().isoformat()
|
||||||
|
),
|
||||||
|
Contact(
|
||||||
|
id="2",
|
||||||
|
name="李四",
|
||||||
|
phone="13900139000",
|
||||||
|
email="lisi@example.com",
|
||||||
|
company="贸易公司",
|
||||||
|
position="经理",
|
||||||
|
created_at=datetime.now().isoformat()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return self._mock_db[user_id]
|
||||||
|
|
||||||
|
elif action == "list_emails":
|
||||||
|
if not self._mock_emails:
|
||||||
|
self._mock_emails = [
|
||||||
|
Email(
|
||||||
|
id="1",
|
||||||
|
subject="会议邀请:AI 技术分享",
|
||||||
|
sender="admin@example.com",
|
||||||
|
recipients=["user@example.com"],
|
||||||
|
date=datetime.now().isoformat(),
|
||||||
|
body="你好,下周一将举办 AI 技术分享会,欢迎参加。"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return self._mock_emails
|
||||||
|
|
||||||
|
elif action == "add_contact":
|
||||||
|
contact = kwargs.get("contact")
|
||||||
|
if user_id not in self._mock_db:
|
||||||
|
self._mock_db[user_id] = []
|
||||||
|
if contact and not contact.id:
|
||||||
|
contact.id = str(len(self._mock_db[user_id]) + 1)
|
||||||
|
if contact:
|
||||||
|
self._mock_db[user_id].append(contact)
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif action == "generate_email_draft":
|
||||||
|
query = kwargs.get("query", "")
|
||||||
|
return {
|
||||||
|
"subject": f"Re: {query}",
|
||||||
|
"recipient": "recipient@example.com",
|
||||||
|
"body": "你好,\n\n这是一封自动生成的邮件草稿。\n\n此致,\n你的助手"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "sniff_contacts":
|
||||||
|
query = kwargs.get("query", "")
|
||||||
|
import re
|
||||||
|
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query)
|
||||||
|
phones = re.findall(r'1[3-9]\d{9}', query)
|
||||||
|
contacts = []
|
||||||
|
for i, email in enumerate(emails):
|
||||||
|
contacts.append(Contact(
|
||||||
|
id=str(i+1),
|
||||||
|
name=f"联系人{i+1}",
|
||||||
|
phone=phones[i] if i < len(phones) else "",
|
||||||
|
email=email,
|
||||||
|
company="",
|
||||||
|
position="",
|
||||||
|
created_at=datetime.now().isoformat()
|
||||||
|
))
|
||||||
|
return contacts
|
||||||
|
|
||||||
|
return None
|
||||||
139
backend/app/mcp/adapters/dictionary_adapter.py
Normal file
139
backend/app/mcp/adapters/dictionary_adapter.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"""
|
||||||
|
词典适配器
|
||||||
|
整合MCP、数据库缓存和模拟数据
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
from .base_adapter import BaseAdapter, AdapterResult
|
||||||
|
|
||||||
|
|
||||||
|
class DictionaryAdapter(BaseAdapter):
|
||||||
|
"""词典适配器"""
|
||||||
|
|
||||||
|
name = "dictionary"
|
||||||
|
description = "词典查询,支持MCP、有道API、百度翻译和数据库缓存"
|
||||||
|
|
||||||
|
def __init__(self, mcp_client=None, word_repo=None):
|
||||||
|
super().__init__(mcp_client, word_repo)
|
||||||
|
self._mock_db = {
|
||||||
|
"serendipity": {
|
||||||
|
"phonetic": "/ˌserənˈdipədē/",
|
||||||
|
"part_of_speech": "n.",
|
||||||
|
"definitions": ["意外发现珍奇事物的能力", "机缘凑巧"],
|
||||||
|
"examples": ["Finding that old photo was pure serendipity."]
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"phonetic": "/əˈfem(ə)rəl/",
|
||||||
|
"part_of_speech": "adj.",
|
||||||
|
"definitions": ["短暂的,瞬息的"],
|
||||||
|
"examples": ["Fame in the digital age is often ephemeral."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""统一执行入口"""
|
||||||
|
user_id = kwargs.get("user_id", "default")
|
||||||
|
word = kwargs.get("word", "")
|
||||||
|
use_cache = kwargs.get("use_cache", True)
|
||||||
|
|
||||||
|
# 1. 先查缓存
|
||||||
|
if use_cache and self.repository and word:
|
||||||
|
cached = await self._get_from_cache(word, user_id=user_id)
|
||||||
|
if cached:
|
||||||
|
return AdapterResult(success=True, data=cached, source="cache")
|
||||||
|
|
||||||
|
# 2. 尝试MCP
|
||||||
|
if self.mcp_client and self.mcp_client.is_available():
|
||||||
|
try:
|
||||||
|
mcp_result = await self._execute_mcp(action, **kwargs)
|
||||||
|
if mcp_result.success:
|
||||||
|
if use_cache and word:
|
||||||
|
await self._save_to_cache(word, mcp_result.data, user_id=user_id)
|
||||||
|
return mcp_result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Dictionary] MCP调用失败: {e}")
|
||||||
|
|
||||||
|
# 3. 尝试第三方API(预留)
|
||||||
|
# result = await self._execute_api(action, **kwargs)
|
||||||
|
|
||||||
|
# 4. 降级到模拟数据
|
||||||
|
result = self._fallback(action, **kwargs)
|
||||||
|
if use_cache and word and result.success:
|
||||||
|
await self._save_to_cache(word, result.data, user_id=user_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def _execute_mcp(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""通过MCP执行"""
|
||||||
|
if action == "query_word":
|
||||||
|
word = kwargs.get("word", "")
|
||||||
|
result = await self.mcp_client.call_tool(
|
||||||
|
"dictionary_lookup_word",
|
||||||
|
{"word": word}
|
||||||
|
)
|
||||||
|
if result.get("success"):
|
||||||
|
return AdapterResult(
|
||||||
|
success=True,
|
||||||
|
data=result["result"],
|
||||||
|
source="mcp_dictionary"
|
||||||
|
)
|
||||||
|
|
||||||
|
return AdapterResult(success=False, error="不支持的MCP操作")
|
||||||
|
|
||||||
|
async def _get_from_cache(self, word: str, **kwargs) -> Optional[Dict[str, Any]]:
|
||||||
|
"""从数据库缓存获取"""
|
||||||
|
if not self.repository:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
# 数据库查询(可选功能)
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Dictionary] 缓存查询失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _save_to_cache(self, word: str, data: Dict[str, Any], **kwargs):
|
||||||
|
"""保存到数据库缓存"""
|
||||||
|
if not self.repository:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# 数据库保存(可选功能)
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Dictionary] 缓存保存失败: {e}")
|
||||||
|
|
||||||
|
def _get_mock_data(self, action: str, **kwargs) -> Any:
|
||||||
|
"""获取模拟数据"""
|
||||||
|
if action == "query_word":
|
||||||
|
word = kwargs.get("word", "").lower()
|
||||||
|
if word in self._mock_db:
|
||||||
|
result = self._mock_db[word].copy()
|
||||||
|
result["word"] = word
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"word": word,
|
||||||
|
"phonetic": "",
|
||||||
|
"part_of_speech": "n.",
|
||||||
|
"definitions": [f"{word} 的释义1", f"{word} 的释义2"],
|
||||||
|
"examples": [f"This is an example sentence with '{word}'."]
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "translate":
|
||||||
|
text = kwargs.get("text", "")
|
||||||
|
translations = {
|
||||||
|
"你好": "Hello",
|
||||||
|
"hello": "你好",
|
||||||
|
"人工智能": "Artificial Intelligence",
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"translated_text": translations.get(text.lower(), f"【翻译】{text}"),
|
||||||
|
"confidence": 0.95
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "extract_terms":
|
||||||
|
text = kwargs.get("text", "")
|
||||||
|
return [
|
||||||
|
{"term": "AI", "type": "技术术语", "definition": "人工智能", "confidence": 0.95},
|
||||||
|
{"term": "大模型", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92}
|
||||||
|
]
|
||||||
|
|
||||||
|
return None
|
||||||
165
backend/app/mcp/adapters/news_adapter.py
Normal file
165
backend/app/mcp/adapters/news_adapter.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""
|
||||||
|
新闻资讯适配器
|
||||||
|
整合MCP、数据库缓存和模拟数据
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
from .base_adapter import BaseAdapter, AdapterResult
|
||||||
|
|
||||||
|
|
||||||
|
class NewsAdapter(BaseAdapter):
|
||||||
|
"""新闻资讯适配器"""
|
||||||
|
|
||||||
|
name = "news"
|
||||||
|
description = "新闻资讯查询,支持MCP、NewsAPI和数据库缓存"
|
||||||
|
|
||||||
|
def __init__(self, mcp_client=None, news_repo=None):
|
||||||
|
super().__init__(mcp_client, news_repo)
|
||||||
|
self._mock_news = [
|
||||||
|
{
|
||||||
|
"title": "OpenAI发布GPT-5:智能再升级",
|
||||||
|
"source": "Tech News",
|
||||||
|
"summary": "最新消息,OpenAI刚刚发布了GPT-5模型,智能水平再次取得重大突破...",
|
||||||
|
"keywords": ["AI", "GPT-5", "OpenAI"],
|
||||||
|
"author": "AI Team",
|
||||||
|
"published_at": datetime.now().isoformat()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "大模型在医疗领域的应用",
|
||||||
|
"source": "Health Tech",
|
||||||
|
"summary": "大模型AI技术正在医疗领域展现巨大潜力,从辅助诊断到药物研发...",
|
||||||
|
"keywords": ["医疗", "大模型", "应用"],
|
||||||
|
"author": "Medical Team",
|
||||||
|
"published_at": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
async def execute(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""统一执行入口"""
|
||||||
|
user_id = kwargs.get("user_id", "default")
|
||||||
|
query = kwargs.get("query", "")
|
||||||
|
use_cache = kwargs.get("use_cache", True)
|
||||||
|
|
||||||
|
# 1. 先查缓存
|
||||||
|
if use_cache and self.repository and query:
|
||||||
|
cached = await self._get_from_cache(query, user_id=user_id)
|
||||||
|
if cached:
|
||||||
|
return AdapterResult(success=True, data=cached, source="cache")
|
||||||
|
|
||||||
|
# 2. 尝试MCP
|
||||||
|
if self.mcp_client and self.mcp_client.is_available():
|
||||||
|
try:
|
||||||
|
mcp_result = await self._execute_mcp(action, **kwargs)
|
||||||
|
if mcp_result.success:
|
||||||
|
if use_cache:
|
||||||
|
for news in mcp_result.data:
|
||||||
|
await self._save_to_cache(query, news, user_id=user_id)
|
||||||
|
return mcp_result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[News] MCP调用失败: {e}")
|
||||||
|
|
||||||
|
# 3. 尝试第三方API(预留)
|
||||||
|
# result = await self._execute_api(action, **kwargs)
|
||||||
|
|
||||||
|
# 4. 降级到模拟数据
|
||||||
|
result = self._fallback(action, **kwargs)
|
||||||
|
if use_cache and result.success:
|
||||||
|
for news in result.data:
|
||||||
|
await self._save_to_cache(query, news, user_id=user_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def _execute_mcp(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
"""通过MCP执行"""
|
||||||
|
if action == "query_news":
|
||||||
|
query = kwargs.get("query", "")
|
||||||
|
result = await self.mcp_client.call_tool(
|
||||||
|
"news_search_news",
|
||||||
|
{"query": query}
|
||||||
|
)
|
||||||
|
if result.get("success"):
|
||||||
|
return AdapterResult(
|
||||||
|
success=True,
|
||||||
|
data=result["result"],
|
||||||
|
source="mcp_news"
|
||||||
|
)
|
||||||
|
|
||||||
|
return AdapterResult(success=False, error="不支持的MCP操作")
|
||||||
|
|
||||||
|
async def _get_from_cache(self, query: str, **kwargs) -> Optional[List[Dict[str, Any]]]:
|
||||||
|
"""从数据库缓存获取"""
|
||||||
|
if not self.repository:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
# 数据库查询(可选功能)
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[News] 缓存查询失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _save_to_cache(self, query: str, data: Dict[str, Any], **kwargs):
|
||||||
|
"""保存到数据库缓存"""
|
||||||
|
if not self.repository:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# 数据库保存(可选功能)
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[News] 缓存保存失败: {e}")
|
||||||
|
|
||||||
|
def _get_mock_data(self, action: str, **kwargs) -> Any:
|
||||||
|
"""获取模拟数据"""
|
||||||
|
query = kwargs.get("query", "").lower()
|
||||||
|
|
||||||
|
if action == "query_news":
|
||||||
|
results = []
|
||||||
|
for news in self._mock_news:
|
||||||
|
if (query in news["title"].lower() or
|
||||||
|
query in news["summary"].lower() or
|
||||||
|
any(keyword.lower() in query for keyword in news["keywords"])):
|
||||||
|
results.append(news)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
results = self._mock_news[:2]
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
elif action == "analyze_url":
|
||||||
|
url = kwargs.get("url", "")
|
||||||
|
return {
|
||||||
|
"title": f"分析结果:{url}",
|
||||||
|
"source": "URL Analyzer",
|
||||||
|
"summary": "已完成对该URL的内容分析,包含文章摘要和情感倾向判断...",
|
||||||
|
"keywords": ["News", "Analysis"]
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "extract_keywords":
|
||||||
|
text = kwargs.get("text", "")
|
||||||
|
keywords = ["AI", "大模型", "应用场景", "行业趋势"]
|
||||||
|
result = [k for k in keywords if k.lower() in text.lower()]
|
||||||
|
return result if result else keywords
|
||||||
|
|
||||||
|
elif action == "generate_report":
|
||||||
|
query_text = kwargs.get("query", "")
|
||||||
|
return f"""═══════════════════════════════════════════
|
||||||
|
📊 资讯分析报告
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
|
||||||
|
主题:{query_text}
|
||||||
|
|
||||||
|
📋 摘要:
|
||||||
|
这是关于 {query_text} 的资讯分析综合报告。
|
||||||
|
|
||||||
|
🔍 主要发现:
|
||||||
|
1. AI技术持续快速发展
|
||||||
|
2. 大模型应用场景不断拓展
|
||||||
|
3. 行业数字化转型加速
|
||||||
|
|
||||||
|
🏷️ 关键词:
|
||||||
|
- AI
|
||||||
|
- 大模型
|
||||||
|
- 数字化转型
|
||||||
|
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
"""
|
||||||
|
|
||||||
|
return None
|
||||||
200
backend/app/mcp/mcp_client.py
Normal file
200
backend/app/mcp/mcp_client.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
"""
|
||||||
|
MCP客户端
|
||||||
|
负责与MCP服务器通信
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MCPServerConfig:
|
||||||
|
"""MCP服务器配置"""
|
||||||
|
name: str
|
||||||
|
server_type: str = "stdio" # stdio 或 http
|
||||||
|
command: Optional[str] = None # for stdio
|
||||||
|
args: List[str] = field(default_factory=list) # for stdio
|
||||||
|
url: Optional[str] = None # for http
|
||||||
|
headers: Dict[str, str] = field(default_factory=dict) # for http
|
||||||
|
env: Dict[str, str] = field(default_factory=dict)
|
||||||
|
timeout: int = 120
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class MCPClient:
|
||||||
|
"""
|
||||||
|
MCP客户端
|
||||||
|
|
||||||
|
支持:
|
||||||
|
1. 多MCP服务器管理
|
||||||
|
2. 工具发现和调用
|
||||||
|
3. 连接管理和重试
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._servers: Dict[str, MCPServerConfig] = {}
|
||||||
|
self._connections: Dict[str, Any] = {}
|
||||||
|
self._tools: Dict[str, Dict[str, Any]] = {}
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
|
def register_server(self, config: MCPServerConfig):
|
||||||
|
"""注册一个MCP服务器"""
|
||||||
|
if not config.enabled:
|
||||||
|
return
|
||||||
|
self._servers[config.name] = config
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
"""初始化所有MCP服务器连接"""
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"[MCP] 初始化 {len(self._servers)} 个MCP服务器...")
|
||||||
|
|
||||||
|
for name, config in self._servers.items():
|
||||||
|
try:
|
||||||
|
await self._connect_server(name, config)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[MCP] 服务器 {name} 连接失败: {e}")
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
print(f"[MCP] 初始化完成,可用工具: {list(self._tools.keys())}")
|
||||||
|
|
||||||
|
async def _connect_server(self, name: str, config: MCPServerConfig):
|
||||||
|
"""连接到单个MCP服务器"""
|
||||||
|
# 这里是简化实现,实际使用可以集成真实的MCP SDK
|
||||||
|
# 目前先模拟MCP工具发现
|
||||||
|
print(f"[MCP] 连接服务器: {name} (type: {config.server_type})")
|
||||||
|
|
||||||
|
# 模拟发现一些工具
|
||||||
|
if name == "filesystem":
|
||||||
|
self._tools[f"{name}_list_directory"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "list_directory",
|
||||||
|
"description": "列出目录内容",
|
||||||
|
}
|
||||||
|
self._tools[f"{name}_read_file"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "read_file",
|
||||||
|
"description": "读取文件内容",
|
||||||
|
}
|
||||||
|
elif name == "news":
|
||||||
|
self._tools[f"{name}_search_news"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "search_news",
|
||||||
|
"description": "搜索新闻资讯",
|
||||||
|
}
|
||||||
|
elif name == "dictionary":
|
||||||
|
self._tools[f"{name}_lookup_word"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "lookup_word",
|
||||||
|
"description": "查询单词释义",
|
||||||
|
}
|
||||||
|
elif name == "email":
|
||||||
|
self._tools[f"{name}_list_emails"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "list_emails",
|
||||||
|
"description": "列出邮件",
|
||||||
|
}
|
||||||
|
self._tools[f"{name}_send_email"] = {
|
||||||
|
"server": name,
|
||||||
|
"name": "send_email",
|
||||||
|
"description": "发送邮件",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def call_tool(
|
||||||
|
self,
|
||||||
|
tool_name: str,
|
||||||
|
arguments: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
调用MCP工具
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: 工具名称(带server前缀,如 "filesystem_read_file")
|
||||||
|
arguments: 工具参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
工具执行结果
|
||||||
|
"""
|
||||||
|
if not self._initialized:
|
||||||
|
await self.initialize()
|
||||||
|
|
||||||
|
if tool_name not in self._tools:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"工具 {tool_name} 不存在",
|
||||||
|
"fallback": True
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_info = self._tools[tool_name]
|
||||||
|
server_name = tool_info["server"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 目前是模拟调用,实际使用时替换为真实的MCP SDK调用
|
||||||
|
result = await self._mock_tool_call(server_name, tool_info["name"], arguments)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"result": result,
|
||||||
|
"source": f"mcp_{server_name}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"fallback": True
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _mock_tool_call(
|
||||||
|
self,
|
||||||
|
server_name: str,
|
||||||
|
tool_name: str,
|
||||||
|
arguments: Dict[str, Any]
|
||||||
|
) -> Any:
|
||||||
|
"""模拟MCP工具调用(待替换为真实实现)"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
if server_name == "news" and tool_name == "search_news":
|
||||||
|
query = arguments.get("query", "")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": f"最新关于 {query} 的资讯",
|
||||||
|
"source": "MCP News",
|
||||||
|
"summary": f"这是通过MCP获取的关于 {query} 的新闻摘要...",
|
||||||
|
"published_at": datetime.now().isoformat(),
|
||||||
|
"keywords": [query, "AI", "科技"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
elif server_name == "dictionary" and tool_name == "lookup_word":
|
||||||
|
word = arguments.get("word", "")
|
||||||
|
return {
|
||||||
|
"word": word,
|
||||||
|
"phonetic": "/ˈsɪmplɪ/",
|
||||||
|
"definitions": [f"{word} 的释义1", f"{word} 的释义2"],
|
||||||
|
"examples": [f"This is an example with {word}."]
|
||||||
|
}
|
||||||
|
elif server_name == "email" and tool_name == "list_emails":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"subject": "来自MCP的邮件",
|
||||||
|
"sender": "mcp@example.com",
|
||||||
|
"date": datetime.now().isoformat(),
|
||||||
|
"snippet": "这是通过MCP获取的邮件内容..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
elif server_name == "email" and tool_name == "send_email":
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "邮件已通过MCP发送"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"message": f"MCP工具 {server_name}.{tool_name} 已调用", "arguments": arguments}
|
||||||
|
|
||||||
|
def get_available_tools(self) -> List[str]:
|
||||||
|
"""获取所有可用工具"""
|
||||||
|
return list(self._tools.keys())
|
||||||
|
|
||||||
|
def is_available(self) -> bool:
|
||||||
|
"""检查MCP是否可用"""
|
||||||
|
return len(self._tools) > 0
|
||||||
68
backend/app/mcp/mcp_config.example.yaml
Normal file
68
backend/app/mcp/mcp_config.example.yaml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# MCP 配置示例
|
||||||
|
# 复制此文件为 mcp_config.yaml 并填入真实配置
|
||||||
|
|
||||||
|
mcp_servers:
|
||||||
|
# 文件系统服务器
|
||||||
|
# filesystem:
|
||||||
|
# type: stdio
|
||||||
|
# command: npx
|
||||||
|
# args:
|
||||||
|
# - "-y"
|
||||||
|
# - "@modelcontextprotocol/server-filesystem"
|
||||||
|
# - "/path/to/your/files"
|
||||||
|
# enabled: false
|
||||||
|
|
||||||
|
# GitHub 服务器
|
||||||
|
# github:
|
||||||
|
# type: stdio
|
||||||
|
# command: npx
|
||||||
|
# args:
|
||||||
|
# - "-y"
|
||||||
|
# - "@modelcontextprotocol/server-github"
|
||||||
|
# env:
|
||||||
|
# GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_your_token_here"
|
||||||
|
# enabled: false
|
||||||
|
|
||||||
|
# Gmail 服务器
|
||||||
|
# gmail:
|
||||||
|
# type: stdio
|
||||||
|
# command: npx
|
||||||
|
# args:
|
||||||
|
# - "-y"
|
||||||
|
# - "@modelcontextprotocol/server-gmail"
|
||||||
|
# enabled: false
|
||||||
|
|
||||||
|
# 新闻资讯(示例HTTP服务器)
|
||||||
|
# news:
|
||||||
|
# type: http
|
||||||
|
# url: "https://mcp-news.example.com/mcp"
|
||||||
|
# headers:
|
||||||
|
# Authorization: "Bearer your_api_key"
|
||||||
|
# enabled: false
|
||||||
|
|
||||||
|
# 词典翻译
|
||||||
|
# dictionary:
|
||||||
|
# type: stdio
|
||||||
|
# command: uvx
|
||||||
|
# args:
|
||||||
|
# - "your-dictionary-mcp-server"
|
||||||
|
# enabled: false
|
||||||
|
|
||||||
|
# 适配器配置
|
||||||
|
adapters:
|
||||||
|
contact:
|
||||||
|
use_mcp: true
|
||||||
|
use_database: true
|
||||||
|
use_fallback: true
|
||||||
|
|
||||||
|
dictionary:
|
||||||
|
use_mcp: true
|
||||||
|
use_database: true
|
||||||
|
use_fallback: true
|
||||||
|
cache_ttl: 86400 # 缓存一天
|
||||||
|
|
||||||
|
news:
|
||||||
|
use_mcp: true
|
||||||
|
use_database: true
|
||||||
|
use_fallback: true
|
||||||
|
cache_ttl: 3600 # 缓存一小时
|
||||||
87
backend/app/mcp/mcp_example.py
Normal file
87
backend/app/mcp/mcp_example.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
MCP集成示例
|
||||||
|
展示如何使用统一的MCP接口
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
from ..mcp.mcp_manager import mcp_manager
|
||||||
|
from ..mcp.adapters import ContactAdapter, DictionaryAdapter, NewsAdapter
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_mcp():
|
||||||
|
"""设置MCP系统"""
|
||||||
|
# 1. 配置MCP服务器(可选)
|
||||||
|
servers_config = {
|
||||||
|
"news": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-news"],
|
||||||
|
"enabled": False # 先禁用,等配置好后启用
|
||||||
|
},
|
||||||
|
"dictionary": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-dictionary"],
|
||||||
|
"enabled": False
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-gmail"],
|
||||||
|
"enabled": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mcp_manager.configure_servers(servers_config)
|
||||||
|
|
||||||
|
# 2. 注册适配器
|
||||||
|
mcp_manager.register_adapter(ContactAdapter())
|
||||||
|
mcp_manager.register_adapter(DictionaryAdapter())
|
||||||
|
mcp_manager.register_adapter(NewsAdapter())
|
||||||
|
|
||||||
|
# 3. 初始化
|
||||||
|
await mcp_manager.initialize()
|
||||||
|
|
||||||
|
|
||||||
|
async def example_usage():
|
||||||
|
"""使用示例"""
|
||||||
|
await setup_mcp()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("可用适配器:", mcp_manager.get_available_adapters())
|
||||||
|
print("可用MCP工具:", mcp_manager.get_available_tools())
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 1. 查询词典
|
||||||
|
print("\n📖 查询单词 'ephemeral':")
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"dictionary",
|
||||||
|
"query_word",
|
||||||
|
word="ephemeral",
|
||||||
|
user_id="default"
|
||||||
|
)
|
||||||
|
print(f"来源: {result.source}")
|
||||||
|
print(f"结果: {result.data}")
|
||||||
|
|
||||||
|
# 2. 查询新闻
|
||||||
|
print("\n📰 查询新闻 'AI':")
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"news",
|
||||||
|
"query_news",
|
||||||
|
query="AI",
|
||||||
|
user_id="default"
|
||||||
|
)
|
||||||
|
print(f"来源: {result.source}")
|
||||||
|
print(f"结果数量: {len(result.data) if result.data else 0}")
|
||||||
|
|
||||||
|
# 3. 获取联系人
|
||||||
|
print("\n👥 获取联系人列表:")
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"contact",
|
||||||
|
"list_contacts",
|
||||||
|
user_id="default"
|
||||||
|
)
|
||||||
|
print(f"来源: {result.source}")
|
||||||
|
print(f"联系人数量: {len(result.data) if result.data else 0}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(example_usage())
|
||||||
114
backend/app/mcp/mcp_manager.py
Normal file
114
backend/app/mcp/mcp_manager.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
"""
|
||||||
|
MCP管理器
|
||||||
|
统一管理所有MCP适配器和外部接口
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any, Optional, List, Type
|
||||||
|
from .mcp_client import MCPClient, MCPServerConfig
|
||||||
|
from .adapters.base_adapter import BaseAdapter, AdapterResult
|
||||||
|
|
||||||
|
|
||||||
|
class MCPManager:
|
||||||
|
"""
|
||||||
|
MCP管理器
|
||||||
|
|
||||||
|
职责:
|
||||||
|
1. 管理MCP客户端
|
||||||
|
2. 注册和管理适配器
|
||||||
|
3. 提供统一的调用接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._mcp_client = MCPClient()
|
||||||
|
self._adapters: Dict[str, BaseAdapter] = {}
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
|
def configure_servers(self, servers_config: Dict[str, Dict[str, Any]]):
|
||||||
|
"""
|
||||||
|
配置MCP服务器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
servers_config: 服务器配置字典
|
||||||
|
{
|
||||||
|
"filesystem": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
|
||||||
|
},
|
||||||
|
"news": {...}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
for name, config in servers_config.items():
|
||||||
|
server_config = MCPServerConfig(
|
||||||
|
name=name,
|
||||||
|
server_type=config.get("type", "stdio"),
|
||||||
|
command=config.get("command"),
|
||||||
|
args=config.get("args", []),
|
||||||
|
url=config.get("url"),
|
||||||
|
headers=config.get("headers", {}),
|
||||||
|
env=config.get("env", {}),
|
||||||
|
enabled=config.get("enabled", True)
|
||||||
|
)
|
||||||
|
self._mcp_client.register_server(server_config)
|
||||||
|
|
||||||
|
def register_adapter(self, adapter: BaseAdapter):
|
||||||
|
"""注册适配器"""
|
||||||
|
adapter.mcp_client = self._mcp_client
|
||||||
|
self._adapters[adapter.name] = adapter
|
||||||
|
|
||||||
|
def get_adapter(self, name: str) -> Optional[BaseAdapter]:
|
||||||
|
"""获取适配器"""
|
||||||
|
return self._adapters.get(name)
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
"""初始化MCP系统"""
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._mcp_client.initialize()
|
||||||
|
|
||||||
|
# 初始化所有适配器
|
||||||
|
for name, adapter in self._adapters.items():
|
||||||
|
print(f"[MCP] 初始化适配器: {name}")
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
print(f"[MCP] 管理器初始化完成,适配器: {list(self._adapters.keys())}")
|
||||||
|
|
||||||
|
async def execute(
|
||||||
|
self,
|
||||||
|
adapter_name: str,
|
||||||
|
action: str,
|
||||||
|
**kwargs
|
||||||
|
) -> AdapterResult:
|
||||||
|
"""
|
||||||
|
统一执行接口
|
||||||
|
|
||||||
|
Args:
|
||||||
|
adapter_name: 适配器名称
|
||||||
|
action: 操作类型
|
||||||
|
**kwargs: 操作参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AdapterResult: 执行结果
|
||||||
|
"""
|
||||||
|
if not self._initialized:
|
||||||
|
await self.initialize()
|
||||||
|
|
||||||
|
adapter = self._adapters.get(adapter_name)
|
||||||
|
if not adapter:
|
||||||
|
return AdapterResult(
|
||||||
|
success=False,
|
||||||
|
error=f"适配器 {adapter_name} 不存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
return await adapter.execute(action, **kwargs)
|
||||||
|
|
||||||
|
def get_available_adapters(self) -> List[str]:
|
||||||
|
"""获取所有可用适配器"""
|
||||||
|
return list(self._adapters.keys())
|
||||||
|
|
||||||
|
def get_available_tools(self) -> List[str]:
|
||||||
|
"""获取所有可用MCP工具"""
|
||||||
|
return self._mcp_client.get_available_tools()
|
||||||
|
|
||||||
|
|
||||||
|
# 全局单例
|
||||||
|
mcp_manager = MCPManager()
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
通讯录子图 API 调用工具
|
通讯录子图 API 调用工具(使用MCP统一接口)
|
||||||
支持模拟数据和真实数据库两种模式
|
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from .state import Contact, Email
|
from .state import Contact, Email
|
||||||
|
from ...mcp.mcp_manager import mcp_manager
|
||||||
|
from ...mcp.adapters import ContactAdapter
|
||||||
# ========== 模拟数据(保留作为备选)==========
|
|
||||||
|
|
||||||
# 模拟数据库
|
|
||||||
MOCK_CONTACTS_DB = {}
|
|
||||||
MOCK_EMAILS_DB = []
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ContactAPIClient:
|
class ContactAPIClient:
|
||||||
"""
|
"""
|
||||||
通讯录 API 客户端 - 支持真实数据库和模拟模式
|
通讯录 API 客户端 - 使用MCP统一接口
|
||||||
|
|
||||||
使用方式:
|
保持向后兼容,内部使用MCP适配器
|
||||||
1. 真实数据库模式:传入 conn 参数
|
|
||||||
2. 模拟模式:不传入 conn,或 conn 为 None
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, conn=None):
|
def __init__(self, conn=None):
|
||||||
@@ -31,256 +23,99 @@ class ContactAPIClient:
|
|||||||
初始化
|
初始化
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
conn: 数据库连接(来自 checkpointer.conn),为 None 时使用模拟模式
|
conn: 数据库连接(保留用于向后兼容)
|
||||||
"""
|
"""
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self._use_db = conn is not None
|
|
||||||
|
|
||||||
if self._use_db:
|
# 确保MCP已初始化
|
||||||
|
import asyncio
|
||||||
try:
|
try:
|
||||||
from ...db.models import ContactRepository, ContactEntity
|
asyncio.create_task(self._init_mcp())
|
||||||
self._repo = ContactRepository(conn)
|
except RuntimeError:
|
||||||
except Exception as e:
|
pass # 没有事件循环时跳过,延迟初始化
|
||||||
print(f"Repository 初始化失败,回退到模拟模式: {e}")
|
|
||||||
self._use_db = False
|
|
||||||
self._repo = None
|
|
||||||
|
|
||||||
# ========== 真实数据库方法 ==========
|
async def _init_mcp(self):
|
||||||
|
"""初始化MCP系统"""
|
||||||
|
if not mcp_manager.get_adapter("contact"):
|
||||||
|
# 获取repository(如果有)
|
||||||
|
repo = None
|
||||||
|
if self.conn:
|
||||||
|
try:
|
||||||
|
from ...db.models import ContactRepository
|
||||||
|
repo = ContactRepository(self.conn)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def list_contacts_db(self, user_id: str = "default") -> List[Contact]:
|
mcp_manager.register_adapter(ContactAdapter(contact_repo=repo))
|
||||||
"""真实数据库:获取联系人列表"""
|
await mcp_manager.initialize()
|
||||||
if not self._repo:
|
|
||||||
return await self.list_contacts_mock(user_id)
|
|
||||||
|
|
||||||
entities = await self._repo.list_by_user(user_id)
|
async def list_contacts(self, user_id: str = "default") -> List[Contact]:
|
||||||
return [
|
"""获取联系人列表"""
|
||||||
Contact(
|
await self._init_mcp()
|
||||||
id=e.id,
|
result = await mcp_manager.execute("contact", "list_contacts", user_id=user_id)
|
||||||
name=e.name,
|
if result.success:
|
||||||
phone=e.phone,
|
return result.data
|
||||||
email=e.email,
|
return []
|
||||||
company=e.company,
|
|
||||||
position=e.position,
|
async def add_contact(self, user_id: str, contact: Contact) -> bool:
|
||||||
created_at=e.created_at
|
"""添加联系人"""
|
||||||
|
await self._init_mcp()
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"contact", "add_contact",
|
||||||
|
user_id=user_id, contact=contact
|
||||||
)
|
)
|
||||||
for e in entities
|
return result.success and result.data
|
||||||
]
|
|
||||||
|
|
||||||
async def add_contact_db(self, user_id: str, contact: Contact) -> bool:
|
async def list_emails(self, user_id: str = "default") -> List[Email]:
|
||||||
"""真实数据库:添加联系人"""
|
"""查询邮件列表"""
|
||||||
if not self._repo:
|
await self._init_mcp()
|
||||||
return await self.save_contact_mock(user_id, contact)
|
result = await mcp_manager.execute("contact", "list_emails", user_id=user_id)
|
||||||
|
if result.success:
|
||||||
|
return result.data
|
||||||
|
return []
|
||||||
|
|
||||||
from ...db.models import ContactEntity
|
async def generate_email_draft(self, query: str) -> Dict[str, str]:
|
||||||
entity = ContactEntity(
|
"""生成邮件草稿"""
|
||||||
user_id=user_id,
|
await self._init_mcp()
|
||||||
name=contact.name,
|
result = await mcp_manager.execute(
|
||||||
phone=contact.phone,
|
"contact", "generate_email_draft", query=query
|
||||||
email=contact.email,
|
|
||||||
company=contact.company,
|
|
||||||
position=contact.position,
|
|
||||||
created_at=contact.created_at or datetime.now().isoformat()
|
|
||||||
)
|
)
|
||||||
await self._repo.insert(entity)
|
if result.success:
|
||||||
return True
|
return result.data
|
||||||
|
|
||||||
# ========== 模拟数据方法(保留)==========
|
|
||||||
|
|
||||||
def list_contacts_mock(self, user_id: str = "default") -> List[Contact]:
|
|
||||||
"""模拟查询联系人列表"""
|
|
||||||
if user_id not in MOCK_CONTACTS_DB:
|
|
||||||
# 初始化一些示例数据
|
|
||||||
MOCK_CONTACTS_DB[user_id] = [
|
|
||||||
Contact(
|
|
||||||
id="1",
|
|
||||||
name="张三",
|
|
||||||
phone="13800138000",
|
|
||||||
email="zhangsan@example.com",
|
|
||||||
company="科技公司",
|
|
||||||
position="工程师",
|
|
||||||
created_at=datetime.now().isoformat()
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
id="2",
|
|
||||||
name="李四",
|
|
||||||
phone="13900139000",
|
|
||||||
email="lisi@example.com",
|
|
||||||
company="贸易公司",
|
|
||||||
position="经理",
|
|
||||||
created_at=datetime.now().isoformat()
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
id="3",
|
|
||||||
name="王五",
|
|
||||||
phone="13700137000",
|
|
||||||
email="wangwu@example.com",
|
|
||||||
company="咨询公司",
|
|
||||||
position="顾问",
|
|
||||||
created_at=datetime.now().isoformat()
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
return MOCK_CONTACTS_DB[user_id]
|
|
||||||
|
|
||||||
def extract_contact_info_mock(self, query: str) -> Optional[Dict[str, Any]]:
|
|
||||||
"""模拟从查询中提取联系人信息"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
# 提取邮箱
|
|
||||||
email_match = re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query)
|
|
||||||
# 提取手机号
|
|
||||||
phone_match = re.search(r'1[3-9]\d{9}', query)
|
|
||||||
# 提取姓名(简单匹配)
|
|
||||||
if any(keyword in query for keyword in ["添加", "add"]):
|
|
||||||
name = "未知"
|
|
||||||
clean_query = query
|
|
||||||
if email_match:
|
|
||||||
clean_query = clean_query.replace(email_match.group(), "")
|
|
||||||
if phone_match:
|
|
||||||
clean_query = clean_query.replace(phone_match.group(), "")
|
|
||||||
clean_query = clean_query.replace("添加", "").replace("add", "").replace("联系人", "").strip()
|
|
||||||
if clean_query:
|
|
||||||
name = clean_query
|
|
||||||
|
|
||||||
return {
|
|
||||||
"name": name,
|
|
||||||
"phone": phone_match.group() if phone_match else "",
|
|
||||||
"email": email_match.group() if email_match else "",
|
|
||||||
"created_at": datetime.now().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def save_contact_mock(self, user_id: str, contact: Contact) -> bool:
|
|
||||||
"""模拟保存联系人"""
|
|
||||||
if user_id not in MOCK_CONTACTS_DB:
|
|
||||||
MOCK_CONTACTS_DB[user_id] = []
|
|
||||||
|
|
||||||
if not contact.id:
|
|
||||||
contact.id = str(len(MOCK_CONTACTS_DB[user_id]) + 1)
|
|
||||||
|
|
||||||
MOCK_CONTACTS_DB[user_id].append(contact)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def list_emails_mock(self) -> List[Email]:
|
|
||||||
"""模拟查询邮件列表"""
|
|
||||||
global MOCK_EMAILS_DB
|
|
||||||
|
|
||||||
if not MOCK_EMAILS_DB:
|
|
||||||
MOCK_EMAILS_DB = [
|
|
||||||
Email(
|
|
||||||
id="1",
|
|
||||||
subject="会议邀请:AI 技术分享",
|
|
||||||
sender="admin@example.com",
|
|
||||||
recipients=["user@example.com"],
|
|
||||||
date=datetime.now().isoformat(),
|
|
||||||
body="你好,下周一将举办 AI 技术分享会,欢迎参加。"
|
|
||||||
),
|
|
||||||
Email(
|
|
||||||
id="2",
|
|
||||||
subject="项目进度更新",
|
|
||||||
sender="manager@example.com",
|
|
||||||
recipients=["user@example.com"],
|
|
||||||
date=datetime.now().isoformat(),
|
|
||||||
body="项目进度良好,继续保持。"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
return MOCK_EMAILS_DB
|
|
||||||
|
|
||||||
def generate_email_draft_mock(self, query: str) -> Dict[str, str]:
|
|
||||||
"""模拟生成邮件草稿"""
|
|
||||||
return {
|
return {
|
||||||
"subject": f"Re: {query}",
|
"subject": f"Re: {query}",
|
||||||
"recipient": "recipient@example.com",
|
"recipient": "recipient@example.com",
|
||||||
"body": "你好,\n\n这是一封自动生成的邮件草稿。\n\n此致,\n你的助手"
|
"body": "你好,\n\n这是一封自动生成的邮件草稿。"
|
||||||
}
|
}
|
||||||
|
|
||||||
def send_email_mock(self, recipient: str, subject: str, body: str) -> Dict[str, Any]:
|
|
||||||
"""模拟发送邮件"""
|
|
||||||
global MOCK_EMAILS_DB
|
|
||||||
|
|
||||||
MOCK_EMAILS_DB.append(
|
|
||||||
Email(
|
|
||||||
id=str(len(MOCK_EMAILS_DB) + 1),
|
|
||||||
subject=subject,
|
|
||||||
sender="me@example.com",
|
|
||||||
recipients=[recipient],
|
|
||||||
date=datetime.now().isoformat(),
|
|
||||||
body=body
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"message": "邮件发送成功"
|
|
||||||
}
|
|
||||||
|
|
||||||
def sniff_contacts_mock(self, query: str) -> Dict[str, Any]:
|
|
||||||
"""模拟智能嗅探联系人"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', query)
|
|
||||||
phones = re.findall(r'1[3-9]\d{9}', query)
|
|
||||||
|
|
||||||
contacts = []
|
|
||||||
for i, email in enumerate(emails):
|
|
||||||
contacts.append({
|
|
||||||
"name": f"联系人{i+1}",
|
|
||||||
"email": email,
|
|
||||||
"phone": phones[i] if i < len(phones) else ""
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
"contacts": contacts,
|
|
||||||
"count": len(contacts),
|
|
||||||
"suggestion": "是否添加这些联系人?"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ========== 公共方法(自动选择模式)==========
|
|
||||||
|
|
||||||
async def list_contacts(self, user_id: str = "default") -> List[Contact]:
|
|
||||||
"""获取联系人列表(自动选择数据库或模拟模式)"""
|
|
||||||
if self._use_db:
|
|
||||||
return await self.list_contacts_db(user_id)
|
|
||||||
return self.list_contacts_mock(user_id)
|
|
||||||
|
|
||||||
async def add_contact(self, user_id: str, contact: Contact) -> bool:
|
|
||||||
"""添加联系人(自动选择数据库或模拟模式)"""
|
|
||||||
if self._use_db:
|
|
||||||
return await self.add_contact_db(user_id, contact)
|
|
||||||
return self.save_contact_mock(user_id, contact)
|
|
||||||
|
|
||||||
async def list_emails(self, user_id: str = "default") -> List[Email]:
|
|
||||||
"""查询邮件列表(目前用模拟)"""
|
|
||||||
return self.list_emails_mock()
|
|
||||||
|
|
||||||
async def generate_email_draft(self, query: str) -> Dict[str, str]:
|
|
||||||
"""生成邮件草稿(目前用模拟)"""
|
|
||||||
return self.generate_email_draft_mock(query)
|
|
||||||
|
|
||||||
async def send_email(self, user_id: str, recipient: str, subject: str, body: str) -> bool:
|
async def send_email(self, user_id: str, recipient: str, subject: str, body: str) -> bool:
|
||||||
"""发送邮件(目前用模拟)"""
|
"""发送邮件"""
|
||||||
result = self.send_email_mock(recipient, subject, body)
|
await self._init_mcp()
|
||||||
return result.get("success", False)
|
result = await mcp_manager.execute(
|
||||||
|
"contact", "send_email",
|
||||||
|
user_id=user_id, recipient=recipient, subject=subject, body=body
|
||||||
|
)
|
||||||
|
return result.success
|
||||||
|
|
||||||
async def sniff_contacts(self, query: str) -> List[Contact]:
|
async def sniff_contacts(self, query: str) -> List[Contact]:
|
||||||
"""智能嗅探联系人(目前用模拟)"""
|
"""智能嗅探联系人"""
|
||||||
result = self.sniff_contacts_mock(query)
|
await self._init_mcp()
|
||||||
contact_dicts = result.get("contacts", [])
|
result = await mcp_manager.execute(
|
||||||
return [
|
"contact", "sniff_contacts", query=query
|
||||||
Contact(
|
|
||||||
id=str(i+1),
|
|
||||||
name=c.get("name", ""),
|
|
||||||
phone=c.get("phone", ""),
|
|
||||||
email=c.get("email", ""),
|
|
||||||
company="",
|
|
||||||
position="",
|
|
||||||
created_at=datetime.now().isoformat()
|
|
||||||
)
|
)
|
||||||
for i, c in enumerate(contact_dicts)
|
if result.success:
|
||||||
]
|
return result.data
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 保持向后兼容的旧方法
|
||||||
|
def list_contacts_mock(self, user_id: str = "default") -> List[Contact]:
|
||||||
|
"""模拟查询(保留用于向后兼容)"""
|
||||||
|
import asyncio
|
||||||
|
try:
|
||||||
|
return asyncio.run(self.list_contacts(user_id))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# 全局实例(模拟模式,保留向后兼容)
|
# 全局单例(保持向后兼容)
|
||||||
contact_api = ContactAPIClient()
|
contact_api = ContactAPIClient()
|
||||||
|
|||||||
@@ -1,133 +1,62 @@
|
|||||||
"""
|
"""
|
||||||
词典API调用工具
|
词典API调用工具(使用MCP统一接口)
|
||||||
Dictionary API Client
|
|
||||||
支持 async 和真实数据库缓存
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from ...mcp.mcp_manager import mcp_manager
|
||||||
|
from ...mcp.adapters import DictionaryAdapter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DictionaryAPIClient:
|
class DictionaryAPIClient:
|
||||||
"""
|
"""
|
||||||
词典API客户端 - 可扩展支持多种API和数据库缓存
|
词典API客户端 - 使用MCP统一接口
|
||||||
|
|
||||||
|
保持向后兼容,内部使用MCP适配器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 可以配置多个API
|
# 保留配置字段用于向后兼容
|
||||||
youdao_api_key: Optional[str] = None
|
youdao_api_key: Optional[str] = None
|
||||||
youdao_api_secret: Optional[str] = None
|
youdao_api_secret: Optional[str] = None
|
||||||
|
|
||||||
# 数据库 Repository(可选,用于缓存单词查询)
|
|
||||||
word_repository: Optional[Any] = None
|
word_repository: Optional[Any] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
"""初始化后,如果有 repository 则支持 async"""
|
"""初始化后设置MCP"""
|
||||||
|
import asyncio
|
||||||
|
try:
|
||||||
|
asyncio.create_task(self._init_mcp())
|
||||||
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def query_word_db(self, user_id: str, word: str) -> Optional[Dict[str, Any]]:
|
async def _init_mcp(self):
|
||||||
"""从数据库缓存查询单词"""
|
"""初始化MCP系统"""
|
||||||
if not self.word_repository:
|
if not mcp_manager.get_adapter("dictionary"):
|
||||||
return None
|
mcp_manager.register_adapter(
|
||||||
try:
|
DictionaryAdapter(word_repo=self.word_repository)
|
||||||
entity = await self.word_repository.search_by_word(user_id, word)
|
|
||||||
if entity:
|
|
||||||
return {
|
|
||||||
"phonetic": entity.phonetic,
|
|
||||||
"part_of_speech": entity.part_of_speech,
|
|
||||||
"definitions": [entity.definition] if entity.definition else [],
|
|
||||||
"examples": [entity.examples] if entity.examples else []
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"从数据库查询单词失败:{e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def cache_word_db(self, user_id: str, word: str, data: Dict[str, Any]):
|
|
||||||
"""把单词查询结果缓存到数据库"""
|
|
||||||
if not self.word_repository:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
from ...db.models import WordEntity
|
|
||||||
entity = WordEntity(
|
|
||||||
user_id=user_id,
|
|
||||||
word=word,
|
|
||||||
phonetic=data.get("phonetic", ""),
|
|
||||||
part_of_speech=data.get("part_of_speech", ""),
|
|
||||||
definition=data.get("definitions", [""])[0] if data.get("definitions") else "",
|
|
||||||
examples=data.get("examples", [""])[0] if data.get("examples") else ""
|
|
||||||
)
|
)
|
||||||
await self.word_repository.insert(entity)
|
await mcp_manager.initialize()
|
||||||
except Exception as e:
|
|
||||||
print(f"缓存单词到数据库失败:{e}")
|
|
||||||
|
|
||||||
async def query_word_youdao(self, word: str) -> Optional[Dict[str, Any]]:
|
async def query_word(
|
||||||
"""
|
self,
|
||||||
调用有道词典API查询单词(async 版本)
|
user_id: str = "default",
|
||||||
注意:需要配置有道API密钥才能使用
|
word: str = "",
|
||||||
文档:https://ai.youdao.com/doc.s#guide
|
use_cache: bool = True
|
||||||
"""
|
) -> Dict[str, Any]:
|
||||||
if not self.youdao_api_key or not self.youdao_api_secret:
|
"""查询单词(统一入口)"""
|
||||||
return None
|
await self._init_mcp()
|
||||||
|
result = await mcp_manager.execute(
|
||||||
try:
|
"dictionary", "query_word",
|
||||||
# TODO: 实现真实的有道API调用(用 httpx 或 aiohttp)
|
user_id=user_id, word=word, use_cache=use_cache
|
||||||
# 这里是示例结构
|
)
|
||||||
return None
|
if result.success:
|
||||||
|
return result.data
|
||||||
except Exception as e:
|
return self.query_word_mock(word)
|
||||||
print(f"有道API调用失败:{e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def translate_baidu(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
调用百度翻译API(async 版本)
|
|
||||||
注意:需要配置百度API密钥才能使用
|
|
||||||
文档:https://fanyi-api.baidu.com/doc/21
|
|
||||||
"""
|
|
||||||
# TODO: 实现真实的百度翻译API调用(用 httpx 或 aiohttp)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def query_word_mock(self, word: str) -> Dict[str, Any]:
|
def query_word_mock(self, word: str) -> Dict[str, Any]:
|
||||||
"""
|
"""模拟查询(保留用于向后兼容)"""
|
||||||
模拟词典API - 目前用于演示
|
|
||||||
"""
|
|
||||||
mock_db = {
|
|
||||||
"serendipity": {
|
|
||||||
"phonetic": "/ˌserənˈdipədē/",
|
|
||||||
"part_of_speech": "n.",
|
|
||||||
"definitions": ["意外发现珍奇事物的能力", "机缘凑巧"],
|
|
||||||
"examples": ["Finding that old photo was pure serendipity."]
|
|
||||||
},
|
|
||||||
"ephemeral": {
|
|
||||||
"phonetic": "/əˈfem(ə)rəl/",
|
|
||||||
"part_of_speech": "adj.",
|
|
||||||
"definitions": ["短暂的,瞬息的"],
|
|
||||||
"examples": ["Fame in the digital age is often ephemeral."]
|
|
||||||
},
|
|
||||||
"ubiquitous": {
|
|
||||||
"phonetic": "/yo͞oˈbikwədəs/",
|
|
||||||
"part_of_speech": "adj.",
|
|
||||||
"definitions": ["无处不在的", "普遍存在的"],
|
|
||||||
"examples": ["Smartphones have become ubiquitous in modern life."]
|
|
||||||
},
|
|
||||||
"eloquent": {
|
|
||||||
"phonetic": "/ˈeləkwənt/",
|
|
||||||
"part_of_speech": "adj.",
|
|
||||||
"definitions": ["雄辩的,有说服力的"],
|
|
||||||
"examples": ["She gave an eloquent speech at the conference."]
|
|
||||||
},
|
|
||||||
"resilient": {
|
|
||||||
"phonetic": "/rəˈzilyənt/",
|
|
||||||
"part_of_speech": "adj.",
|
|
||||||
"definitions": ["有复原力的,能适应的"],
|
|
||||||
"examples": ["The community has proven to be resilient in the face of challenges."]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if word.lower() in mock_db:
|
|
||||||
return mock_db[word.lower()]
|
|
||||||
else:
|
|
||||||
return {
|
return {
|
||||||
|
"word": word,
|
||||||
"phonetic": "",
|
"phonetic": "",
|
||||||
"part_of_speech": "n.",
|
"part_of_speech": "n.",
|
||||||
"definitions": [f"{word} 的释义1", f"{word} 的释义2"],
|
"definitions": [f"{word} 的释义1", f"{word} 的释义2"],
|
||||||
@@ -135,58 +64,19 @@ class DictionaryAPIClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def translate_mock(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Dict[str, Any]:
|
def translate_mock(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Dict[str, Any]:
|
||||||
"""
|
"""模拟翻译(保留用于向后兼容)"""
|
||||||
模拟翻译API - 目前用于演示
|
|
||||||
"""
|
|
||||||
translations = {
|
|
||||||
"你好": "Hello",
|
|
||||||
"hello": "你好",
|
|
||||||
"人工智能": "Artificial Intelligence",
|
|
||||||
"artificial intelligence": "人工智能",
|
|
||||||
"ai": "人工智能",
|
|
||||||
"大模型": "Large Language Model",
|
|
||||||
"自然语言处理": "Natural Language Processing"
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"translated_text": translations.get(text.lower(), f"【翻译结果】{text}"),
|
"translated_text": f"【翻译】{text}",
|
||||||
"confidence": 0.95
|
"confidence": 0.95
|
||||||
}
|
}
|
||||||
|
|
||||||
def extract_terms_mock(self, text: str) -> list:
|
def extract_terms_mock(self, text: str) -> list:
|
||||||
"""
|
"""模拟术语提取(保留用于向后兼容)"""
|
||||||
模拟术语提取API
|
|
||||||
"""
|
|
||||||
return [
|
return [
|
||||||
{"term": "AI", "type": "技术术语", "definition": "人工智能", "confidence": 0.95},
|
{"term": "AI", "type": "技术术语", "definition": "人工智能", "confidence": 0.95},
|
||||||
{"term": "LLM", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92},
|
{"term": "大模型", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92}
|
||||||
{"term": "NLP", "type": "技术术语", "definition": "自然语言处理", "confidence": 0.88}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# ========== 统一入口(优先查缓存) ==========
|
|
||||||
async def query_word(self, user_id: str = "default", word: str = "", use_cache: bool = True) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
查询单词(统一入口,优先查数据库缓存)
|
|
||||||
"""
|
|
||||||
# 1. 先查数据库缓存
|
|
||||||
if use_cache:
|
|
||||||
cached = await self.query_word_db(user_id, word)
|
|
||||||
if cached:
|
|
||||||
return cached
|
|
||||||
|
|
||||||
# 2. 查第三方 API(暂未实现)
|
# 全局单例(保持向后兼容)
|
||||||
api_result = await self.query_word_youdao(word)
|
|
||||||
if api_result:
|
|
||||||
if use_cache:
|
|
||||||
await self.cache_word_db(user_id, word, api_result)
|
|
||||||
return api_result
|
|
||||||
|
|
||||||
# 3. 用模拟数据(兜底)
|
|
||||||
mock_result = self.query_word_mock(word)
|
|
||||||
if use_cache:
|
|
||||||
await self.cache_word_db(user_id, word, mock_result)
|
|
||||||
return mock_result
|
|
||||||
|
|
||||||
|
|
||||||
# 单例实例(模拟模式,保持向后兼容)
|
|
||||||
dictionary_api = DictionaryAPIClient()
|
dictionary_api = DictionaryAPIClient()
|
||||||
|
|||||||
@@ -1,72 +1,61 @@
|
|||||||
"""
|
"""
|
||||||
资讯子图API调用工具
|
资讯子图API调用工具(使用MCP统一接口)
|
||||||
News Analysis API Client
|
|
||||||
支持 async 和真实数据库缓存
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional, List
|
||||||
import random
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from ...mcp.mcp_manager import mcp_manager
|
||||||
|
from ...mcp.adapters import NewsAdapter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NewsAPIClient:
|
class NewsAPIClient:
|
||||||
"""
|
"""
|
||||||
资讯API客户端 - 可扩展支持多种API和数据库缓存
|
资讯API客户端 - 使用MCP统一接口
|
||||||
|
|
||||||
|
保持向后兼容,内部使用MCP适配器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 可以配置多个API(如 NewsAPI, 今日头条, 百度新闻等)
|
# 保留配置字段用于向后兼容
|
||||||
newsapi_key: Optional[str] = None
|
newsapi_key: Optional[str] = None
|
||||||
|
|
||||||
# 数据库 Repository(可选,用于缓存新闻)
|
|
||||||
news_repository: Optional[Any] = None
|
news_repository: Optional[Any] = None
|
||||||
|
|
||||||
async def query_news_db(self, user_id: str, keyword: str) -> Optional[List[Dict[str, Any]]]:
|
def __post_init__(self):
|
||||||
"""从数据库缓存查询新闻"""
|
"""初始化后设置MCP"""
|
||||||
if not self.news_repository:
|
import asyncio
|
||||||
return None
|
|
||||||
try:
|
try:
|
||||||
entities = await self.news_repository.search_by_keywords(user_id, keyword)
|
asyncio.create_task(self._init_mcp())
|
||||||
if entities:
|
except RuntimeError:
|
||||||
return [
|
pass
|
||||||
{
|
|
||||||
"title": e.title,
|
|
||||||
"source": e.source,
|
|
||||||
"summary": e.content,
|
|
||||||
"keywords": e.keywords.split(",") if e.keywords else [],
|
|
||||||
"author": "",
|
|
||||||
"published_at": e.created_at
|
|
||||||
}
|
|
||||||
for e in entities
|
|
||||||
]
|
|
||||||
except Exception as e:
|
|
||||||
print(f"从数据库查询新闻失败:{e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def cache_news_db(self, user_id: str, news: Dict[str, Any]):
|
async def _init_mcp(self):
|
||||||
"""把新闻缓存到数据库"""
|
"""初始化MCP系统"""
|
||||||
if not self.news_repository:
|
if not mcp_manager.get_adapter("news"):
|
||||||
return
|
mcp_manager.register_adapter(
|
||||||
try:
|
NewsAdapter(news_repo=self.news_repository)
|
||||||
from ...db.models import NewsEntity
|
|
||||||
entity = NewsEntity(
|
|
||||||
user_id=user_id,
|
|
||||||
title=news.get("title", ""),
|
|
||||||
content=news.get("summary", ""),
|
|
||||||
url=news.get("url", ""),
|
|
||||||
source=news.get("source", ""),
|
|
||||||
keywords=",".join(news.get("keywords", []))
|
|
||||||
)
|
)
|
||||||
await self.news_repository.insert(entity)
|
await mcp_manager.initialize()
|
||||||
except Exception as e:
|
|
||||||
print(f"缓存新闻到数据库失败:{e}")
|
async def query_news(
|
||||||
|
self,
|
||||||
|
user_id: str = "default",
|
||||||
|
query: str = "",
|
||||||
|
use_cache: bool = True
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""查询新闻(统一入口)"""
|
||||||
|
await self._init_mcp()
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"news", "query_news",
|
||||||
|
user_id=user_id, query=query, use_cache=use_cache
|
||||||
|
)
|
||||||
|
if result.success:
|
||||||
|
return result.data
|
||||||
|
return self.query_news_mock(query)
|
||||||
|
|
||||||
def query_news_mock(self, query: str) -> List[Dict[str, Any]]:
|
def query_news_mock(self, query: str) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""模拟查询(保留用于向后兼容)"""
|
||||||
模拟查询资讯 - 目前用于演示
|
|
||||||
"""
|
|
||||||
# 模拟资讯数据库
|
|
||||||
mock_news = [
|
mock_news = [
|
||||||
{
|
{
|
||||||
"title": "OpenAI发布GPT-5:智能再升级",
|
"title": "OpenAI发布GPT-5:智能再升级",
|
||||||
@@ -83,74 +72,44 @@ class NewsAPIClient:
|
|||||||
"keywords": ["医疗", "大模型", "应用"],
|
"keywords": ["医疗", "大模型", "应用"],
|
||||||
"author": "Medical Team",
|
"author": "Medical Team",
|
||||||
"published_at": datetime.now().isoformat()
|
"published_at": datetime.now().isoformat()
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "2026年AI行业发展趋势报告",
|
|
||||||
"source": "Business Daily",
|
|
||||||
"summary": "最新行业报告显示,AI行业将继续保持高速增长,企业数字化转型加速...",
|
|
||||||
"keywords": ["趋势", "AI", "商业"],
|
|
||||||
"author": "Business Team",
|
|
||||||
"published_at": datetime.now().isoformat()
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
# 根据查询词简单过滤
|
|
||||||
results = []
|
results = []
|
||||||
query_lower = query.lower()
|
query_lower = query.lower()
|
||||||
|
|
||||||
for news in mock_news:
|
for news in mock_news:
|
||||||
if (query_lower in news["title"].lower() or
|
if (query_lower in news["title"].lower() or
|
||||||
query_lower in news["summary"].lower() or
|
query_lower in news["summary"].lower() or
|
||||||
any(keyword.lower() in query_lower for keyword in news["keywords"])):
|
any(keyword.lower() in query_lower for keyword in news["keywords"])):
|
||||||
results.append(news)
|
results.append(news)
|
||||||
|
|
||||||
# 如果没有匹配到,返回前两条
|
return results if results else mock_news[:2]
|
||||||
if not results:
|
|
||||||
results = mock_news[:2]
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def analyze_url_mock(self, url: str) -> Dict[str, Any]:
|
def analyze_url_mock(self, url: str) -> Dict[str, Any]:
|
||||||
"""
|
"""模拟URL分析(保留用于向后兼容)"""
|
||||||
模拟URL分析 - 目前用于演示
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
"title": f"分析结果:{url}",
|
"title": f"分析结果:{url}",
|
||||||
"source": "URL Analyzer",
|
"source": "URL Analyzer",
|
||||||
"summary": "已完成对该URL的内容分析,包含文章摘要和情感倾向判断...",
|
"summary": "已完成对该URL的内容分析,包含文章摘要和情感倾向判断...",
|
||||||
"keywords": ["News", "Analysis", url.split("/")[-1] if url else "unknown"]
|
"keywords": ["News", "Analysis"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def extract_keywords_mock(self, text: str) -> List[str]:
|
def extract_keywords_mock(self, text: str) -> List[str]:
|
||||||
"""
|
"""模拟关键词提取(保留用于向后兼容)"""
|
||||||
模拟关键词提取 - 目前用于演示
|
keywords = ["AI", "大模型", "应用场景", "行业趋势", "创新", "技术"]
|
||||||
"""
|
result = [k for k in keywords if k.lower() in text.lower()]
|
||||||
# 简单的关键词提取模拟
|
return result if result else keywords[:4]
|
||||||
common_keywords = ["AI", "大模型", "应用场景", "行业趋势", "创新", "技术"]
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for keyword in common_keywords:
|
|
||||||
if keyword.lower() in text.lower():
|
|
||||||
result.append(keyword)
|
|
||||||
|
|
||||||
# 如果没找到,返回默认关键词
|
|
||||||
if not result:
|
|
||||||
result = ["AI", "大模型", "应用场景", "行业趋势"]
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def generate_report_mock(self, query: str) -> str:
|
def generate_report_mock(self, query: str) -> str:
|
||||||
"""
|
"""模拟报告生成(保留用于向后兼容)"""
|
||||||
模拟报告生成 - 目前用于演示
|
return f"""═══════════════════════════════════════════
|
||||||
"""
|
|
||||||
report = f"""═══════════════════════════════════════════
|
|
||||||
📊 资讯分析报告
|
📊 资讯分析报告
|
||||||
═══════════════════════════════════════════
|
═══════════════════════════════════════════
|
||||||
|
|
||||||
主题:{query}
|
主题:{query}
|
||||||
|
|
||||||
📋 摘要:
|
📋 摘要:
|
||||||
这是一份关于 {query} 的资讯分析综合报告,包含最新行业动态和趋势分析。
|
这是关于 {query} 的资讯分析综合报告。
|
||||||
|
|
||||||
🔍 主要发现:
|
🔍 主要发现:
|
||||||
1. AI技术持续快速发展
|
1. AI技术持续快速发展
|
||||||
@@ -161,36 +120,10 @@ class NewsAPIClient:
|
|||||||
- AI
|
- AI
|
||||||
- 大模型
|
- 大模型
|
||||||
- 数字化转型
|
- 数字化转型
|
||||||
- 创新
|
|
||||||
|
|
||||||
═══════════════════════════════════════════
|
═══════════════════════════════════════════
|
||||||
💡 建议:继续关注行业动态,把握发展机遇!
|
|
||||||
"""
|
"""
|
||||||
return report
|
|
||||||
|
|
||||||
# ========== 统一入口(优先查缓存) ==========
|
|
||||||
async def query_news(self, user_id: str = "default", query: str = "", use_cache: bool = True) -> List[Dict[str, Any]]:
|
|
||||||
"""查询新闻(统一入口,优先查数据库缓存)"""
|
|
||||||
# 1. 先查数据库缓存
|
|
||||||
if use_cache:
|
|
||||||
cached = await self.query_news_db(user_id, query)
|
|
||||||
if cached:
|
|
||||||
return cached
|
|
||||||
|
|
||||||
# 2. 查第三方 API(暂未实现)
|
|
||||||
# api_result = await self.query_news_api(query)
|
|
||||||
# if api_result:
|
|
||||||
# for news in api_result:
|
|
||||||
# await self.cache_news_db(user_id, news)
|
|
||||||
# return api_result
|
|
||||||
|
|
||||||
# 3. 用模拟数据(兜底)
|
|
||||||
mock_result = self.query_news_mock(query)
|
|
||||||
if use_cache:
|
|
||||||
for news in mock_result:
|
|
||||||
await self.cache_news_db(user_id, news)
|
|
||||||
return mock_result
|
|
||||||
|
|
||||||
|
|
||||||
# 单例实例(模拟模式,保持向后兼容)
|
# 全局单例(保持向后兼容)
|
||||||
news_api = NewsAPIClient()
|
news_api = NewsAPIClient()
|
||||||
|
|||||||
179
backend/docs/MCP_INTEGRATION.md
Normal file
179
backend/docs/MCP_INTEGRATION.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# MCP 集成系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
这是一个统一的外部接口管理层,集成了 MCP (Model Context Protocol),同时支持数据库缓存和降级到模拟数据。
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 子图 (Subgraphs) │
|
||||||
|
│ contact_api │ dictionary_api │ news_api │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ MCP Manager (统一入口) │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Adapters (适配器层) │ │
|
||||||
|
│ │ ContactAdapter │ DictionaryAdapter │ NewsAdapter│ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────┼─────────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ MCP Client │ │ Database │ │ Mock Data │
|
||||||
|
│ (真实服务) │ │ (缓存层) │ │ (降级层) │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/app/mcp/
|
||||||
|
├── __init__.py # 模块初始化
|
||||||
|
├── mcp_manager.py # MCP管理器(统一入口)
|
||||||
|
├── mcp_client.py # MCP客户端
|
||||||
|
├── base_adapter.py # 适配器基类
|
||||||
|
├── mcp_config.example.yaml # 配置示例
|
||||||
|
├── mcp_example.py # 使用示例
|
||||||
|
└── adapters/
|
||||||
|
├── __init__.py
|
||||||
|
├── contact_adapter.py # 通讯录适配器
|
||||||
|
├── dictionary_adapter.py# 词典适配器
|
||||||
|
└── news_adapter.py # 新闻适配器
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 基本使用(自动降级)
|
||||||
|
|
||||||
|
现有的子图API已经无缝迁移,无需修改代码:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 通讯录 - 和之前一样使用
|
||||||
|
from backend.app.subgraphs.contact.api_client import contact_api
|
||||||
|
|
||||||
|
contacts = await contact_api.list_contacts(user_id="default")
|
||||||
|
|
||||||
|
# 词典 - 和之前一样使用
|
||||||
|
from backend.app.subgraphs.dictionary.api_client import dictionary_api
|
||||||
|
|
||||||
|
word_data = await dictionary_api.query_word(word="ephemeral")
|
||||||
|
|
||||||
|
# 新闻 - 和之前一样使用
|
||||||
|
from backend.app.subgraphs.news_analysis.api_client import news_api
|
||||||
|
|
||||||
|
news_list = await news_api.query_news(query="AI")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 直接使用MCP管理器
|
||||||
|
|
||||||
|
```python
|
||||||
|
from backend.app.mcp import mcp_manager, ContactAdapter, DictionaryAdapter, NewsAdapter
|
||||||
|
|
||||||
|
# 注册适配器
|
||||||
|
mcp_manager.register_adapter(ContactAdapter())
|
||||||
|
mcp_manager.register_adapter(DictionaryAdapter())
|
||||||
|
mcp_manager.register_adapter(NewsAdapter())
|
||||||
|
|
||||||
|
# 初始化
|
||||||
|
await mcp_manager.initialize()
|
||||||
|
|
||||||
|
# 统一调用接口
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"dictionary",
|
||||||
|
"query_word",
|
||||||
|
word="serendipity",
|
||||||
|
user_id="default"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"来源: {result.source}") # mcp_dictionary / database / mock
|
||||||
|
print(f"数据: {result.data}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置MCP服务器
|
||||||
|
|
||||||
|
复制配置示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp backend/app/mcp/mcp_config.example.yaml backend/app/mcp/mcp_config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑 `mcp_config.yaml`,启用需要的MCP服务器:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mcp_servers:
|
||||||
|
# Gmail 邮件服务
|
||||||
|
gmail:
|
||||||
|
type: stdio
|
||||||
|
command: npx
|
||||||
|
args:
|
||||||
|
- "-y"
|
||||||
|
- "@modelcontextprotocol/server-gmail"
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# GitHub
|
||||||
|
github:
|
||||||
|
type: stdio
|
||||||
|
command: npx
|
||||||
|
args:
|
||||||
|
- "-y"
|
||||||
|
- "@modelcontextprotocol/server-github"
|
||||||
|
env:
|
||||||
|
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_your_token_here"
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
### 1. 三层降级策略
|
||||||
|
|
||||||
|
- **MCP层**: 优先使用真实的MCP服务
|
||||||
|
- **数据库层**: 其次使用数据库缓存
|
||||||
|
- **模拟层**: 最后降级到模拟数据,确保系统始终可用
|
||||||
|
|
||||||
|
### 2. 统一接口
|
||||||
|
|
||||||
|
所有外部服务都通过 `mcp_manager.execute()` 统一调用,返回标准化的 `AdapterResult`。
|
||||||
|
|
||||||
|
### 3. 向后兼容
|
||||||
|
|
||||||
|
保留了原有的 `api_client` 接口,现有代码无需修改即可使用新系统。
|
||||||
|
|
||||||
|
### 4. 可扩展
|
||||||
|
|
||||||
|
通过继承 `BaseAdapter` 可以轻松添加新的适配器。
|
||||||
|
|
||||||
|
## 创建自定义适配器
|
||||||
|
|
||||||
|
```python
|
||||||
|
from backend.app.mcp import BaseAdapter, AdapterResult
|
||||||
|
|
||||||
|
class MyAdapter(BaseAdapter):
|
||||||
|
name = "my_service"
|
||||||
|
description = "我的自定义服务"
|
||||||
|
|
||||||
|
async def execute(self, action: str, **kwargs) -> AdapterResult:
|
||||||
|
# 1. 尝试MCP
|
||||||
|
# 2. 尝试数据库
|
||||||
|
# 3. 降级到模拟
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 注册
|
||||||
|
mcp_manager.register_adapter(MyAdapter())
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可用的MCP服务器
|
||||||
|
|
||||||
|
- **@modelcontextprotocol/server-filesystem** - 文件系统访问
|
||||||
|
- **@modelcontextprotocol/server-github** - GitHub 集成
|
||||||
|
- **@modelcontextprotocol/server-gmail** - Gmail 邮件
|
||||||
|
- **@modelcontextprotocol/server-brave-search** - 网页搜索
|
||||||
|
- 更多社区服务器...
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
参见 `backend/app/mcp/mcp_example.py` 获取完整的使用示例。
|
||||||
112
backend/test/test_mcp_simple.py
Normal file
112
backend/test/test_mcp_simple.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
简化版MCP测试 - 直接测试mcp模块
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 直接添加mcp模块路径
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent / "backend" / "app"))
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mcp_direct():
|
||||||
|
"""直接测试MCP模块"""
|
||||||
|
print("=" * 70)
|
||||||
|
print("🧪 直接测试 MCP 模块")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# 直接导入mcp子模块
|
||||||
|
print("\n[1/4] 导入MCP核心模块...")
|
||||||
|
try:
|
||||||
|
from mcp.mcp_manager import MCPManager, mcp_manager
|
||||||
|
from mcp.adapters.base_adapter import BaseAdapter, AdapterResult
|
||||||
|
print("✅ 核心模块导入成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 核心模块导入失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 导入适配器
|
||||||
|
print("\n[2/4] 导入适配器...")
|
||||||
|
try:
|
||||||
|
from mcp.adapters.contact_adapter import ContactAdapter
|
||||||
|
from mcp.adapters.dictionary_adapter import DictionaryAdapter
|
||||||
|
from mcp.adapters.news_adapter import NewsAdapter
|
||||||
|
print("✅ 适配器导入成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 适配器导入失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 测试适配器
|
||||||
|
print("\n[3/4] 测试各个适配器...")
|
||||||
|
|
||||||
|
print("\n📖 测试 DictionaryAdapter...")
|
||||||
|
dict_adapter = DictionaryAdapter()
|
||||||
|
result = dict_adapter._fallback("query_word", word="ephemeral")
|
||||||
|
print(f" 来源: {result.source}")
|
||||||
|
print(f" 成功: {result.success}")
|
||||||
|
print(f" 单词: {result.data.get('word', '')}")
|
||||||
|
print(f" 释义: {result.data.get('definitions', [])}")
|
||||||
|
|
||||||
|
print("\n📰 测试 NewsAdapter...")
|
||||||
|
news_adapter = NewsAdapter()
|
||||||
|
result = news_adapter._fallback("query_news", query="AI")
|
||||||
|
print(f" 来源: {result.source}")
|
||||||
|
print(f" 成功: {result.success}")
|
||||||
|
print(f" 数量: {len(result.data) if result.data else 0}")
|
||||||
|
|
||||||
|
print("\n👥 测试 ContactAdapter...")
|
||||||
|
contact_adapter = ContactAdapter()
|
||||||
|
result = contact_adapter._fallback("list_contacts", user_id="test")
|
||||||
|
print(f" 来源: {result.source}")
|
||||||
|
print(f" 成功: {result.success}")
|
||||||
|
print(f" 数量: {len(result.data) if result.data else 0}")
|
||||||
|
|
||||||
|
# 测试MCP管理器
|
||||||
|
print("\n[4/4] 测试 MCP Manager...")
|
||||||
|
try:
|
||||||
|
mcp_manager.register_adapter(ContactAdapter())
|
||||||
|
mcp_manager.register_adapter(DictionaryAdapter())
|
||||||
|
mcp_manager.register_adapter(NewsAdapter())
|
||||||
|
|
||||||
|
await mcp_manager.initialize()
|
||||||
|
|
||||||
|
print(f"✅ 可用适配器: {mcp_manager.get_available_adapters()}")
|
||||||
|
print(f"✅ 可用工具: {mcp_manager.get_available_tools()}")
|
||||||
|
|
||||||
|
# 测试通过manager调用
|
||||||
|
print("\n测试通过Manager调用...")
|
||||||
|
result = await mcp_manager.execute(
|
||||||
|
"dictionary", "query_word", word="serendipity", user_id="test"
|
||||||
|
)
|
||||||
|
print(f" 成功: {result.success}")
|
||||||
|
print(f" 来源: {result.source}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ MCP Manager 测试失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("🎉 直接测试完成!")
|
||||||
|
print("=" * 70)
|
||||||
|
print("\n✅ MCP集成系统架构已就绪:")
|
||||||
|
print(" ┌─────────────────────────────────────────────┐")
|
||||||
|
print(" │ MCP Manager (统一入口) │")
|
||||||
|
print(" ├─────────────────────────────────────────────┤")
|
||||||
|
print(" │ ContactAdapter │ DictionaryAdapter │ News │")
|
||||||
|
print(" ├─────────────────────────────────────────────┤")
|
||||||
|
print(" │ MCP Client -> Database -> Mock (降级) │")
|
||||||
|
print(" └─────────────────────────────────────────────┘")
|
||||||
|
print("\n📖 文档: docs/MCP_INTEGRATION.md")
|
||||||
|
print("⚙️ 配置: backend/app/mcp/mcp_config.example.yaml")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(test_mcp_direct())
|
||||||
Reference in New Issue
Block a user