62b8fef18a
- C1 (Critical): 添加 PyYAML 解析 frontmatter,支持多行值和列表 - M1 (Major): 添加 FTS5 查询验证,防止注入攻击 - M2/M3 (Major): 工具类使用显式 wiki_vault_path 参数 - M4 (Major): 集成 aiofiles 实现真正的异步文件 I/O - M5 (Major): 改用 logger.exception() 记录完整堆栈 Co-Authored-By: Claude Dev <noreply@anthropic.com>
222 lines
7.4 KiB
Python
222 lines
7.4 KiB
Python
"""
|
|
MCP Server - Wiki MCP Server 主入口
|
|
|
|
实现 MCP 协议解析、工具注册和路由。
|
|
|
|
参考设计文档:第 2.1 节
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import sys
|
|
from typing import Any, Callable, Dict, Optional
|
|
from pathlib import Path
|
|
|
|
# MCP SDK
|
|
try:
|
|
from mcp.server.models import InitializationOptions
|
|
from mcp.server import Server, NotificationOptions
|
|
from mcp.types import Tool, TextContent
|
|
MCP_AVAILABLE = True
|
|
except ImportError:
|
|
MCP_AVAILABLE = False
|
|
logger = logging.getLogger(__name__)
|
|
logger.warning("MCP SDK not available, using mock implementation")
|
|
|
|
# 本地模块
|
|
from .storage import Database, fix_dirty_states
|
|
from .services import CacheService, QueryService, ParserService, IndexerService, GraphService
|
|
from .tools import (
|
|
WikiQueryTool, MemoryBridgeTool, WikiStatusTool,
|
|
WikiLintTool, CrossLinkerTool, TagTaxonomyTool,
|
|
WikiSynthesizeTool, DailyUpdateTool
|
|
)
|
|
|
|
# 配置
|
|
DEFAULT_WIKI_VAULT = "/Volumes/KnowledgeBase/wiki-vault"
|
|
DEFAULT_INDEX_PATH = "~/.sanguo-llmwiki/index.db"
|
|
DEFAULT_LOG_LEVEL = "INFO"
|
|
|
|
# 配置日志
|
|
logging.basicConfig(
|
|
level=getattr(logging, DEFAULT_LOG_LEVEL),
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MCPServer:
|
|
"""MCP 服务器封装类"""
|
|
|
|
def __init__(self, wiki_vault_path: str, index_path: str):
|
|
self.wiki_vault_path = wiki_vault_path
|
|
self.index_path = index_path
|
|
self.db: Optional[Database] = None
|
|
self.tools: Dict[str, Callable] = {}
|
|
self.tool_schemas: Dict[str, dict] = {}
|
|
|
|
# 如果 MCP SDK 可用,创建 Server 实例
|
|
if MCP_AVAILABLE:
|
|
self.server = Server("wiki-mcp")
|
|
self._setup_handlers()
|
|
else:
|
|
self.server = None
|
|
logger.warning("Running in mock mode (MCP SDK not available)")
|
|
|
|
async def initialize(self) -> None:
|
|
"""初始化数据库和服务"""
|
|
logger.info("Initializing Wiki MCP Server...")
|
|
|
|
# 初始化数据库
|
|
self.db = Database(self.index_path)
|
|
await self.db.connect()
|
|
|
|
# 恢复脏状态
|
|
await fix_dirty_states(self.db)
|
|
|
|
# 初始化服务
|
|
self.cache = CacheService(max_size=1000)
|
|
self.query_service = QueryService(self.db, self.cache)
|
|
self.parser_service = ParserService()
|
|
self.indexer_service = IndexerService(self.db, self.parser_service, self.wiki_vault_path)
|
|
self.graph_service = GraphService(self.db)
|
|
|
|
# 初始化工具
|
|
self._init_tools()
|
|
|
|
logger.info("Wiki MCP Server initialized")
|
|
|
|
def _init_tools(self) -> None:
|
|
"""初始化所有工具"""
|
|
wiki_query = WikiQueryTool(self.query_service)
|
|
memory_bridge = MemoryBridgeTool(self.query_service)
|
|
wiki_status = WikiStatusTool(self.query_service, self.indexer_service, self.graph_service)
|
|
wiki_lint = WikiLintTool(self.parser_service, self.query_service, self.graph_service, self.wiki_vault_path)
|
|
cross_linker = CrossLinkerTool(self.graph_service)
|
|
tag_taxonomy = TagTaxonomyTool(self.query_service)
|
|
wiki_synthesize = WikiSynthesizeTool(self.query_service, self.graph_service)
|
|
daily_update = DailyUpdateTool(self.query_service, self.indexer_service, self.wiki_vault_path)
|
|
|
|
# 注册工具
|
|
self.tools = {
|
|
"wiki_query": wiki_query.handle,
|
|
"memory_bridge": memory_bridge.handle,
|
|
"wiki_status": wiki_status.handle,
|
|
"wiki_lint": wiki_lint.handle,
|
|
"cross_linker": cross_linker.handle,
|
|
"tag_taxonomy": tag_taxonomy.handle,
|
|
"wiki_synthesize": wiki_synthesize.handle,
|
|
"daily_update": daily_update.handle
|
|
}
|
|
|
|
# 注册工具 schema
|
|
self.tool_schemas = {
|
|
"wiki_query": wiki_query.get_schema(),
|
|
"memory_bridge": memory_bridge.get_schema(),
|
|
"wiki_status": wiki_status.get_schema(),
|
|
"wiki_lint": wiki_lint.get_schema(),
|
|
"cross_linker": cross_linker.get_schema(),
|
|
"tag_taxonomy": tag_taxonomy.get_schema(),
|
|
"wiki_synthesize": wiki_synthesize.get_schema(),
|
|
"daily_update": daily_update.get_schema()
|
|
}
|
|
|
|
logger.info(f"Registered {len(self.tools)} tools")
|
|
|
|
def _setup_handlers(self) -> None:
|
|
"""设置 MCP 处理器"""
|
|
if not self.server:
|
|
return
|
|
|
|
@self.server.list_tools()
|
|
async def list_tools() -> list[Tool]:
|
|
"""列出所有可用工具"""
|
|
return [
|
|
Tool(
|
|
name=schema["name"],
|
|
description=schema["description"],
|
|
inputSchema=schema["inputSchema"]
|
|
)
|
|
for schema in self.tool_schemas.values()
|
|
]
|
|
|
|
@self.server.call_tool()
|
|
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
"""调用工具"""
|
|
if name not in self.tools:
|
|
raise ValueError(f"Unknown tool: {name}")
|
|
|
|
try:
|
|
result = await self.tools[name](**arguments)
|
|
return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
|
|
except Exception as e:
|
|
# 记录完整堆栈用于调试
|
|
logger.exception(f"Error calling tool {name} with arguments {arguments}")
|
|
# 返回用户友好的错误消息
|
|
return [TextContent(type="text", text=json.dumps({
|
|
"success": False,
|
|
"error": str(e),
|
|
"tool": name
|
|
}, ensure_ascii=False))]
|
|
|
|
async def run(self) -> None:
|
|
"""运行 MCP 服务器"""
|
|
if not self.server:
|
|
logger.error("MCP Server not available (MCP SDK not installed)")
|
|
return
|
|
|
|
# 运行服务器
|
|
async with self.server.stdio_stdio() as (read_stream, write_stream):
|
|
await self.server.run(
|
|
read_stream,
|
|
write_stream,
|
|
InitializationOptions(
|
|
server_name="wiki-mcp",
|
|
server_version="1.0.0",
|
|
capabilities=self.server.get_capabilities(
|
|
notification_options=NotificationOptions(),
|
|
experimental_capabilities={}
|
|
)
|
|
)
|
|
)
|
|
|
|
async def close(self) -> None:
|
|
"""关闭服务器"""
|
|
if self.db:
|
|
await self.db.close()
|
|
logger.info("Database connection closed")
|
|
|
|
|
|
async def main():
|
|
"""主入口"""
|
|
import os
|
|
|
|
# 从环境变量读取配置
|
|
wiki_vault_path = os.environ.get("WIKI_VAULT_PATH", DEFAULT_WIKI_VAULT)
|
|
index_path = os.path.expanduser(os.environ.get("WIKI_INDEX_PATH", DEFAULT_INDEX_PATH))
|
|
log_level = os.environ.get("LOG_LEVEL", DEFAULT_LOG_LEVEL)
|
|
|
|
# 设置日志级别
|
|
logging.getLogger().setLevel(getattr(logging, log_level))
|
|
|
|
# 检查 wiki vault 路径
|
|
if not Path(wiki_vault_path).exists():
|
|
logger.error(f"Wiki vault path does not exist: {wiki_vault_path}")
|
|
sys.exit(1)
|
|
|
|
# 创建并运行服务器
|
|
server = MCPServer(wiki_vault_path, index_path)
|
|
|
|
try:
|
|
await server.initialize()
|
|
await server.run()
|
|
except KeyboardInterrupt:
|
|
logger.info("Interrupted by user")
|
|
finally:
|
|
await server.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|