From 506da17931bfdb8092a80afdd8de242eb3afaab7 Mon Sep 17 00:00:00 2001 From: Pascal Date: Sat, 17 Jan 2026 16:26:53 +0100 Subject: [PATCH] refactor: eliminate MCP circular dependency - Change architecture from mcpStore <-> mcpClient to mcpClient -> mcpStore - Remove bidirectional callback pattern (set*Callback, notify* methods) - Add updateState/updateHealthCheck public methods in mcpStore - Replace callback calls with direct mcpStore method calls - Remove unused imports (browser, HealthCheckState) and constructor - Fixes CI: ReferenceError Cannot access mcpClient before initialization --- .../webui/src/lib/clients/mcp.client.ts | 88 +++---------------- .../server/webui/src/lib/stores/mcp.svelte.ts | 44 ++++------ 2 files changed, 31 insertions(+), 101 deletions(-) diff --git a/tools/server/webui/src/lib/clients/mcp.client.ts b/tools/server/webui/src/lib/clients/mcp.client.ts index 05c595e1ed..455e3a9383 100644 --- a/tools/server/webui/src/lib/clients/mcp.client.ts +++ b/tools/server/webui/src/lib/clients/mcp.client.ts @@ -25,6 +25,7 @@ * - Usage statistics tracking */ +import { mcpStore } from '$lib/stores/mcp.svelte'; import { browser } from '$app/environment'; import { MCPService } from '$lib/services/mcp.service'; import type { @@ -34,7 +35,6 @@ import type { ToolExecutionResult, MCPClientConfig, MCPConnection, - HealthCheckState, HealthCheckParams, ServerCapabilities, ClientCapabilities, @@ -88,70 +88,6 @@ export class MCPClient { private toolsIndex = new Map(); private configSignature: string | null = null; private initPromise: Promise | null = null; - - private onStateChange?: (state: { - isInitializing?: boolean; - error?: string | null; - toolCount?: number; - connectedServers?: string[]; - }) => void; - - private onHealthCheckChange?: (serverId: string, state: HealthCheckState) => void; - private onServerUsage?: (serverId: string) => void; - - /** - * - * - * Store Integration - * - * - */ - - /** - * Sets callback for state changes. - * Called by mcpStore to sync reactive state. - */ - setStateChangeCallback( - callback: (state: { - isInitializing?: boolean; - error?: string | null; - toolCount?: number; - connectedServers?: string[]; - }) => void - ): void { - this.onStateChange = callback; - } - - /** - * Set callback for health check state changes - */ - setHealthCheckCallback(callback: (serverId: string, state: HealthCheckState) => void): void { - this.onHealthCheckChange = callback; - } - - /** - * Set callback for server usage tracking - */ - setServerUsageCallback(callback: (serverId: string) => void): void { - this.onServerUsage = callback; - } - - private notifyStateChange(state: Parameters>[0]): void { - this.onStateChange?.(state); - } - - private notifyHealthCheck(serverId: string, state: HealthCheckState): void { - this.onHealthCheckChange?.(serverId, state); - } - - /** - * - * - * Lifecycle - * - * - */ - /** * Ensures MCP is initialized with current config. * Handles config changes by reinitializing as needed. @@ -189,13 +125,13 @@ export class MCPClient { private async initialize(signature: string, mcpConfig: MCPClientConfig): Promise { console.log('[MCPClient] Starting initialization...'); - this.notifyStateChange({ isInitializing: true, error: null }); + mcpStore.updateState({ isInitializing: true, error: null }); this.configSignature = signature; const serverEntries = Object.entries(mcpConfig.servers); if (serverEntries.length === 0) { console.log('[MCPClient] No servers configured'); - this.notifyStateChange({ isInitializing: false, toolCount: 0, connectedServers: [] }); + mcpStore.updateState({ isInitializing: false, toolCount: 0, connectedServers: [] }); return false; } @@ -253,7 +189,7 @@ export class MCPClient { if (successCount === 0 && totalCount > 0) { const error = 'All MCP server connections failed'; - this.notifyStateChange({ + mcpStore.updateState({ isInitializing: false, error, toolCount: 0, @@ -263,7 +199,7 @@ export class MCPClient { return false; } - this.notifyStateChange({ + mcpStore.updateState({ isInitializing: false, error: null, toolCount: this.toolsIndex.size, @@ -306,7 +242,7 @@ export class MCPClient { this.toolsIndex.clear(); this.configSignature = null; - this.notifyStateChange({ + mcpStore.updateState({ isInitializing: false, error: null, toolCount: 0, @@ -472,7 +408,7 @@ export class MCPClient { throw new MCPError(`Server "${serverName}" is not connected`, -32000); } - this.onServerUsage?.(serverName); + mcpStore.incrementServerUsage(serverName); const args = this.parseToolArguments(toolCall.function.arguments); return MCPService.callTool(connection, { name: toolName, arguments: args }, signal); @@ -569,7 +505,7 @@ export class MCPClient { let currentPhase: MCPConnectionPhase = MCPConnectionPhase.Idle; if (!trimmedUrl) { - this.notifyHealthCheck(server.id, { + mcpStore.updateHealthCheck(server.id, { status: HealthCheckStatus.Error, message: 'Please enter a server URL first.', logs: [] @@ -578,7 +514,7 @@ export class MCPClient { } // Initial connecting state - this.notifyHealthCheck(server.id, { + mcpStore.updateHealthCheck(server.id, { status: HealthCheckStatus.Connecting, phase: MCPConnectionPhase.TransportCreating, logs: [] @@ -603,7 +539,7 @@ export class MCPClient { (phase, log) => { currentPhase = phase; logs.push(log); - this.notifyHealthCheck(server.id, { + mcpStore.updateHealthCheck(server.id, { status: HealthCheckStatus.Connecting, phase, logs: [...logs] @@ -622,7 +558,7 @@ export class MCPClient { connection.clientCapabilities ); - this.notifyHealthCheck(server.id, { + mcpStore.updateHealthCheck(server.id, { status: HealthCheckStatus.Success, tools, serverInfo: connection.serverInfo, @@ -643,7 +579,7 @@ export class MCPClient { message: `Connection failed: ${message}`, level: MCPLogLevel.Error }); - this.notifyHealthCheck(server.id, { + mcpStore.updateHealthCheck(server.id, { status: HealthCheckStatus.Error, message, phase: currentPhase, diff --git a/tools/server/webui/src/lib/stores/mcp.svelte.ts b/tools/server/webui/src/lib/stores/mcp.svelte.ts index 044e60eff0..69d522c314 100644 --- a/tools/server/webui/src/lib/stores/mcp.svelte.ts +++ b/tools/server/webui/src/lib/stores/mcp.svelte.ts @@ -19,7 +19,6 @@ * @see MCPService in services/mcp.ts for protocol operations */ -import { browser } from '$app/environment'; import { mcpClient } from '$lib/clients/mcp.client'; import type { HealthCheckState, MCPServerSettingsEntry, McpServerUsageStats } from '$lib/types'; import type { McpServerOverride } from '$lib/types/database'; @@ -59,31 +58,26 @@ class MCPStore { private _connectedServers = $state([]); private _healthChecks = $state>({}); - constructor() { - if (browser) { - mcpClient.setStateChangeCallback((state) => { - if (state.isInitializing !== undefined) { - this._isInitializing = state.isInitializing; - } - if (state.error !== undefined) { - this._error = state.error; - } - if (state.toolCount !== undefined) { - this._toolCount = state.toolCount; - } - if (state.connectedServers !== undefined) { - this._connectedServers = state.connectedServers; - } - }); + /** + * Update state from MCPClient + */ + updateState(state: { + isInitializing?: boolean; + error?: string | null; + toolCount?: number; + connectedServers?: string[]; + }): void { + if (state.isInitializing !== undefined) this._isInitializing = state.isInitializing; + if (state.error !== undefined) this._error = state.error; + if (state.toolCount !== undefined) this._toolCount = state.toolCount; + if (state.connectedServers !== undefined) this._connectedServers = state.connectedServers; + } - mcpClient.setHealthCheckCallback((serverId, state) => { - this._healthChecks = { ...this._healthChecks, [serverId]: state }; - }); - - mcpClient.setServerUsageCallback((serverId) => { - this.incrementServerUsage(serverId); - }); - } + /** + * Update health check state from MCPClient + */ + updateHealthCheck(serverId: string, state: HealthCheckState): void { + this._healthChecks = { ...this._healthChecks, [serverId]: state }; } get isInitializing(): boolean {