diff --git a/mcp-server/index.js b/mcp-server/index.js index 08b4e323..5dea659a 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -32,10 +32,16 @@ import { startUiBridge } from './ws-bridge.js' const VAULT_PATH = process.env.VAULT_PATH || process.env.HOME + '/Laputa' const WS_UI_PORT = parseInt(process.env.WS_UI_PORT || '9711', 10) -// Start the UI bridge so stdio-based MCP tools can broadcast UI actions -const uiBridge = startUiBridge(WS_UI_PORT) +// Start the UI bridge so stdio-based MCP tools can broadcast UI actions. +// If the port is already in use (e.g. by the running Laputa app), continue +// without the bridge — vault tools still work via stdio MCP. +let uiBridge = null +startUiBridge(WS_UI_PORT).then((bridge) => { + uiBridge = bridge +}) function broadcastUiAction(action, payload) { + if (!uiBridge) return const msg = JSON.stringify({ type: 'ui_action', action, ...payload }) for (const client of uiBridge.clients) { if (client.readyState === 1) client.send(msg) diff --git a/mcp-server/ws-bridge.js b/mcp-server/ws-bridge.js index 9db7efc7..a2bbca77 100644 --- a/mcp-server/ws-bridge.js +++ b/mcp-server/ws-bridge.js @@ -19,6 +19,7 @@ * Protocol (UI bridge): * Server broadcasts: { "type": "ui_action", "action": "open_note", "path": "..." } */ +import { createServer } from 'node:http' import { WebSocketServer } from 'ws' import { readNote, createNote, searchNotes, appendToNote, @@ -80,15 +81,34 @@ async function handleMessage(data) { } } +/** + * Attempt to start the UI bridge WebSocket server. + * Returns a Promise that resolves to the WebSocketServer or null if the port + * is unavailable (e.g. another Laputa instance owns it). + */ export function startUiBridge(port = WS_UI_PORT) { - uiBridge = new WebSocketServer({ port }) + return new Promise((resolve) => { + const httpServer = createServer() - uiBridge.on('connection', () => { - console.error(`[ws-bridge] UI client connected on port ${port}`) + httpServer.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.error(`[ws-bridge] UI bridge port ${port} already in use, disabling bridge`) + } else { + console.error(`[ws-bridge] UI bridge error: ${err.message}`) + } + resolve(null) + }) + + httpServer.listen(port, () => { + const wss = new WebSocketServer({ server: httpServer }) + wss.on('connection', () => { + console.error(`[ws-bridge] UI client connected on port ${port}`) + }) + uiBridge = wss + console.error(`[ws-bridge] UI bridge listening on ws://localhost:${port}`) + resolve(wss) + }) }) - - console.error(`[ws-bridge] UI bridge listening on ws://localhost:${port}`) - return uiBridge } export function startBridge(port = WS_PORT) { @@ -116,6 +136,5 @@ export function startBridge(port = WS_PORT) { // Run directly if invoked as main module const isMain = process.argv[1]?.endsWith('ws-bridge.js') if (isMain) { - startUiBridge() - startBridge() + startUiBridge().then(() => startBridge()) }