Files
beaver_project/app-instance/backend/beaver/interfaces/mcp/tools_server.py
steven_li 3b0af173cc refactor(beaver): 移除Hermes相关引用和迁移代码,完善Beaver后端主线实现
移除了所有Hermes相关的命名引用,包括:
- 从.gitignore中清理相关构建缓存文件
- 将README中的beaver-home路径配置更新
- 完善backend/README.md文档说明Beaver后端主线实现
- 移除Hermes风格的相关注释和兼容性代码
- 清理nanobot环境变量兼容性处理
- 删除技能迁移和服务迁移相关功能代码
- 更新测试用例中相关命名和函数名

BREAKING CHANGE: 移除了Hermes迁移相关API和CLI命令,不再支持nanobot环境变量兼容性
2026-05-14 17:20:32 +08:00

193 lines
6.4 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")
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()