dfd8421dc6
**Storage Layer:** - Database 类(aiosqlite + WAL + 并发保护) - WikiPage 数据模型 - fix_dirty_states 恢复机制 - FTS5 全文搜索支持 - 完整的 SQLite 表结构 **Service Layer:** - CacheService(LRU 缓存 + TTL + 大小限制) - QueryService(查询服务) - ParserService(Markdown 解析) - IndexerService(索引服务) - GraphService(链接图服务) **Tool Layer (8 个 MCP Tools):** - wiki_query - FTS5 全文搜索 + 标签过滤 - memory_bridge - 按工具来源浏览 - wiki_status - 索引状态 - wiki_lint - 健康审计 - cross_linker - 缺失链接发现 - tag_taxonomy - 标签一致性 - wiki_synthesize - 跨概念综合分析 - daily_update - 日常维护 + hot.md 生成 **MCP Protocol Layer:** - MCP 协议解析和封装 - 工具注册和路由 - 错误处理和日志 - stdio 模式支持 **配置和部署:** - requirements.txt - config.example.yaml - ecosystem.config.cjs (PM2) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
219 lines
7.2 KiB
Python
219 lines
7.2 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)
|
|
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.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.error(f"Error calling tool {name}: {e}")
|
|
return [TextContent(type="text", text=json.dumps({
|
|
"success": False,
|
|
"error": str(e)
|
|
}, 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())
|