import { useCanvasStore } from '../stores/canvas' import { endpoints, isSecure, wsProtocol, hostname } from '../config/endpoints' // WebMCP HTTP API base for direct token requests const WEBMCP_HTTP = endpoints.webmcpHttp let webmcpInstance: any = null const registeredTools = new Set() const eventUnsubscribers: Array<() => void> = [] const API_BASE = endpoints.api let tokenPollingInterval: number | null = null export async function initWebMCP() { if (webmcpInstance) return webmcpInstance const WebMCPModule = await import('@nucleoriofrio/webmcp/src/webmcp.js') const WebMCP = WebMCPModule.default || WebMCPModule webmcpInstance = new WebMCP({ headless: true, inactivityTimeout: 60 * 60 * 1000 // 1 hora }) setupEventHandlers() // Check initial connection state if (webmcpInstance.isConnected) { const canvasStore = useCanvasStore() canvasStore.setConnected(true) updateConnectionInfo() } // Expose globally for debug ;(window as any).webmcp = webmcpInstance return webmcpInstance } function setupEventHandlers() { // Skip if instance doesn't support events if (typeof webmcpInstance.on !== 'function') { console.warn('[WebMCP] Event emitter not available') return } const canvasStore = useCanvasStore() // Connection events eventUnsubscribers.push( webmcpInstance.on('connected', () => { console.log('[WebMCP] Connected') canvasStore.setConnected(true) canvasStore.setReconnecting(false) canvasStore.setConnectionError(null) updateConnectionInfo() }) ) eventUnsubscribers.push( webmcpInstance.on('disconnected', () => { console.log('[WebMCP] Disconnected') canvasStore.setConnected(false) canvasStore.setReconnecting(false) canvasStore.setConnectionInfo(null) }) ) eventUnsubscribers.push( webmcpInstance.on('reconnecting', () => { console.log('[WebMCP] Reconnecting...') canvasStore.setReconnecting(true) }) ) // Status changes eventUnsubscribers.push( webmcpInstance.on('statusChange', (data: { status: string }) => { canvasStore.setConnectionStatus(data.status) }) ) // Error handling eventUnsubscribers.push( webmcpInstance.on('error', (data: { message: string }) => { console.error('[WebMCP] Error:', data.message) canvasStore.setConnectionError(data.message) canvasStore.showNotification(data.message, 'error') }) ) // Tool events eventUnsubscribers.push( webmcpInstance.on('toolRegistered', (data: { name: string }) => { console.log('[WebMCP] Tool registered by server:', data.name) updateConnectionInfo() }) ) eventUnsubscribers.push( webmcpInstance.on('toolCreated', (data: { name: string }) => { console.log('[WebMCP] Tool created:', data.name) registeredTools.add(data.name) updateConnectionInfo() }) ) eventUnsubscribers.push( webmcpInstance.on('toolRemoved', (data: { name: string }) => { if (data.name === '*') { console.log('[WebMCP] All tools removed') registeredTools.clear() } else { console.log('[WebMCP] Tool removed:', data.name) registeredTools.delete(data.name) } updateConnectionInfo() }) ) } function updateConnectionInfo() { if (!webmcpInstance) return const canvasStore = useCanvasStore() const info = webmcpInstance.getConnectionInfo?.() if (info) { canvasStore.setConnectionInfo({ isConnected: info.isConnected, channel: info.channel, server: info.server, status: info.status, tools: info.tools || [], prompts: info.prompts || [], resources: info.resources || [] }) } } export function getConnectionInfo() { return webmcpInstance?.getConnectionInfo?.() || null } export function getWebMCP() { return webmcpInstance } export function registerTool( name: string, description: string, schema: object, handler: Function ) { if (!webmcpInstance) { console.warn('[WebMCP] Instance not initialized') return false } if (registeredTools.has(name)) { console.warn(`[WebMCP] Tool "${name}" already registered, skipping`) return false } webmcpInstance.registerTool(name, description, schema, handler) registeredTools.add(name) console.log(`[WebMCP] Tool registered: ${name}`) return true } export function unregisterTool(name: string) { if (!webmcpInstance) { console.warn('[WebMCP] Instance not initialized') return false } if (!registeredTools.has(name)) { return false } webmcpInstance.unregisterTool(name) registeredTools.delete(name) console.log(`[WebMCP] Tool unregistered: ${name}`) return true } export function unregisterTools(names: string[]) { for (const name of names) { unregisterTool(name) } } export function clearAllTools() { if (!webmcpInstance) return for (const name of registeredTools) { webmcpInstance.unregisterTool(name) } console.log(`[WebMCP] Cleared ${registeredTools.size} tools`) registeredTools.clear() } export function destroyWebMCP() { // Unsubscribe all event handlers for (const unsub of eventUnsubscribers) { unsub() } eventUnsubscribers.length = 0 // Clear tools clearAllTools() // Disconnect if connected if (webmcpInstance?.disconnect) { webmcpInstance.disconnect() } webmcpInstance = null ;(window as any).webmcp = null console.log('[WebMCP] Instance destroyed') } export function getRegisteredTools(): string[] { return [...registeredTools] } export function isToolRegistered(name: string): boolean { return registeredTools.has(name) } // Token polling functions export async function checkForToken(): Promise { try { const res = await fetch(`${API_BASE}/webmcp-token`) const data = await res.json() return data.token || null } catch (e) { return null } } export async function clearToken(): Promise { try { await fetch(`${API_BASE}/webmcp-token`, { method: 'DELETE' }) } catch (e) { // ignore } } export function startTokenPolling(onToken: (token: string) => void, intervalMs: number = 2000) { if (tokenPollingInterval) return console.log('[WebMCP] Starting token polling...') tokenPollingInterval = window.setInterval(async () => { const token = await checkForToken() if (token) { console.log('[WebMCP] Token detected!') stopTokenPolling() onToken(token) } }, intervalMs) } export function stopTokenPolling() { if (tokenPollingInterval) { window.clearInterval(tokenPollingInterval) tokenPollingInterval = null console.log('[WebMCP] Token polling stopped') } } export function parseToken(token: string): { server: string; token: string } | null { try { const decoded = atob(token) return JSON.parse(decoded) } catch (e) { console.error('[WebMCP] Failed to parse token:', e) return null } } /** * Request a new token directly from WebMCP server via HTTP API * In development: calls WebMCP directly at port 4102 * In production (HTTPS): calls Agent UI API which proxies to WebMCP */ export async function requestToken(): Promise { try { console.log('[WebMCP] Requesting token from server...') // In HTTPS mode, use Agent UI API as proxy (Traefik can't reach WebMCP directly) // In development, call WebMCP directly const url = isSecure ? `${API_BASE}/webmcp-request-token` : `${WEBMCP_HTTP}/token` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }) if (!res.ok) { console.error('[WebMCP] Failed to request token:', res.status) return null } // Check if response is JSON const contentType = res.headers.get('content-type') if (!contentType?.includes('application/json')) { console.warn('[WebMCP] /token endpoint not available (server returned non-JSON)') return null } const data = await res.json() if (data.success && data.token) { console.log('[WebMCP] Token received from server') return data.token } return null } catch (e) { // Network error or endpoint not available console.warn('[WebMCP] /token endpoint not available:', (e as Error).message) return null } } /** * Auto-connect to WebMCP by requesting a token and connecting * Returns true if connection was successful */ export async function autoConnect(): Promise { if (!webmcpInstance) { console.error('[WebMCP] Instance not initialized, call initWebMCP() first') return false } // Check if already connected if (webmcpInstance.isConnected) { console.log('[WebMCP] Already connected') return true } // Request token from server const token = await requestToken() if (!token) { console.error('[WebMCP] Failed to get token') return false } // Connect with the token return connectWithToken(token) } export async function connectWithToken(token: string): Promise { if (!webmcpInstance) { console.error('[WebMCP] Instance not initialized') return false } if (typeof webmcpInstance.connect !== 'function') { console.error('[WebMCP] connect method not available') return false } console.log('[WebMCP] Connecting with token...') // Clear the pending token from server await clearToken() // If behind HTTPS/Traefik, modify token to use secure WebSocket let finalToken = token if (isSecure) { const parsed = parseToken(token) if (parsed) { // Replace ws://localhost:4102 with wss://hostname/ws/mcp const secureServer = `${wsProtocol}//${hostname}/ws/mcp` const modifiedToken = { server: secureServer, token: parsed.token } finalToken = btoa(JSON.stringify(modifiedToken)) console.log('[WebMCP] Modified token for HTTPS:', secureServer) } } // Connect passing the token directly try { await webmcpInstance.connect(finalToken) return true } catch (e) { console.error('[WebMCP] Failed to connect:', e) return false } }