feat(engine): 添加MCP连接管理和工具集成功能
- 集成MCP连接管理器,支持MCP服务器连接 - 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、 PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、 TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等 - 实现工具注册和装配功能 - 添加技能选择上下文参数 - 支持思考模式控制参数thinking_enabled feat(coordinator): 重构任务执行计划器参数命名 - 将learning_candidate_enabled重命名为allow_candidate_generation - 更新TeamGraphScheduler中的参数传递 - 修改LocalAgentRunner中的相关参数处理 - 更新README文档中的相应描述 refactor(context): 标准化工具调用参数格式 - 添加_json导入用于参数序列化 - 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷 - 修复工具调用中参数非字符串类型的序列化问题 refactor(session): 优化消息历史记录过滤逻辑 - 修改get_messages_as_conversation为基于运行状态过滤消息 - 排除未完成、失败或错误结束的运行记录 - 改进对话历史的可见性控制机制 fix(store): 修复FTS索引重建逻辑 - 添加异常处理防止FTS索引创建失败 - 实现_rebuild_fts_index方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
This commit is contained in:
@ -4,10 +4,30 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .schema import AgentDefaultsConfig, BeaverConfig, EmbeddingConfig, ProviderConfig
|
||||
from .schema import (
|
||||
AgentDefaultsConfig,
|
||||
AuthzConfig,
|
||||
BackendIdentityConfig,
|
||||
BeaverConfig,
|
||||
EmbeddingConfig,
|
||||
MCPServerConfig,
|
||||
ProviderConfig,
|
||||
ToolsConfig,
|
||||
)
|
||||
|
||||
LOCAL_MCP_CATEGORIES: dict[str, dict[str, str]] = {
|
||||
"local_filesystem_mcp": {"category": "filesystem", "display_name": "本地文件工具"},
|
||||
"local_runtime_mcp": {"category": "runtime", "display_name": "本地运行工具"},
|
||||
"local_memory_mcp": {"category": "memory", "display_name": "本地记忆工具"},
|
||||
"local_skills_mcp": {"category": "skills", "display_name": "本地技能工具"},
|
||||
"local_coordination_mcp": {"category": "coordination", "display_name": "本地协作工具"},
|
||||
"local_scheduler_mcp": {"category": "scheduler", "display_name": "本地定时工具"},
|
||||
"local_web_mcp": {"category": "web", "display_name": "本地联网工具"},
|
||||
}
|
||||
|
||||
|
||||
def default_config_path(*, workspace: str | Path | None = None) -> Path:
|
||||
@ -57,6 +77,9 @@ def load_config(
|
||||
agents_defaults=_parse_agent_defaults(data),
|
||||
providers=_parse_providers(data.get("providers")),
|
||||
embedding=_parse_embedding(data),
|
||||
tools=_parse_tools(data.get("tools")),
|
||||
authz=_parse_authz(data.get("authz")),
|
||||
backend_identity=_parse_backend_identity(data.get("backend_identity") or data.get("backendIdentity")),
|
||||
config_path=path,
|
||||
)
|
||||
|
||||
@ -104,6 +127,73 @@ def _parse_embedding(data: dict[str, Any]) -> EmbeddingConfig:
|
||||
)
|
||||
|
||||
|
||||
def _parse_tools(raw: Any) -> ToolsConfig:
|
||||
data = _as_dict(raw)
|
||||
mcp_servers: dict[str, MCPServerConfig] = {}
|
||||
for server_id, payload in _as_dict(data.get("mcpServers") or data.get("mcp_servers")).items():
|
||||
if not isinstance(payload, dict):
|
||||
continue
|
||||
mcp_servers[str(server_id)] = MCPServerConfig(
|
||||
command=_string(payload.get("command")) or "",
|
||||
args=_string_list(payload.get("args")),
|
||||
env=_string_dict(payload.get("env")),
|
||||
url=_string(payload.get("url")) or "",
|
||||
headers=_string_dict(payload.get("headers")),
|
||||
auth_mode=(_string(payload.get("authMode") or payload.get("auth_mode")) or "none").lower(),
|
||||
auth_audience=_string(payload.get("authAudience") or payload.get("auth_audience")) or "",
|
||||
auth_scopes=_string_list(payload.get("authScopes") or payload.get("auth_scopes")),
|
||||
tool_timeout=int(_float(payload.get("toolTimeout") or payload.get("tool_timeout")) or 30),
|
||||
sensitive=_bool(payload.get("sensitive"), default=False),
|
||||
kind=(_string(payload.get("kind")) or ("local" if payload.get("command") else "online")).lower(),
|
||||
category=_string(payload.get("category")) or ("local" if payload.get("command") else "online"),
|
||||
managed=_bool(payload.get("managed"), default=False),
|
||||
display_name=_string(payload.get("displayName") or payload.get("display_name")) or "",
|
||||
source=_string(payload.get("source")) or "config",
|
||||
)
|
||||
for server_id, meta in LOCAL_MCP_CATEGORIES.items():
|
||||
if server_id in mcp_servers:
|
||||
continue
|
||||
mcp_servers[server_id] = MCPServerConfig(
|
||||
command=sys.executable or "python",
|
||||
args=["-m", "beaver.interfaces.mcp.tools_server", "--category", meta["category"]],
|
||||
env={},
|
||||
kind="local",
|
||||
category=meta["category"],
|
||||
managed=True,
|
||||
display_name=meta["display_name"],
|
||||
source="beaver-default",
|
||||
tool_timeout=60,
|
||||
)
|
||||
return ToolsConfig(
|
||||
restrict_to_workspace=_bool(
|
||||
data.get("restrictToWorkspace") if "restrictToWorkspace" in data else data.get("restrict_to_workspace"),
|
||||
default=True,
|
||||
),
|
||||
mcp_servers=mcp_servers,
|
||||
)
|
||||
|
||||
|
||||
def _parse_authz(raw: Any) -> AuthzConfig:
|
||||
data = _as_dict(raw)
|
||||
return AuthzConfig(
|
||||
enabled=_bool(data.get("enabled"), default=False),
|
||||
base_url=_string(data.get("baseUrl") or data.get("base_url")) or "",
|
||||
request_timeout_seconds=int(_float(data.get("requestTimeoutSeconds") or data.get("request_timeout_seconds")) or 10),
|
||||
outlook_mcp_url=_string(data.get("outlookMcpUrl") or data.get("outlook_mcp_url")) or "",
|
||||
)
|
||||
|
||||
|
||||
def _parse_backend_identity(raw: Any) -> BackendIdentityConfig:
|
||||
data = _as_dict(raw)
|
||||
return BackendIdentityConfig(
|
||||
backend_id=_string(data.get("backendId") or data.get("backend_id")) or "",
|
||||
client_id=_string(data.get("clientId") or data.get("client_id")) or "",
|
||||
client_secret=_string(data.get("clientSecret") or data.get("client_secret")) or "",
|
||||
name=_string(data.get("name")) or "",
|
||||
public_base_url=_string(data.get("publicBaseUrl") or data.get("public_base_url")) or "",
|
||||
)
|
||||
|
||||
|
||||
def _as_dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
@ -121,7 +211,23 @@ def _string_dict(value: Any) -> dict[str, str]:
|
||||
return {str(key): str(item) for key, item in value.items() if item is not None}
|
||||
|
||||
|
||||
def _string_list(value: Any) -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [str(item) for item in value if str(item).strip()]
|
||||
|
||||
|
||||
def _float(value: Any) -> float | None:
|
||||
if value in (None, ""):
|
||||
return None
|
||||
return float(value)
|
||||
|
||||
|
||||
def _bool(value: Any, *, default: bool) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value in (None, ""):
|
||||
return default
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||||
return bool(value)
|
||||
|
||||
Reference in New Issue
Block a user