feat: 集成MCP统一外部接口管理系统
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:
2026-05-03 12:36:12 +08:00
parent 3e9462a693
commit 9c53f58165
15 changed files with 1540 additions and 519 deletions

View 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"]

View 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

View 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

View 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

View 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