- 集成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方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
193 lines
6.5 KiB
Python
193 lines
6.5 KiB
Python
"""Beaver local tools as real stdio MCP servers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import mcp.types as types
|
|
from mcp.server.lowlevel import Server
|
|
from mcp.server.lowlevel.server import NotificationOptions
|
|
from mcp.server.models import InitializationOptions
|
|
from mcp.server.stdio import stdio_server
|
|
|
|
from beaver.engine.session import SessionManager
|
|
from beaver.memory.curated.store import MemoryStore
|
|
from beaver.services.cron_service import CronService
|
|
from beaver.skills import SkillsLoader
|
|
from beaver.skills.drafts import DraftService
|
|
from beaver.skills.specs import SkillSpecStore
|
|
from beaver.tools.base import BaseTool, ObjectBackedTool, ToolContext
|
|
from beaver.tools.builtins import (
|
|
ClarifyTool,
|
|
CronTool,
|
|
DelegateTool,
|
|
ExecuteCodeTool,
|
|
ListDirectoryTool,
|
|
MemoryTool,
|
|
PatchFileTool,
|
|
ProcessTool,
|
|
ReadFileTool,
|
|
SearchFilesTool,
|
|
SendMessageTool,
|
|
SkillManageTool,
|
|
SkillViewTool,
|
|
SkillsListTool,
|
|
SpawnTool,
|
|
TerminalTool,
|
|
TodoTool,
|
|
WebFetchTool,
|
|
WebSearchTool,
|
|
WriteFileTool,
|
|
)
|
|
|
|
|
|
LOCAL_TOOL_CATEGORIES = {
|
|
"filesystem": "Beaver Local Filesystem Tools",
|
|
"runtime": "Beaver Local Runtime Tools",
|
|
"memory": "Beaver Local Memory Tools",
|
|
"skills": "Beaver Local Skills Tools",
|
|
"coordination": "Beaver Local Coordination Tools",
|
|
"scheduler": "Beaver Local Scheduler Tools",
|
|
"web": "Beaver Local Web Tools",
|
|
}
|
|
|
|
|
|
def _workspace_path(value: str | None = None) -> Path:
|
|
raw = value or os.getenv("BEAVER_WORKSPACE") or os.getenv("NANOBOT_WORKSPACE")
|
|
if raw:
|
|
return Path(raw).expanduser().resolve()
|
|
return Path.cwd()
|
|
|
|
|
|
def _json_content(value: str) -> dict[str, Any]:
|
|
try:
|
|
parsed = json.loads(value)
|
|
return parsed if isinstance(parsed, dict) else {"success": True, "result": parsed}
|
|
except json.JSONDecodeError:
|
|
return {"success": True, "content": value}
|
|
|
|
|
|
def _category_tools(category: str, workspace: Path) -> tuple[list[BaseTool], ToolContext]:
|
|
skill_store = SkillSpecStore(workspace)
|
|
skills_loader = SkillsLoader(workspace, skill_store=skill_store)
|
|
draft_service = DraftService(skill_store)
|
|
services = {
|
|
"skills_loader": skills_loader,
|
|
"draft_service": draft_service,
|
|
}
|
|
context = ToolContext(workspace=str(workspace), services=services)
|
|
|
|
if category == "filesystem":
|
|
tools: list[BaseTool] = [
|
|
ObjectBackedTool(ListDirectoryTool()),
|
|
ObjectBackedTool(ReadFileTool()),
|
|
ObjectBackedTool(SearchFilesTool()),
|
|
ObjectBackedTool(WriteFileTool()),
|
|
ObjectBackedTool(PatchFileTool()),
|
|
]
|
|
elif category == "runtime":
|
|
tools = [
|
|
ObjectBackedTool(TerminalTool()),
|
|
ObjectBackedTool(ProcessTool()),
|
|
ObjectBackedTool(ExecuteCodeTool()),
|
|
]
|
|
elif category == "memory":
|
|
session_manager = SessionManager(workspace)
|
|
memory_store = MemoryStore(workspace / "memory" / "curated")
|
|
memory_store.load_from_disk()
|
|
tools = [
|
|
ObjectBackedTool(MemoryTool(store=memory_store)),
|
|
ObjectBackedTool(__import__("beaver.tools.builtins.session_search", fromlist=["SessionSearchTool"]).SessionSearchTool(db=session_manager)),
|
|
]
|
|
elif category == "skills":
|
|
tools = [
|
|
ObjectBackedTool(SkillViewTool(loader=skills_loader)),
|
|
SkillsListTool(),
|
|
SkillManageTool(),
|
|
]
|
|
elif category == "coordination":
|
|
tools = [
|
|
ObjectBackedTool(TodoTool()),
|
|
ObjectBackedTool(ClarifyTool()),
|
|
ObjectBackedTool(DelegateTool()),
|
|
ObjectBackedTool(SpawnTool()),
|
|
ObjectBackedTool(SendMessageTool()),
|
|
]
|
|
elif category == "scheduler":
|
|
services["cron_service"] = CronService(workspace / "cron" / "jobs.json")
|
|
tools = [CronTool()]
|
|
elif category == "web":
|
|
tools = [
|
|
ObjectBackedTool(WebFetchTool()),
|
|
ObjectBackedTool(WebSearchTool()),
|
|
]
|
|
else:
|
|
raise ValueError(f"Unknown local tool category: {category}")
|
|
return tools, context
|
|
|
|
|
|
def create_tools_server(*, category: str, workspace: str | None = None) -> Server:
|
|
workspace_path = _workspace_path(workspace)
|
|
tools, context = _category_tools(category, workspace_path)
|
|
tool_map = {tool.spec.name: tool for tool in tools}
|
|
server = Server(LOCAL_TOOL_CATEGORIES.get(category, f"Beaver Local {category} Tools"))
|
|
|
|
@server.list_tools()
|
|
async def list_tools() -> list[types.Tool]:
|
|
return [
|
|
types.Tool(
|
|
name=tool.spec.name,
|
|
description=tool.spec.description,
|
|
inputSchema=tool.spec.input_schema,
|
|
)
|
|
for tool in tools
|
|
]
|
|
|
|
@server.call_tool(validate_input=True)
|
|
async def call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
tool = tool_map.get(name)
|
|
if tool is None:
|
|
return {"success": False, "error": f"Unknown tool: {name}"}
|
|
result = await tool.invoke(arguments or {}, context)
|
|
if result.raw_output is not None and isinstance(result.raw_output, dict):
|
|
return result.raw_output
|
|
payload = _json_content(result.content)
|
|
if "success" not in payload:
|
|
payload["success"] = bool(result.success)
|
|
if result.error and "error" not in payload:
|
|
payload["error"] = result.error
|
|
return payload
|
|
|
|
return server
|
|
|
|
|
|
async def _run_stdio(category: str, workspace: str | None) -> None:
|
|
server = create_tools_server(category=category, workspace=workspace)
|
|
async with stdio_server() as (read_stream, write_stream):
|
|
await server.run(
|
|
read_stream,
|
|
write_stream,
|
|
InitializationOptions(
|
|
server_name=LOCAL_TOOL_CATEGORIES.get(category, f"beaver-{category}"),
|
|
server_version="0.1.0",
|
|
capabilities=server.get_capabilities(notification_options=NotificationOptions(), experimental_capabilities={}),
|
|
),
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Run a Beaver local tool category as a stdio MCP server.")
|
|
parser.add_argument("--category", choices=sorted(LOCAL_TOOL_CATEGORIES), required=True)
|
|
parser.add_argument("--workspace", default=None)
|
|
args = parser.parse_args()
|
|
asyncio.run(_run_stdio(args.category, args.workspace))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|