feat: Add WebMCP token polling for automatic browser connection

- Add token API endpoint to server for storing/retrieving pending tokens
- Add token polling in frontend to auto-detect and connect with tokens
- Add xterm dependencies for terminal functionality
- Add terminal page support in toolRegistry
This commit is contained in:
2026-02-13 07:49:15 -06:00
parent 2cf869d2e9
commit 9681ce4198
8 changed files with 189 additions and 5 deletions

View File

@@ -9,6 +9,9 @@
"version": "0.0.0",
"dependencies": {
"@nucleoriofrio/webmcp": "git+https://gitea.nucleoriofrio.com/nucleo000/webmcp.git",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"pinia": "^3.0.4",
"vite-plugin-pwa": "^1.2.0",
"vue": "^3.5.25",
@@ -2103,6 +2106,27 @@
}
}
},
"node_modules/@xterm/addon-fit": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz",
"integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==",
"license": "MIT"
},
"node_modules/@xterm/addon-web-links": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz",
"integrity": "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==",
"license": "MIT"
},
"node_modules/@xterm/xterm": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
"integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==",
"license": "MIT",
"workspaces": [
"addons/*"
]
},
"node_modules/accepts": {
"version": "2.0.0",
"license": "MIT",

View File

@@ -11,6 +11,9 @@
},
"dependencies": {
"@nucleoriofrio/webmcp": "git+https://gitea.nucleoriofrio.com/nucleo000/webmcp.git",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"pinia": "^3.0.4",
"vite-plugin-pwa": "^1.2.0",
"vue": "^3.5.25",

View File

@@ -1,16 +1,18 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import StatusBar from './components/StatusBar.vue'
import Toolbar from './components/Toolbar.vue'
import ComponentsDropdown from './components/ComponentsDropdown.vue'
import FloatingTerminal from './components/FloatingTerminal.vue'
import { initWebMCP } from './services/webmcp'
import { initWebMCP, getWebMCP, startTokenPolling, stopTokenPolling, connectWithToken } from './services/webmcp'
import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry'
import { useCanvasStore } from './stores/canvas'
const route = useRoute()
const router = useRouter()
const showTerminal = ref(false)
const canvasStore = useCanvasStore()
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source' | 'terminal'
@@ -24,6 +26,22 @@ onMounted(async () => {
// Initialize tools for current page (handles refresh)
const currentPage = (route.name as string) || 'canvas'
initToolsOnRefresh(currentPage as PageName)
// Start polling for token 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')
}
})
}
})
onUnmounted(() => {
stopTokenPolling()
})
// Watch for route changes and update tools

View File

@@ -35,7 +35,7 @@ import {
SOURCE_CODE_TOOLS
} from './tools/sourceCodeTools'
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source'
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'projects' | 'project-canvas' | 'database' | 'source' | 'terminal'
interface PageToolSet {
register: () => void
@@ -105,6 +105,11 @@ const pageTools: Record<PageName, PageToolSet> = {
register: registerSourceCodeTools,
unregister: unregisterSourceCodeTools,
toolNames: SOURCE_CODE_TOOLS
},
terminal: {
register: () => {},
unregister: () => {},
toolNames: []
}
}

View File

@@ -3,6 +3,9 @@ import { useCanvasStore } from '../stores/canvas'
let webmcpInstance: any = null
const registeredTools = new Set<string>()
const API_BASE = 'http://localhost:4101'
let tokenPollingInterval: number | null = null
export async function initWebMCP() {
if (webmcpInstance) return webmcpInstance
@@ -95,3 +98,80 @@ export function getRegisteredTools(): string[] {
export function isToolRegistered(name: string): boolean {
return registeredTools.has(name)
}
// Token polling functions
export async function checkForToken(): Promise<string | null> {
try {
const res = await fetch(`${API_BASE}/api/webmcp-token`)
const data = await res.json()
return data.token || null
} catch (e) {
return null
}
}
export async function clearToken(): Promise<void> {
try {
await fetch(`${API_BASE}/api/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
}
}
export async function connectWithToken(token: string): Promise<boolean> {
const parsed = parseToken(token)
if (!parsed) return false
console.log('[WebMCP] Connecting with token to:', parsed.server)
// Store token for webmcp to use
localStorage.setItem('webmcp_token', 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
}
}
return true
}