package main import ( "fmt" "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" "github.com/mark3labs/mcp-go/server" "fwh-toolbox-server/cache" "fwh-toolbox-server/config" "fwh-toolbox-server/database" "fwh-toolbox-server/logger" "fwh-toolbox-server/repositories" "fwh-toolbox-server/utils/filestore" minioUtil "fwh-toolbox-server/utils/minio" "fwh-toolbox-server/cmd/mcp/tools" "fwh-toolbox-server/cmd/mcp/tools/storage" ) const ( serverName = "fwh-tools" serverVersion = "2.0.0" logDir = "/var/log/fwh-mcp-server" logFile = "/var/log/fwh-mcp-server/mcp.log" ) func main() { // 初始化 MCP 专用 logger(写入文件,不污染 stdio) initMCPLogger() defer zap.L().Sync() // 加载配置(复用现有 config) cfg, err := config.LoadConfig() if err != nil { zap.L().Fatal("加载配置失败", zap.Error(err)) } // 初始化基础设施(与 HTTP Server 相同) if err := database.Init(&cfg.App); err != nil { zap.L().Fatal("数据库初始化失败", zap.Error(err)) } cache.Init(cfg.App.RedisAddr) minioUtil.InitClient(&cfg.App) repositories.InitPlane(&cfg.PlaneWebApi) // 初始化 MCP FileStore mcpFS := filestore.NewFileStore("download") storage.InitFileStore(mcpFS) // 创建 MCP Server s := server.NewMCPServer( serverName, serverVersion, server.WithToolCapabilities(false), server.WithRecovery(), server.WithInstructions(serverInstructions), ) // 注册所有 Tools(唯一入口) tools.RegisterAll(s) // 启动 stdio 通信 zap.L().Info("[fwh-mcp-server] started", zap.String("version", serverVersion)) fmt.Fprintln(os.Stderr, "[fwh-mcp-server] started (stdio) v"+serverVersion) if err := server.ServeStdio(s); err != nil { zap.L().Fatal("[fwh-mcp-server] error", zap.Error(err)) } } // initMCPLogger 初始化 MCP Server 专用 logger // 日志写入文件,不输出到 stdout/stderr(避免污染 stdio JSON-RPC 通道) func initMCPLogger() { // 确保日志目录存在 if err := os.MkdirAll(logDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "[fwh-mcp-server] 创建日志目录失败: %v\n", err) } config := zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), Development: false, Encoding: "json", EncoderConfig: zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, }, OutputPaths: []string{logFile}, ErrorOutputPaths: []string{logFile}, } zapLogger, err := config.Build(zap.AddStacktrace(zap.ErrorLevel)) if err != nil { fmt.Fprintf(os.Stderr, "[fwh-mcp-server] logger 初始化失败: %v\n", err) // fallback: 使用 nop logger zapLogger = zap.NewNop() } // 设置全局 logger(zap.L() 和 zap.S()) zap.ReplaceGlobals(zapLogger) // 同时设置 logger 包变量(database 等模块直接引用 logger.Sugar) logger.Log = zapLogger logger.Sugar = zapLogger.Sugar() logger.Std = zap.NewStdLog(zapLogger) } // serverInstructions MCP Server 使用说明,帮助 AI 理解工具分类和使用优先级 const serverInstructions = `FranklinWH 工具箱 MCP Server — 内部工具平台的 AI 接口层。 ## 工具分类(按访问层级) ### service_* — 业务逻辑层(优先使用) 调用后端 Service 层函数,包含业务校验、数据聚合和格式化。 适用场景:常规的业务数据查询和操作。 - service_site_*: 站点设备管理(查详情、搜索、功率流) - service_sim_*: SIM 卡管理(查详情、搜索) - service_monitor_*: 运维监控(告警统计、停机统计) - service_feedback_*: 反馈协作(创建/查询 Plane Issue) ### data_* — 数据直查层(灵活查询) 直接读取数据库或缓存,跳过业务逻辑层。仅限只读操作。 适用场景:当 service 层工具无法满足查询需求时(自定义 filter、聚合统计等)。 - data_mongo_*: MongoDB 集合查询 - data_redis_*: Redis 缓存查看 ### storage_* — 对象存储层 文件和对象存储操作。 - storage_file_*: 用户上传文件的读取和写入 - storage_minio_*: MinIO bucket 对象浏览和读取(只读) ### util_* — 辅助工具 纯函数工具,不涉及数据持久化。 - util_parse_sn: 解析设备 SN 序列号含义 ## 使用原则 1. 优先使用 service_* 工具,它们包含完整的业务逻辑 2. 仅当 service 层无法满足时才使用 data_* 直查 3. data_* 工具仅支持只读操作 4. storage_file_* 支持读写,storage_minio_* 仅只读 5. 查询 MongoDB 时注意 collection 白名单限制 6. MinIO 操作受 bucket 白名单限制`