feat: Improve WebMCP connection handling and tools management
WebMCP service: - Add headless mode configuration - Implement proper event handlers with unsubscribe support - Add connection info tracking (channel, server, status, tools) - Add destroyWebMCP for cleanup - Improve connectWithToken to pass token directly Canvas store: - Add connection state (reconnecting, status, error, info) - Add computed statusColor for UI feedback Components: - Add ConnectionDropdown for connection status display - Add ToolsDropdown for tools management UI Tool registry: - Improve tool activation/deactivation logic - Better error handling and logging
This commit is contained in:
@@ -2,6 +2,7 @@ import { useCanvasStore } from '../stores/canvas'
|
||||
|
||||
let webmcpInstance: any = null
|
||||
const registeredTools = new Set<string>()
|
||||
const eventUnsubscribers: Array<() => void> = []
|
||||
|
||||
const API_BASE = 'http://localhost:4101'
|
||||
let tokenPollingInterval: number | null = null
|
||||
@@ -13,31 +14,130 @@ export async function initWebMCP() {
|
||||
const WebMCP = WebMCPModule.default || WebMCPModule
|
||||
|
||||
webmcpInstance = new WebMCP({
|
||||
color: '#6366f1',
|
||||
position: 'bottom-right',
|
||||
headless: true,
|
||||
inactivityTimeout: 60 * 60 * 1000 // 1 hora
|
||||
})
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
webmcpInstance.on?.('connected', () => {
|
||||
canvasStore.setConnected(true)
|
||||
})
|
||||
|
||||
webmcpInstance.on?.('disconnected', () => {
|
||||
canvasStore.setConnected(false)
|
||||
})
|
||||
setupEventHandlers()
|
||||
|
||||
// Check initial connection state
|
||||
if (webmcpInstance.isConnected) {
|
||||
const canvasStore = useCanvasStore()
|
||||
canvasStore.setConnected(true)
|
||||
updateConnectionInfo()
|
||||
}
|
||||
|
||||
// Exponer globalmente para debug
|
||||
// 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
|
||||
}
|
||||
@@ -91,6 +191,26 @@ export function clearAllTools() {
|
||||
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]
|
||||
}
|
||||
@@ -151,27 +271,27 @@ export function parseToken(token: string): { server: string; token: string } | n
|
||||
}
|
||||
|
||||
export async function connectWithToken(token: string): Promise<boolean> {
|
||||
const parsed = parseToken(token)
|
||||
if (!parsed) return false
|
||||
if (!webmcpInstance) {
|
||||
console.error('[WebMCP] Instance not initialized')
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('[WebMCP] Connecting with token to:', parsed.server)
|
||||
if (typeof webmcpInstance.connect !== 'function') {
|
||||
console.error('[WebMCP] connect method not available')
|
||||
return false
|
||||
}
|
||||
|
||||
// Store token for webmcp to use
|
||||
localStorage.setItem('webmcp_token', token)
|
||||
console.log('[WebMCP] Connecting with token...')
|
||||
|
||||
// Clear the pending token from server
|
||||
await clearToken()
|
||||
|
||||
// If webmcp is already initialized, try to reconnect
|
||||
if (webmcpInstance && typeof webmcpInstance.connect === 'function') {
|
||||
try {
|
||||
await webmcpInstance.connect()
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[WebMCP] Failed to connect:', e)
|
||||
return false
|
||||
}
|
||||
// Connect passing the token directly
|
||||
try {
|
||||
await webmcpInstance.connect(token)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[WebMCP] Failed to connect:', e)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user