feat: 重构 MCP 配置管理,移除旧配置文件,更新为使用全局配置
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"tavily": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": [
|
|
||||||
"-y",
|
|
||||||
"mcp-remote",
|
|
||||||
"https://mcp.tavily.com/mcp/?tavilyApiKey=YOUR_API_KEY_HERE"
|
|
||||||
],
|
|
||||||
"env": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ MaiSaka CLI and conversation loop.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -259,7 +258,7 @@ class BufferCLI:
|
|||||||
knowledge_result = results[0] if results else None
|
knowledge_result = results[0] if results else None
|
||||||
if isinstance(knowledge_result, Exception):
|
if isinstance(knowledge_result, Exception):
|
||||||
console.print(f"[warning]知识分析失败:{knowledge_result}[/warning]")
|
console.print(f"[warning]知识分析失败:{knowledge_result}[/warning]")
|
||||||
elif knowledge_result:
|
elif isinstance(knowledge_result, str) and knowledge_result.strip():
|
||||||
knowledge_analysis = knowledge_result
|
knowledge_analysis = knowledge_result
|
||||||
if global_config.maisaka.show_thinking:
|
if global_config.maisaka.show_thinking:
|
||||||
console.print(
|
console.print(
|
||||||
@@ -333,7 +332,7 @@ class BufferCLI:
|
|||||||
should_stop = True
|
should_stop = True
|
||||||
|
|
||||||
elif tool_call.func_name == "reply":
|
elif tool_call.func_name == "reply":
|
||||||
reply = await self._generate_visible_reply(chat_history, response.content)
|
reply = await self._generate_visible_reply(chat_history, response.content or "")
|
||||||
chat_history.append(
|
chat_history.append(
|
||||||
ToolResultMessage(
|
ToolResultMessage(
|
||||||
content="已生成并记录可见回复。",
|
content="已生成并记录可见回复。",
|
||||||
@@ -384,8 +383,7 @@ class BufferCLI:
|
|||||||
|
|
||||||
async def _init_mcp(self) -> None:
|
async def _init_mcp(self) -> None:
|
||||||
"""初始化 MCP 服务并注册暴露的工具。"""
|
"""初始化 MCP 服务并注册暴露的工具。"""
|
||||||
config_path = Path(__file__).resolve().parents[2] / "config" / "mcp_config.json"
|
self._mcp_manager = await MCPManager.from_app_config(global_config.mcp)
|
||||||
self._mcp_manager = await MCPManager.from_config(str(config_path))
|
|
||||||
|
|
||||||
if self._mcp_manager and self._chat_loop_service:
|
if self._mcp_manager and self._chat_loop_service:
|
||||||
mcp_tools = self._mcp_manager.get_openai_tools()
|
mcp_tools = self._mcp_manager.get_openai_tools()
|
||||||
@@ -429,10 +427,10 @@ class BufferCLI:
|
|||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
"""主交互循环。"""
|
"""主交互循环。"""
|
||||||
if global_config.maisaka.enable_mcp:
|
if global_config.mcp.enable:
|
||||||
await self._init_mcp()
|
await self._init_mcp()
|
||||||
else:
|
else:
|
||||||
console.print("[muted]MCP 已禁用(ENABLE_MCP=false)[/muted]")
|
console.print("[muted]MCP 已禁用(mcp.enable=false)[/muted]")
|
||||||
|
|
||||||
self._reader.start(asyncio.get_event_loop())
|
self._reader.start(asyncio.get_event_loop())
|
||||||
self._show_banner()
|
self._show_banner()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Mapping, Sequence, TypeVar
|
from typing import Any, Callable, Mapping, Sequence, TypeVar, cast
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
@@ -27,6 +27,7 @@ from .official_configs import (
|
|||||||
LPMMKnowledgeConfig,
|
LPMMKnowledgeConfig,
|
||||||
MaiSakaConfig,
|
MaiSakaConfig,
|
||||||
MaimMessageConfig,
|
MaimMessageConfig,
|
||||||
|
MCPConfig,
|
||||||
PluginRuntimeConfig,
|
PluginRuntimeConfig,
|
||||||
MemoryConfig,
|
MemoryConfig,
|
||||||
MessageReceiveConfig,
|
MessageReceiveConfig,
|
||||||
@@ -56,7 +57,7 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
|
|||||||
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
||||||
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
||||||
MMC_VERSION: str = "1.0.0"
|
MMC_VERSION: str = "1.0.0"
|
||||||
CONFIG_VERSION: str = "8.1.11"
|
CONFIG_VERSION: str = "8.2.0"
|
||||||
MODEL_CONFIG_VERSION: str = "1.13.1"
|
MODEL_CONFIG_VERSION: str = "1.13.1"
|
||||||
|
|
||||||
logger = get_logger("config")
|
logger = get_logger("config")
|
||||||
@@ -134,6 +135,9 @@ class Config(ConfigBase):
|
|||||||
maisaka: MaiSakaConfig = Field(default_factory=MaiSakaConfig)
|
maisaka: MaiSakaConfig = Field(default_factory=MaiSakaConfig)
|
||||||
"""MaiSaka对话系统配置类"""
|
"""MaiSaka对话系统配置类"""
|
||||||
|
|
||||||
|
mcp: MCPConfig = Field(default_factory=MCPConfig)
|
||||||
|
"""MCP 配置类"""
|
||||||
|
|
||||||
plugin_runtime: PluginRuntimeConfig = Field(default_factory=PluginRuntimeConfig)
|
plugin_runtime: PluginRuntimeConfig = Field(default_factory=PluginRuntimeConfig)
|
||||||
"""插件运行时配置类"""
|
"""插件运行时配置类"""
|
||||||
|
|
||||||
@@ -332,7 +336,12 @@ class ConfigManager:
|
|||||||
changed_scopes: 本次热重载命中的配置范围。
|
changed_scopes: 本次热重载命中的配置范围。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = callback(changed_scopes) if self._callback_accepts_scopes(callback) else callback()
|
if self._callback_accepts_scopes(callback):
|
||||||
|
callback_with_scopes = cast(Callable[[Sequence[str]], object], callback)
|
||||||
|
result = callback_with_scopes(changed_scopes)
|
||||||
|
else:
|
||||||
|
callback_without_scopes = cast(Callable[[], object], callback)
|
||||||
|
result = callback_without_scopes()
|
||||||
if asyncio.iscoroutine(result):
|
if asyncio.iscoroutine(result):
|
||||||
await result
|
await result
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from .config_base import ConfigBase, Field
|
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Literal
|
from typing import Literal, Optional
|
||||||
|
|
||||||
|
from .config_base import ConfigBase, Field
|
||||||
|
|
||||||
"""
|
"""
|
||||||
须知:
|
须知:
|
||||||
@@ -1493,15 +1494,6 @@ class MaiSakaConfig(ConfigBase):
|
|||||||
)
|
)
|
||||||
"""启用知识库模块"""
|
"""启用知识库模块"""
|
||||||
|
|
||||||
enable_mcp: bool = Field(
|
|
||||||
default=True,
|
|
||||||
json_schema_extra={
|
|
||||||
"x-widget": "switch",
|
|
||||||
"x-icon": "zap",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
"""启用 MCP (Model Context Protocol) 支持"""
|
|
||||||
|
|
||||||
show_analyze_cognition_prompt: bool = Field(
|
show_analyze_cognition_prompt: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -1577,6 +1569,128 @@ class MaiSakaConfig(ConfigBase):
|
|||||||
"""Maisaka终端图片预览的字符宽度"""
|
"""Maisaka终端图片预览的字符宽度"""
|
||||||
|
|
||||||
|
|
||||||
|
class MCPServerItemConfig(ConfigBase):
|
||||||
|
"""单个 MCP 服务器配置。"""
|
||||||
|
|
||||||
|
name: str = Field(
|
||||||
|
default="",
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "tag",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""服务器名称,必须唯一"""
|
||||||
|
|
||||||
|
enabled: bool = Field(
|
||||||
|
default=True,
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "switch",
|
||||||
|
"x-icon": "power",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""是否启用当前 MCP 服务器"""
|
||||||
|
|
||||||
|
transport: Literal["stdio", "sse"] = Field(
|
||||||
|
default="stdio",
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "select",
|
||||||
|
"x-icon": "shuffle",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""传输方式,可选 stdio 或 sse"""
|
||||||
|
|
||||||
|
command: str = Field(
|
||||||
|
default="",
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "terminal",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""stdio 模式下启动服务器的命令"""
|
||||||
|
|
||||||
|
args: list[str] = Field(
|
||||||
|
default_factory=lambda: [],
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "custom",
|
||||||
|
"x-icon": "list",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""stdio 模式下的命令参数列表"""
|
||||||
|
|
||||||
|
env: dict[str, str] = Field(
|
||||||
|
default_factory=lambda: {},
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "custom",
|
||||||
|
"x-icon": "variable",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""stdio 模式下附加的环境变量"""
|
||||||
|
|
||||||
|
url: str = Field(
|
||||||
|
default="",
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "link",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""sse 模式下的服务地址"""
|
||||||
|
|
||||||
|
headers: dict[str, str] = Field(
|
||||||
|
default_factory=lambda: {},
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "custom",
|
||||||
|
"x-icon": "file-json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""sse 模式下附加的请求头"""
|
||||||
|
|
||||||
|
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||||
|
"""验证 MCP 服务器配置。"""
|
||||||
|
|
||||||
|
if not self.name.strip():
|
||||||
|
raise ValueError("MCPServerItemConfig.name 不能为空")
|
||||||
|
|
||||||
|
if self.transport == "stdio" and not self.command.strip():
|
||||||
|
raise ValueError(f"MCP 服务器 {self.name} 使用 stdio 时必须填写 command")
|
||||||
|
|
||||||
|
if self.transport == "sse" and not self.url.strip():
|
||||||
|
raise ValueError(f"MCP 服务器 {self.name} 使用 sse 时必须填写 url")
|
||||||
|
|
||||||
|
return super().model_post_init(context)
|
||||||
|
|
||||||
|
|
||||||
|
class MCPConfig(ConfigBase):
|
||||||
|
"""MCP 总配置。"""
|
||||||
|
|
||||||
|
__ui_parent__ = "maisaka"
|
||||||
|
|
||||||
|
enable: bool = Field(
|
||||||
|
default=True,
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "switch",
|
||||||
|
"x-icon": "zap",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""是否启用 MCP(Model Context Protocol)"""
|
||||||
|
|
||||||
|
servers: list[MCPServerItemConfig] = Field(
|
||||||
|
default_factory=lambda: [],
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "custom",
|
||||||
|
"x-icon": "server",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""_wrap_MCP 服务器配置列表"""
|
||||||
|
|
||||||
|
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||||
|
"""验证 MCP 总配置。"""
|
||||||
|
|
||||||
|
server_names = [server.name.strip() for server in self.servers if server.name.strip()]
|
||||||
|
if len(server_names) != len(set(server_names)):
|
||||||
|
raise ValueError("MCP 配置中的服务器名称不能重复")
|
||||||
|
return super().model_post_init(context)
|
||||||
|
|
||||||
|
|
||||||
class PluginRuntimeConfig(ConfigBase):
|
class PluginRuntimeConfig(ConfigBase):
|
||||||
"""插件运行时配置类"""
|
"""插件运行时配置类"""
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Sequence
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
@@ -133,14 +133,14 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
self._prompts_loaded = True
|
self._prompts_loaded = True
|
||||||
|
|
||||||
def set_extra_tools(self, tools: List[ToolDefinitionInput]) -> None:
|
def set_extra_tools(self, tools: Sequence[ToolDefinitionInput]) -> None:
|
||||||
"""设置额外工具定义。
|
"""设置额外工具定义。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tools: 兼容旧接口的额外工具定义列表。
|
tools: 兼容旧接口的额外工具定义列表。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._extra_tools = normalize_tool_options(tools) or []
|
self._extra_tools = normalize_tool_options(list(tools)) or []
|
||||||
|
|
||||||
def set_tool_registry(self, tool_registry: ToolRegistry | None) -> None:
|
def set_tool_registry(self, tool_registry: ToolRegistry | None) -> None:
|
||||||
"""设置统一工具注册表。
|
"""设置统一工具注册表。
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Maisaka 非 CLI 运行时。"""
|
"""Maisaka 非 CLI 运行时。"""
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal, Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -91,7 +90,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
self._ensure_background_tasks_running()
|
self._ensure_background_tasks_running()
|
||||||
return
|
return
|
||||||
|
|
||||||
if global_config.maisaka.enable_mcp:
|
if global_config.mcp.enable:
|
||||||
await self._init_mcp()
|
await self._init_mcp()
|
||||||
|
|
||||||
self._running = True
|
self._running = True
|
||||||
@@ -386,8 +385,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
|
|
||||||
async def _init_mcp(self) -> None:
|
async def _init_mcp(self) -> None:
|
||||||
"""初始化 MCP 工具并注册到统一工具层。"""
|
"""初始化 MCP 工具并注册到统一工具层。"""
|
||||||
config_path = Path(__file__).resolve().parents[2] / "config" / "mcp_config.json"
|
self._mcp_manager = await MCPManager.from_app_config(global_config.mcp)
|
||||||
self._mcp_manager = await MCPManager.from_config(str(config_path))
|
|
||||||
if self._mcp_manager is None:
|
if self._mcp_manager is None:
|
||||||
logger.info(f"{self.log_prefix} MCP 管理器不可用")
|
logger.info(f"{self.log_prefix} MCP 管理器不可用")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ MCP (Model Context Protocol) 客户端包。
|
|||||||
提供 MCPManager 用于管理 MCP 服务器连接、发现工具、调用工具。
|
提供 MCPManager 用于管理 MCP 服务器连接、发现工具、调用工具。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
|
from src.config.config import global_config
|
||||||
from .manager import MCPManager
|
from .manager import MCPManager
|
||||||
|
|
||||||
manager = await MCPManager.from_config("config/mcp_config.json")
|
manager = await MCPManager.from_app_config(global_config.mcp)
|
||||||
if manager:
|
if manager:
|
||||||
tools = manager.get_openai_tools() # 获取 OpenAI 格式工具列表
|
tools = manager.get_openai_tools() # 获取 OpenAI 格式工具列表
|
||||||
result = await manager.call_tool(name, args) # 调用工具
|
result = await manager.call_tool(name, args) # 调用工具
|
||||||
|
|||||||
@@ -1,56 +1,36 @@
|
|||||||
"""
|
"""MCP 运行时配置转换。
|
||||||
MCP 配置加载与验证。
|
|
||||||
从 config/mcp_config.json 读取 MCP 服务器定义,解析为结构化配置对象。
|
|
||||||
|
|
||||||
配置格式示例:
|
负责将主程序官方配置中的 MCP 配置转换为运行时使用的结构化对象。
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"filesystem": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "C:/Users"],
|
|
||||||
"env": {}
|
|
||||||
},
|
|
||||||
"remote-api": {
|
|
||||||
"url": "http://localhost:8080/sse",
|
|
||||||
"headers": {"Authorization": "Bearer xxx"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- command + args: Stdio 传输(启动子进程)
|
|
||||||
- url: SSE 传输(连接远程服务器)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from typing import TYPE_CHECKING
|
||||||
from typing import Optional
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
from src.cli.console import console
|
if TYPE_CHECKING:
|
||||||
|
from src.config.official_configs import MCPConfig
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_MCP_CONFIG_PATH = Path(__file__).resolve().parents[2] / "config" / "mcp_config.json"
|
@dataclass(slots=True)
|
||||||
|
class MCPServerRuntimeConfig:
|
||||||
|
"""单个 MCP 服务器的运行时配置。"""
|
||||||
@dataclass
|
|
||||||
class MCPServerConfig:
|
|
||||||
"""单个 MCP 服务器配置。"""
|
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
command: str = ""
|
||||||
# ── Stdio 传输 ──
|
|
||||||
command: Optional[str] = None
|
|
||||||
args: list[str] = field(default_factory=list)
|
args: list[str] = field(default_factory=list)
|
||||||
env: Optional[dict[str, str]] = None
|
env: dict[str, str] = field(default_factory=dict)
|
||||||
|
url: str = ""
|
||||||
# ── SSE 传输 ──
|
|
||||||
url: Optional[str] = None
|
|
||||||
headers: dict[str, str] = field(default_factory=dict)
|
headers: dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transport_type(self) -> str:
|
def transport_type(self) -> str:
|
||||||
"""返回传输类型: 'stdio' / 'sse' / 'unknown'。"""
|
"""返回当前服务器的传输类型。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: ``stdio``、``sse`` 或 ``unknown``。
|
||||||
|
"""
|
||||||
|
|
||||||
if self.command:
|
if self.command:
|
||||||
return "stdio"
|
return "stdio"
|
||||||
if self.url:
|
if self.url:
|
||||||
@@ -58,50 +38,33 @@ class MCPServerConfig:
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
def load_mcp_config(config_path: str = str(DEFAULT_MCP_CONFIG_PATH)) -> list[MCPServerConfig]:
|
def build_mcp_server_runtime_configs(mcp_config: "MCPConfig") -> list[MCPServerRuntimeConfig]:
|
||||||
"""
|
"""将官方 MCP 配置转换为运行时配置列表。
|
||||||
从配置文件加载 MCP 服务器列表。
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config_path: 配置文件路径
|
mcp_config: 主程序中的 MCP 官方配置对象。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
解析后的 MCPServerConfig 列表;文件不存在或为空时返回空列表。
|
list[MCPServerRuntimeConfig]: 启用且配置完整的 MCP 服务器列表。
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(config_path):
|
|
||||||
|
if not mcp_config.enable:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
runtime_configs: list[MCPServerRuntimeConfig] = []
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
for server in mcp_config.servers:
|
||||||
data = json.load(f)
|
if not server.enabled:
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
|
||||||
console.print(f"[warning]⚠️ 读取 MCP 配置失败: {e}[/warning]")
|
|
||||||
return []
|
|
||||||
|
|
||||||
mcp_servers = data.get("mcpServers", {})
|
|
||||||
if not isinstance(mcp_servers, dict):
|
|
||||||
console.print("[warning]⚠️ MCP 配置中的 mcpServers 格式无效[/warning]")
|
|
||||||
return []
|
|
||||||
|
|
||||||
configs: list[MCPServerConfig] = []
|
|
||||||
for name, cfg in mcp_servers.items():
|
|
||||||
if not isinstance(cfg, dict):
|
|
||||||
console.print(f"[warning]⚠️ MCP 服务器 '{name}' 配置格式无效,已跳过[/warning]")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
server = MCPServerConfig(
|
runtime_configs.append(
|
||||||
name=name,
|
MCPServerRuntimeConfig(
|
||||||
command=cfg.get("command"),
|
name=server.name.strip(),
|
||||||
args=cfg.get("args", []),
|
command=server.command.strip(),
|
||||||
env=cfg.get("env"),
|
args=[str(argument) for argument in server.args],
|
||||||
url=cfg.get("url"),
|
env={str(key): str(value) for key, value in server.env.items()},
|
||||||
headers=cfg.get("headers", {}),
|
url=server.url.strip(),
|
||||||
|
headers={str(key): str(value) for key, value in server.headers.items()},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if server.transport_type == "unknown":
|
return runtime_configs
|
||||||
console.print(f"[warning]⚠️ MCP 服务器 '{name}' 缺少 command 或 url,已跳过[/warning]")
|
|
||||||
continue
|
|
||||||
|
|
||||||
configs.append(server)
|
|
||||||
|
|
||||||
return configs
|
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ from src.core.tooling import ToolExecutionResult
|
|||||||
|
|
||||||
from src.cli.console import console
|
from src.cli.console import console
|
||||||
|
|
||||||
from .config import MCPServerConfig
|
from .config import MCPServerRuntimeConfig
|
||||||
|
|
||||||
# ──────────────────── MCP SDK 可选导入 ────────────────────
|
# ──────────────────── MCP SDK 可选导入 ────────────────────
|
||||||
#
|
#
|
||||||
# mcp 是可选依赖。如果未安装,MCP_AVAILABLE = False,
|
# mcp 是可选依赖。如果未安装,MCP_AVAILABLE = False,
|
||||||
# MCPManager.from_config() 会检测到并返回 None,不影响主程序运行。
|
# MCPManager.from_app_config() 会检测到并返回 None,不影响主程序运行。
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from mcp import ClientSession
|
from mcp import ClientSession
|
||||||
@@ -44,15 +44,20 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
class MCPConnection:
|
class MCPConnection:
|
||||||
"""
|
"""管理单个 MCP 服务器的连接生命周期。
|
||||||
管理单个 MCP 服务器的连接生命周期。
|
|
||||||
|
|
||||||
支持两种传输方式:
|
支持两种传输方式:
|
||||||
- Stdio: 启动子进程,通过 stdin/stdout 通信
|
- Stdio: 启动子进程,通过 stdin/stdout 通信
|
||||||
- SSE: 连接远程 HTTP SSE 端点
|
- SSE: 连接远程 HTTP SSE 端点
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: MCPServerConfig):
|
def __init__(self, config: MCPServerRuntimeConfig) -> None:
|
||||||
|
"""初始化单个 MCP 连接。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 当前服务器的运行时配置。
|
||||||
|
"""
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
self.session: Optional[Any] = None # mcp.ClientSession
|
self.session: Optional[Any] = None # mcp.ClientSession
|
||||||
self.tools: list = [] # mcp Tool objects
|
self.tools: list = [] # mcp Tool objects
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ MaiSaka - MCP 管理器
|
|||||||
管理所有 MCP 服务器连接,提供统一的工具发现与调用接口。
|
管理所有 MCP 服务器连接,提供统一的工具发现与调用接口。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
from src.cli.console import console
|
from src.cli.console import console
|
||||||
from src.core.tooling import (
|
from src.core.tooling import (
|
||||||
@@ -13,9 +13,12 @@ from src.core.tooling import (
|
|||||||
build_tool_detailed_description,
|
build_tool_detailed_description,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .config import DEFAULT_MCP_CONFIG_PATH, MCPServerConfig, load_mcp_config
|
from .config import MCPServerRuntimeConfig, build_mcp_server_runtime_configs
|
||||||
from .connection import MCPConnection, MCP_AVAILABLE
|
from .connection import MCPConnection, MCP_AVAILABLE
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from src.config.official_configs import MCPConfig
|
||||||
|
|
||||||
# 内置工具名称集合 —— MCP 工具不允许与这些名称冲突
|
# 内置工具名称集合 —— MCP 工具不允许与这些名称冲突
|
||||||
BUILTIN_TOOL_NAMES = frozenset(
|
BUILTIN_TOOL_NAMES = frozenset(
|
||||||
{
|
{
|
||||||
@@ -31,37 +34,38 @@ BUILTIN_TOOL_NAMES = frozenset(
|
|||||||
|
|
||||||
|
|
||||||
class MCPManager:
|
class MCPManager:
|
||||||
"""
|
"""MCP 服务器连接管理器。
|
||||||
MCP 服务器连接管理器。
|
|
||||||
|
|
||||||
职责:
|
职责:
|
||||||
- 根据配置文件连接所有 MCP 服务器
|
- 根据主程序官方配置连接所有 MCP 服务器
|
||||||
- 将 MCP 工具转换为 OpenAI function calling 格式
|
- 将 MCP 工具转换为 OpenAI function calling 格式
|
||||||
- 路由工具调用到正确的 MCP 服务器
|
- 路由工具调用到正确的 MCP 服务器
|
||||||
- 统一管理连接生命周期
|
- 统一管理连接生命周期
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
|
"""初始化 MCP 管理器。"""
|
||||||
|
|
||||||
self._connections: dict[str, MCPConnection] = {} # server_name → connection
|
self._connections: dict[str, MCPConnection] = {} # server_name → connection
|
||||||
self._tool_to_server: dict[str, str] = {} # tool_name → server_name
|
self._tool_to_server: dict[str, str] = {} # tool_name → server_name
|
||||||
|
|
||||||
# ──────── 工厂方法 ────────
|
# ──────── 工厂方法 ────────
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_config(
|
async def from_app_config(
|
||||||
cls,
|
cls,
|
||||||
config_path: str = str(DEFAULT_MCP_CONFIG_PATH),
|
mcp_config: "MCPConfig",
|
||||||
) -> Optional["MCPManager"]:
|
) -> Optional["MCPManager"]:
|
||||||
"""
|
"""
|
||||||
从配置文件创建并初始化 MCPManager。
|
从官方配置创建并初始化 MCPManager。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config_path: mcp_config.json 文件路径
|
mcp_config: 主程序中的 MCP 配置对象。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
初始化完成的 MCPManager;无配置或全部连接失败时返回 None。
|
初始化完成的 MCPManager;无可用配置或全部连接失败时返回 None。
|
||||||
"""
|
"""
|
||||||
configs = load_mcp_config(config_path)
|
configs = build_mcp_server_runtime_configs(mcp_config)
|
||||||
if not configs:
|
if not configs:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -80,7 +84,7 @@ class MCPManager:
|
|||||||
|
|
||||||
# ──────── 连接管理 ────────
|
# ──────── 连接管理 ────────
|
||||||
|
|
||||||
async def _connect_all(self, configs: list[MCPServerConfig]) -> None:
|
async def _connect_all(self, configs: list[MCPServerRuntimeConfig]) -> None:
|
||||||
"""连接所有配置的 MCP 服务器,跳过失败的连接。"""
|
"""连接所有配置的 MCP 服务器,跳过失败的连接。"""
|
||||||
for cfg in configs:
|
for cfg in configs:
|
||||||
conn = MCPConnection(cfg)
|
conn = MCPConnection(cfg)
|
||||||
|
|||||||
Reference in New Issue
Block a user