feat(mcp_module): add hooks, host LLM bridge, and models for MCP integration
- Introduced MCPHostCallbacks for optional host capabilities like sampling and logging. - Implemented MCPHostLLMBridge to handle MCP Sampling requests and bridge to LLM service. - Created models for structured data conversion between MCP SDK and internal data models, including tool content items, prompts, and resources. - Enhanced error handling and logging for better traceability during sampling operations.
This commit is contained in:
@@ -57,7 +57,7 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
|
||||
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
||||
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
||||
MMC_VERSION: str = "1.0.0"
|
||||
CONFIG_VERSION: str = "8.2.0"
|
||||
CONFIG_VERSION: str = "8.3.0"
|
||||
MODEL_CONFIG_VERSION: str = "1.13.1"
|
||||
|
||||
logger = get_logger("config")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from typing import Literal, Optional
|
||||
|
||||
import re
|
||||
|
||||
from .config_base import ConfigBase, Field
|
||||
|
||||
"""
|
||||
@@ -1569,6 +1570,225 @@ class MaiSakaConfig(ConfigBase):
|
||||
"""Maisaka终端图片预览的字符宽度"""
|
||||
|
||||
|
||||
class MCPAuthorizationConfig(ConfigBase):
|
||||
"""MCP HTTP 认证配置。"""
|
||||
|
||||
mode: Literal["none", "bearer"] = Field(
|
||||
default="none",
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "shield",
|
||||
},
|
||||
)
|
||||
"""认证模式,当前支持无认证和静态 Bearer Token"""
|
||||
|
||||
bearer_token: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "password",
|
||||
"x-icon": "key",
|
||||
},
|
||||
)
|
||||
"""静态 Bearer Token,仅在 `mode=\"bearer\"` 时使用"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 认证配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.mode == "bearer" and not self.bearer_token.strip():
|
||||
raise ValueError("MCP 使用 bearer 认证时必须填写 bearer_token")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPRootItemConfig(ConfigBase):
|
||||
"""单个 MCP Root 配置。"""
|
||||
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "power",
|
||||
},
|
||||
)
|
||||
"""是否启用当前 Root"""
|
||||
|
||||
uri: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "folder",
|
||||
},
|
||||
)
|
||||
"""Root URI,通常为 `file://` 路径 URI"""
|
||||
|
||||
name: str = Field(
|
||||
default="",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "tag",
|
||||
},
|
||||
)
|
||||
"""Root 的显示名称"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证单个 Root 配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.enabled and not self.uri.strip():
|
||||
raise ValueError("启用的 MCP Root 必须填写 uri")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPRootsConfig(ConfigBase):
|
||||
"""MCP Roots 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "folder-tree",
|
||||
},
|
||||
)
|
||||
"""是否向 MCP 服务器暴露 Roots 能力"""
|
||||
|
||||
items: list[MCPRootItemConfig] = Field(
|
||||
default_factory=lambda: [],
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "folder",
|
||||
},
|
||||
)
|
||||
"""Roots 列表"""
|
||||
|
||||
|
||||
class MCPSamplingConfig(ConfigBase):
|
||||
"""MCP Sampling 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "brain",
|
||||
},
|
||||
)
|
||||
"""是否启用 Sampling 能力声明"""
|
||||
|
||||
task_name: str = Field(
|
||||
default="planner",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "sparkles",
|
||||
},
|
||||
)
|
||||
"""执行 Sampling 请求时使用的主程序模型任务名"""
|
||||
|
||||
include_context_support: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "layers",
|
||||
},
|
||||
)
|
||||
"""是否声明支持 `includeContext` 非 `none` 语义"""
|
||||
|
||||
tool_support: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "wrench",
|
||||
},
|
||||
)
|
||||
"""是否声明支持在 Sampling 中继续使用工具"""
|
||||
|
||||
|
||||
class MCPElicitationConfig(ConfigBase):
|
||||
"""MCP Elicitation 能力配置。"""
|
||||
|
||||
enable: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "message-circle-question",
|
||||
},
|
||||
)
|
||||
"""是否启用 Elicitation 能力声明"""
|
||||
|
||||
allow_form: bool = Field(
|
||||
default=True,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "form-input",
|
||||
},
|
||||
)
|
||||
"""是否允许表单模式 Elicitation"""
|
||||
|
||||
allow_url: bool = Field(
|
||||
default=False,
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "link",
|
||||
},
|
||||
)
|
||||
"""是否允许 URL 模式 Elicitation"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 Elicitation 配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if self.enable and not (self.allow_form or self.allow_url):
|
||||
raise ValueError("启用 MCP Elicitation 时至少需要允许一种模式")
|
||||
return super().model_post_init(context)
|
||||
|
||||
|
||||
class MCPClientConfig(ConfigBase):
|
||||
"""MCP 客户端宿主能力配置。"""
|
||||
|
||||
client_name: str = Field(
|
||||
default="MaiBot",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "bot",
|
||||
},
|
||||
)
|
||||
"""MCP 客户端实现名称"""
|
||||
|
||||
client_version: str = Field(
|
||||
default="1.0.0",
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "info",
|
||||
},
|
||||
)
|
||||
"""MCP 客户端实现版本"""
|
||||
|
||||
roots: MCPRootsConfig = Field(default_factory=MCPRootsConfig)
|
||||
"""Roots 能力配置"""
|
||||
|
||||
sampling: MCPSamplingConfig = Field(default_factory=MCPSamplingConfig)
|
||||
"""Sampling 能力配置"""
|
||||
|
||||
elicitation: MCPElicitationConfig = Field(default_factory=MCPElicitationConfig)
|
||||
"""Elicitation 能力配置"""
|
||||
|
||||
|
||||
class MCPServerItemConfig(ConfigBase):
|
||||
"""单个 MCP 服务器配置。"""
|
||||
|
||||
@@ -1590,14 +1810,14 @@ class MCPServerItemConfig(ConfigBase):
|
||||
)
|
||||
"""是否启用当前 MCP 服务器"""
|
||||
|
||||
transport: Literal["stdio", "sse"] = Field(
|
||||
transport: Literal["stdio", "streamable_http"] = Field(
|
||||
default="stdio",
|
||||
json_schema_extra={
|
||||
"x-widget": "select",
|
||||
"x-icon": "shuffle",
|
||||
},
|
||||
)
|
||||
"""传输方式,可选 stdio 或 sse"""
|
||||
"""传输方式,可选 `stdio` 或 `streamable_http`"""
|
||||
|
||||
command: str = Field(
|
||||
default="",
|
||||
@@ -1633,7 +1853,7 @@ class MCPServerItemConfig(ConfigBase):
|
||||
"x-icon": "link",
|
||||
},
|
||||
)
|
||||
"""sse 模式下的服务地址"""
|
||||
"""`streamable_http` 模式下的 MCP 端点地址"""
|
||||
|
||||
headers: dict[str, str] = Field(
|
||||
default_factory=lambda: {},
|
||||
@@ -1642,10 +1862,40 @@ class MCPServerItemConfig(ConfigBase):
|
||||
"x-icon": "file-json",
|
||||
},
|
||||
)
|
||||
"""sse 模式下附加的请求头"""
|
||||
"""HTTP 模式下附加的请求头"""
|
||||
|
||||
http_timeout_seconds: float = Field(
|
||||
default=30.0,
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"x-widget": "number",
|
||||
"x-icon": "clock-3",
|
||||
},
|
||||
)
|
||||
"""HTTP 请求超时时间,单位秒"""
|
||||
|
||||
read_timeout_seconds: float = Field(
|
||||
default=300.0,
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"x-widget": "number",
|
||||
"x-icon": "timer",
|
||||
},
|
||||
)
|
||||
"""会话读取超时时间,单位秒"""
|
||||
|
||||
authorization: MCPAuthorizationConfig = Field(default_factory=MCPAuthorizationConfig)
|
||||
"""HTTP 认证配置"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 服务器配置。"""
|
||||
"""验证 MCP 服务器配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
if not self.name.strip():
|
||||
raise ValueError("MCPServerItemConfig.name 不能为空")
|
||||
@@ -1653,8 +1903,8 @@ class MCPServerItemConfig(ConfigBase):
|
||||
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")
|
||||
if self.transport == "streamable_http" and not self.url.strip():
|
||||
raise ValueError(f"MCP 服务器 {self.name} 使用 streamable_http 时必须填写 url")
|
||||
|
||||
return super().model_post_init(context)
|
||||
|
||||
@@ -1673,6 +1923,9 @@ class MCPConfig(ConfigBase):
|
||||
)
|
||||
"""是否启用 MCP(Model Context Protocol)"""
|
||||
|
||||
client: MCPClientConfig = Field(default_factory=MCPClientConfig)
|
||||
"""MCP 客户端宿主能力配置"""
|
||||
|
||||
servers: list[MCPServerItemConfig] = Field(
|
||||
default_factory=lambda: [],
|
||||
json_schema_extra={
|
||||
@@ -1683,7 +1936,14 @@ class MCPConfig(ConfigBase):
|
||||
"""_wrap_MCP 服务器配置列表"""
|
||||
|
||||
def model_post_init(self, context: Optional[dict] = None) -> None:
|
||||
"""验证 MCP 总配置。"""
|
||||
"""验证 MCP 总配置。
|
||||
|
||||
Args:
|
||||
context: Pydantic 传入的上下文对象。
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
server_names = [server.name.strip() for server in self.servers if server.name.strip()]
|
||||
if len(server_names) != len(set(server_names)):
|
||||
|
||||
Reference in New Issue
Block a user