feat: Auto-connect WebMCP without agent intervention

- Add requestToken() and autoConnect() functions to request tokens directly from WebMCP server
- Add /api/webmcp-request-token endpoint to proxy token requests (for HTTPS/Traefik)
- Add webmcpHttp endpoint configuration for direct WebMCP HTTP API access
- Update App.vue to auto-connect on load with polling fallback
- Add kill-ports script to clear ports 4101, 4102, 4103, 4105 before starting
This commit is contained in:
2026-02-14 16:15:49 -06:00
parent 2766cbfd0b
commit 50f670f66c
7 changed files with 145 additions and 13 deletions

View File

@@ -1915,7 +1915,7 @@
},
"node_modules/@nucleoriofrio/webmcp": {
"version": "0.2.0",
"resolved": "git+https://gitea.nucleoriofrio.com/nucleo000/webmcp.git#cec5be355d67e0cf9049380ece74e9eac0e85f5e",
"resolved": "git+https://gitea.nucleoriofrio.com/nucleo000/webmcp.git#870207f15199369bc262d27ce0f90a27f2854be4",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",

View File

@@ -10,7 +10,7 @@ import FloatingTerminal from './components/FloatingTerminal.vue'
import FloatingResponse from './components/FloatingResponse.vue'
import FloatingVoice from './components/FloatingVoice.vue'
import PwaInstallBanner from './components/PwaInstallBanner.vue'
import { initWebMCP, getWebMCP, startTokenPolling, stopTokenPolling, connectWithToken } from './services/webmcp'
import { initWebMCP, getWebMCP, autoConnect, startTokenPolling, stopTokenPolling, connectWithToken } from './services/webmcp'
import { endpoints } from './config/endpoints'
import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry'
import { setTerminalControls } from './services/tools/handlers/terminalHandlers'
@@ -322,16 +322,24 @@ onMounted(async () => {
})
}
// Start polling for token if not connected
// Auto-connect to WebMCP if not connected
const webmcp = getWebMCP()
if (!webmcp?.isConnected) {
startTokenPolling(async (token) => {
console.log('[App] Token received, connecting...')
const success = await connectWithToken(token)
if (success) {
canvasStore.showNotification('WebMCP connected!', 'success')
}
})
// Try auto-connect (works in both dev and HTTPS via API proxy)
const connected = await autoConnect()
if (connected) {
canvasStore.showNotification('WebMCP connected!', 'success')
} else {
// Fallback to polling (for older WebMCP versions without /token endpoint)
console.log('[App] Auto-connect failed, falling back to token polling...')
startTokenPolling(async (token) => {
console.log('[App] Token received, connecting...')
const success = await connectWithToken(token)
if (success) {
canvasStore.showNotification('WebMCP connected!', 'success')
}
})
}
}
})

View File

@@ -22,6 +22,14 @@ function buildWsUrl(securePath: string, devPort: number): string {
return `${wsProtocol}//${hostname}:${devPort}`
}
// Build HTTP URL for services
function buildHttpUrl(securePath: string, devPort: number): string {
if (isSecure) {
return `https://${hostname}${securePath}`
}
return `http://${hostname}:${devPort}`
}
// Endpoint configuration
export const endpoints = {
// Terminal WebSocket
@@ -39,6 +47,9 @@ export const endpoints = {
// WebMCP WebSocket
webmcp: buildWsUrl('/ws/mcp', 4102),
// WebMCP HTTP API (for token requests)
webmcpHttp: buildHttpUrl('/mcp', 4102),
// API base URL (Vite proxy handles /api in dev)
api: '/api'
}

View File

@@ -1,6 +1,9 @@
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<string>()
const eventUnsubscribers: Array<() => void> = []
@@ -271,6 +274,76 @@ export function parseToken(token: string): { server: string; token: string } | n
}
}
/**
* 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<string | null> {
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<boolean> {
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<boolean> {
if (!webmcpInstance) {
console.error('[WebMCP] Instance not initialized')