/** * MCP Config Watcher * * DNA-006 설정 핫 리로드와 MCP 서버 통합 */ import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { watchConfig, hasConfigChanged, getChangedSections, type ManduConfig } from "@mandujs/core"; /** * MCP Config Watcher 옵션 */ export interface McpConfigWatcherOptions { /** MCP Server 인스턴스 */ server?: Server; /** 설정 변경 콜백 */ onReload?: (config: ManduConfig) => void | Promise; /** MCP 설정 변경 시 콜백 */ onMcpConfigChange?: (config: ManduConfig) => void | Promise; /** 에러 콜백 */ onError?: (error: unknown) => void; /** 디바운스 딜레이 (ms) */ debounceMs?: number; } /** * Config Watcher 결과 */ export interface McpConfigWatcher { /** 감시 중지 */ stop: () => void; /** 수동 리로드 */ reload: () => Promise; /** 현재 설정 */ getConfig: () => ManduConfig | undefined; } /** * MCP 서버 설정 감시 시작 * * @example * ```ts * const watcher = await startMcpConfigWatcher(projectRoot, { * server, * onReload: (config) => { * console.log("Config reloaded:", config); * }, * }); * * // 나중에 중지 * watcher.stop(); * ``` */ export async function startMcpConfigWatcher( projectRoot: string, options: McpConfigWatcherOptions = {} ): Promise { const { server, onReload, onMcpConfigChange, onError, debounceMs = 200, } = options; const watcher = await watchConfig( projectRoot, async (newConfig, event) => { // MCP 서버에 알림 if (server) { try { await server.sendLoggingMessage({ level: "info", logger: "mandu-config", data: { type: "config_reload", changedSections: getChangedSections(event.previous, event.current), path: event.path, }, }); } catch { // 알림 실패 무시 } } // MCP 관련 설정 변경 확인 if (event.previous && event.current) { if (needsToolReregistration(event.previous, event.current)) { // MCP 설정 변경 시 도구 재초기화 등 필요한 작업 if (server) { try { await server.sendLoggingMessage({ level: "warning", logger: "mandu-config", data: { type: "mcp_config_changed", message: "MCP configuration changed. Some tools may need reinitialization.", }, }); } catch { // 알림 실패 무시 } } await onMcpConfigChange?.(newConfig); } // Guard 설정 변경 확인 if (hasConfigChanged(event.previous, event.current, "guard")) { if (server) { try { await server.sendLoggingMessage({ level: "info", logger: "mandu-config", data: { type: "guard_config_changed", message: "Guard configuration changed. Architecture rules updated.", }, }); } catch { // 알림 실패 무시 } } } } // 일반 콜백 await onReload?.(newConfig); }, { debounceMs, immediate: false, onError: (err) => { console.error("[MCP:ConfigWatcher] Error:", err); if (server) { server.sendLoggingMessage({ level: "error", logger: "mandu-config", data: { type: "config_error", error: err instanceof Error ? err.message : String(err), }, }).catch(() => {}); } onError?.(err); }, } ); return watcher; } /** * 설정 변경 시 도구 재등록이 필요한지 확인 */ export function needsToolReregistration( previous: ManduConfig | undefined, current: ManduConfig | undefined ): boolean { if (!previous || !current) return false; // MCP 플러그인 설정 변경 const prevPlugins = (previous as Record).mcpPlugins; const currPlugins = (current as Record).mcpPlugins; if (JSON.stringify(prevPlugins) !== JSON.stringify(currPlugins)) { return true; } return false; }