/** * Tool Registry - Single source of truth for MCP tool management * * All tool registration/unregistration MUST go through this module. * Other modules should NOT directly use webmcp registration functions. */ import { initWebMCP, getRegisteredTools as getWebMCPTools, clearAllTools as clearWebMCPTools } from './webmcp' import { useToolsStore } from '../stores/tools' import { createGlobalHandlers, createCanvasHandlers, createComponentHandlers, createThemeHandlers, createDatabaseHandlers, createProjectCanvasHandlers, createSourceCodeHandlers, createTerminalHandlers, createResponseHandlers, type ToolConfig } from './tools/handlers' import { setRouter } from './tools/handlers/globalHandlers' import { setGiteaCredentials, clearGiteaCredentials } from './tools/handlers/sourceCodeHandlers' import { ALL_TOOL_METAS, getAllToolNames, type ToolCategory } from './tools/toolDefinitions' export type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source' | 'terminal' | 'tools' // Internal webmcp functions (not exported for external use) let webmcpInstance: any = null const registeredToolsSet = new Set() async function internalRegisterTool(config: ToolConfig): Promise { if (!webmcpInstance) { webmcpInstance = await initWebMCP() } if (registeredToolsSet.has(config.name)) { return false // Already registered } webmcpInstance.registerTool(config.name, config.description, config.schema, config.handler) registeredToolsSet.add(config.name) console.log(`[ToolRegistry] Registered: ${config.name}`) return true } function internalUnregisterTool(name: string): boolean { if (!webmcpInstance || !registeredToolsSet.has(name)) { return false } webmcpInstance.unregisterTool(name) registeredToolsSet.delete(name) console.log(`[ToolRegistry] Unregistered: ${name}`) return true } function internalClearAllTools() { if (!webmcpInstance) return for (const name of registeredToolsSet) { webmcpInstance.unregisterTool(name) } console.log(`[ToolRegistry] Cleared ${registeredToolsSet.size} tools`) registeredToolsSet.clear() } // Tool configurations cache let toolConfigsCache: Map | null = null function getToolConfigs(): Map { if (toolConfigsCache) return toolConfigsCache toolConfigsCache = new Map() // Create callbacks for global handlers const toolManagementCallbacks = { getRegisteredTools: () => Array.from(registeredToolsSet), getAllToolNames: () => getAllToolNames(), activateTool: async (name: string) => { const config = toolConfigsCache?.get(name) if (!config) return false const result = await internalRegisterTool(config) syncStoreWithActiveTools() return result }, deactivateTool: (name: string) => { const toolsStore = useToolsStore() if (toolsStore.isToolPinned(name)) return false const result = internalUnregisterTool(name) syncStoreWithActiveTools() return result }, togglePin: (name: string) => { const toolsStore = useToolsStore() toolsStore.togglePin(name) }, isToolPinned: (name: string) => { const toolsStore = useToolsStore() return toolsStore.isToolPinned(name) } } // Create all handlers const allHandlers = [ ...createGlobalHandlers(toolManagementCallbacks), ...createCanvasHandlers(), ...createComponentHandlers(), ...createThemeHandlers(), ...createDatabaseHandlers(), ...createProjectCanvasHandlers(), ...createSourceCodeHandlers(), ...createTerminalHandlers(), ...createResponseHandlers() ] for (const config of allHandlers) { toolConfigsCache.set(config.name, config) } return toolConfigsCache } // Category to tool names mapping const categoryTools: Record = { global: ['get_current_page', 'navigate_to', 'list_available_tools', 'activate_tool', 'deactivate_tool', 'pin_tool', 'page_refresh'], canvas: ['render_html', 'render_vue_component'], component: ['save_vue_component', 'load_vue_component', 'list_vue_components', 'delete_vue_component'], theme: ['get_design_tokens', 'get_active_theme', 'set_theme_variable', 'save_theme', 'list_themes', 'switch_theme', 'reset_theme'], database: ['list_tables', 'get_table_schema', 'get_table_data', 'get_database_stats', 'execute_query'], source: ['get_repo_info', 'list_repo_files', 'read_repo_file', 'search_repo_code'], project: ['list_canvases', 'create_canvas', 'get_canvas', 'update_canvas', 'delete_canvas', 'clone_canvas', 'add_component_to_canvas', 'remove_component_from_canvas', 'get_canvas_components'], terminal: ['terminal_open', 'terminal_close', 'terminal_toggle', 'terminal_move', 'terminal_resize', 'bubbleResponse'] } // Page to categories mapping const pageCategories: Record = { home: ['global', 'canvas', 'component', 'project', 'terminal'], canvas: ['global', 'canvas', 'component', 'terminal'], 'project-canvas': ['global', 'canvas', 'component', 'project', 'terminal'], projects: ['global', 'project', 'terminal'], components: ['global', 'component', 'terminal'], themes: ['global', 'theme', 'terminal'], database: ['global', 'database', 'terminal'], source: ['global', 'source', 'terminal'], terminal: ['global', 'terminal'], tools: ['global', 'terminal'] } let currentPage: PageName | null = null let isInitialized = false /** * Initialize the tool registry with Vue router */ export function initToolRegistry(router: any) { setRouter(router) isInitialized = true } /** * Set Gitea credentials for source code tools */ export function setSourceCodeCredentials(creds: any) { setGiteaCredentials(creds) } /** * Clear Gitea credentials */ export function clearSourceCodeCredentials() { clearGiteaCredentials() } /** * Activate tools for a specific page */ export async function activatePageTools(pageName: PageName) { if (!isInitialized) { console.warn('[ToolRegistry] Not initialized') return } if (currentPage === pageName) { console.log(`[ToolRegistry] Already on "${pageName}", skipping`) return } console.log(`[ToolRegistry] Switching from "${currentPage}" to "${pageName}"`) const toolsStore = useToolsStore() const pinnedTools = toolsStore.getPinnedToolNames() const configs = getToolConfigs() // Get tools for old and new page const oldCategories = currentPage ? pageCategories[currentPage] : [] const newCategories = pageCategories[pageName] const oldTools = new Set(oldCategories.flatMap(cat => categoryTools[cat])) const newTools = new Set(newCategories.flatMap(cat => categoryTools[cat])) // Unregister old tools (except pinned) for (const tool of oldTools) { if (!newTools.has(tool) && !pinnedTools.includes(tool)) { internalUnregisterTool(tool) } } // Register new tools for (const toolName of newTools) { const config = configs.get(toolName) if (config) { await internalRegisterTool(config) } } // Ensure pinned tools are registered for (const toolName of pinnedTools) { const config = configs.get(toolName) if (config && !registeredToolsSet.has(toolName)) { await internalRegisterTool(config) } } currentPage = pageName syncStoreWithActiveTools() console.log(`[ToolRegistry] Page "${pageName}" tools activated`) } /** * Initialize tools on page refresh */ export async function initToolsOnRefresh(pageName: PageName) { if (!isInitialized) { console.warn('[ToolRegistry] Not initialized') return } console.log(`[ToolRegistry] Initializing on refresh for "${pageName}"`) internalClearAllTools() currentPage = null await activatePageTools(pageName) } /** * Activate a single tool by name */ export async function activateTool(toolName: string): Promise { const configs = getToolConfigs() const config = configs.get(toolName) if (!config) { console.warn(`[ToolRegistry] Tool "${toolName}" not found`) return false } const result = await internalRegisterTool(config) syncStoreWithActiveTools() return result } /** * Deactivate a single tool by name */ export function deactivateTool(toolName: string): boolean { const toolsStore = useToolsStore() if (toolsStore.isToolPinned(toolName)) { console.warn(`[ToolRegistry] Cannot deactivate pinned tool "${toolName}"`) return false } const result = internalUnregisterTool(toolName) syncStoreWithActiveTools() return result } /** * Activate all tools in a category */ export async function activateCategory(category: ToolCategory) { const configs = getToolConfigs() const tools = categoryTools[category] || [] for (const toolName of tools) { const config = configs.get(toolName) if (config) { await internalRegisterTool(config) } } syncStoreWithActiveTools() } /** * Deactivate all tools in a category (respecting pinned) */ export function deactivateCategory(category: ToolCategory) { const toolsStore = useToolsStore() const tools = categoryTools[category] || [] for (const toolName of tools) { if (!toolsStore.isToolPinned(toolName)) { internalUnregisterTool(toolName) } } syncStoreWithActiveTools() } /** * Sync the store with currently active tools */ export function syncStoreWithActiveTools() { const toolsStore = useToolsStore() toolsStore.setActiveTools(Array.from(registeredToolsSet)) } /** * Get current page name */ export function getCurrentPage(): PageName | null { return currentPage } /** * Get tool names for a page */ export function getPageToolNames(pageName: PageName): string[] { const categories = pageCategories[pageName] || [] return categories.flatMap(cat => categoryTools[cat]) } /** * Check if registry is initialized */ export function isRegistryInitialized(): boolean { return isInitialized } /** * Get all tool metadata */ export function getAllToolMetas() { return ALL_TOOL_METAS } /** * Get registered tools (for internal use) */ export function getRegisteredTools(): string[] { return Array.from(registeredToolsSet) }