diff --git a/tools/server/webui/src/lib/clients/mcp.client.ts b/tools/server/webui/src/lib/clients/mcp.client.ts index 59b118fad3..4513f03d11 100644 --- a/tools/server/webui/src/lib/clients/mcp.client.ts +++ b/tools/server/webui/src/lib/clients/mcp.client.ts @@ -36,10 +36,9 @@ import type { } from '$lib/types/mcp'; import type { McpServerOverride } from '$lib/types/database'; import { MCPError } from '$lib/errors'; -import { buildMcpClientConfig, incrementMcpServerUsage } from '$lib/utils/mcp'; -import { config, settingsStore } from '$lib/stores/settings.svelte'; +import { buildMcpClientConfig, detectMcpTransportFromUrl } from '$lib/utils/mcp'; +import { config } from '$lib/stores/settings.svelte'; import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp'; -import { detectMcpTransportFromUrl } from '$lib/utils/mcp'; export type HealthCheckState = | { status: 'idle' } @@ -68,6 +67,7 @@ export class MCPClient { }) => void; private onHealthCheckChange?: (serverId: string, state: HealthCheckState) => void; + private onServerUsage?: (serverId: string) => void; /** * @@ -99,6 +99,13 @@ export class MCPClient { 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); } @@ -435,8 +442,7 @@ export class MCPClient { throw new MCPError(`Server "${serverName}" is not connected`, -32000); } - const updatedStats = incrementMcpServerUsage(config(), serverName); - settingsStore.updateConfig('mcpServerUsageStats', updatedStats); + this.onServerUsage?.(serverName); const args = this.parseToolArguments(toolCall.function.arguments); return MCPService.callTool(connection, { name: toolName, arguments: args }, signal); diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte index 13696319c1..fe0933fdfe 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte @@ -1,12 +1,7 @@ diff --git a/tools/server/webui/src/lib/components/app/dialogs/DialogMcpServersSettings.svelte b/tools/server/webui/src/lib/components/app/dialogs/DialogMcpServersSettings.svelte index 5620417891..33a931e33e 100644 --- a/tools/server/webui/src/lib/components/app/dialogs/DialogMcpServersSettings.svelte +++ b/tools/server/webui/src/lib/components/app/dialogs/DialogMcpServersSettings.svelte @@ -1,6 +1,5 @@ @@ -63,12 +33,11 @@
- +
- - +
diff --git a/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte b/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte index e5391ed515..a481954d57 100644 --- a/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte @@ -8,14 +8,14 @@ import McpLogo from '$lib/components/app/misc/McpLogo.svelte'; import { settingsStore } from '$lib/stores/settings.svelte'; import { conversationsStore } from '$lib/stores/conversations.svelte'; - import { parseMcpServerSettings, parseMcpServerUsageStats } from '$lib/utils/mcp'; + import { parseMcpServerSettings, getServerDisplayName, getFaviconUrl } from '$lib/utils/mcp'; import type { MCPServerSettingsEntry } from '$lib/types/mcp'; import { mcpGetHealthCheckState, mcpHasHealthCheck, - mcpRunHealthCheck + mcpGetUsageStats } from '$lib/stores/mcp.svelte'; - import { extractServerNameFromUrl, getFaviconUrl } from '$lib/utils/mcp'; + import { mcpClient } from '$lib/clients/mcp.client'; interface Props { class?: string; @@ -33,7 +33,7 @@ let hasMcpServers = $derived(mcpServers.length > 0); - let mcpUsageStats = $derived(parseMcpServerUsageStats(settingsStore.config.mcpServerUsageStats)); + let mcpUsageStats = $derived(mcpGetUsageStats()); function getServerUsageCount(serverId: string): number { return mcpUsageStats[serverId] || 0; @@ -94,11 +94,6 @@ await conversationsStore.toggleMcpServerForChat(serverId, globalEnabled); } - function getServerDisplayName(server: MCPServerSettingsEntry): string { - if (server.name) return server.name; - return extractServerNameFromUrl(server.url); - } - let mcpFavicons = $derived( healthyEnabledMcpServers .slice(0, 3) @@ -111,7 +106,7 @@ onMount(() => { for (const server of serversWithUrls) { if (!mcpHasHealthCheck(server.id)) { - mcpRunHealthCheck(server); + mcpClient.runHealthCheck(server); } } }); diff --git a/tools/server/webui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte b/tools/server/webui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte index c49572dbc9..0b7d5e0599 100644 --- a/tools/server/webui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpServerCard/McpServerCard.svelte @@ -5,9 +5,9 @@ import { mcpGetHealthCheckState, mcpHasHealthCheck, - mcpRunHealthCheck, type HealthCheckState } from '$lib/stores/mcp.svelte'; + import { mcpClient } from '$lib/clients/mcp.client'; import McpServerCardHeader from './McpServerCardHeader.svelte'; import McpServerCardActions from './McpServerCardActions.svelte'; import McpServerCardToolsList from './McpServerCardToolsList.svelte'; @@ -38,12 +38,12 @@ onMount(() => { if (!mcpHasHealthCheck(server.id) && server.enabled && server.url.trim()) { - mcpRunHealthCheck(server); + mcpClient.runHealthCheck(server); } }); function handleHealthCheck() { - mcpRunHealthCheck(server); + mcpClient.runHealthCheck(server); } function startEditing() { @@ -67,7 +67,7 @@ isEditing = false; if (server.enabled && url) { - setTimeout(() => mcpRunHealthCheck({ ...server, url }), 100); + setTimeout(() => mcpClient.runHealthCheck({ ...server, url }), 100); } } diff --git a/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte b/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte index c5f154abd1..6edcbe6199 100644 --- a/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte @@ -2,23 +2,14 @@ import { Plus, X } from '@lucide/svelte'; import { Button } from '$lib/components/ui/button'; import * as Card from '$lib/components/ui/card'; - import { parseMcpServerSettings } from '$lib/utils/mcp'; + import { getServerDisplayName, getFaviconUrl } from '$lib/utils/mcp'; import type { MCPServerSettingsEntry } from '$lib/types/mcp'; - import type { SettingsConfigType } from '$lib/types/settings'; - import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp'; - import { extractServerNameFromUrl, getFaviconUrl } from '$lib/utils/mcp'; + import { mcpStore, mcpGetServers } from '$lib/stores/mcp.svelte'; import { McpServerCard } from '$lib/components/app/mcp/McpServerCard'; import McpServerForm from './McpServerForm.svelte'; - interface Props { - localConfig: SettingsConfigType; - onConfigChange: (key: string, value: string | boolean) => void; - } - - let { localConfig, onConfigChange }: Props = $props(); - - // Get servers from localConfig - let servers = $derived(parseMcpServerSettings(localConfig.mcpServers)); + // Get servers from store + let servers = $derived(mcpGetServers()); // New server form state let isAddingServer = $state(false); @@ -36,10 +27,6 @@ } }); - function serializeServers(updatedServers: MCPServerSettingsEntry[]) { - onConfigChange('mcpServers', JSON.stringify(updatedServers)); - } - function showAddServerForm() { isAddingServer = true; newServerUrl = ''; @@ -54,35 +41,15 @@ function saveNewServer() { if (newServerUrlError) return; - const newServer: MCPServerSettingsEntry = { - id: crypto.randomUUID ? crypto.randomUUID() : `server-${Date.now()}`, + mcpStore.addServer({ enabled: true, url: newServerUrl.trim(), - headers: newServerHeaders.trim() || undefined, - requestTimeoutSeconds: DEFAULT_MCP_CONFIG.requestTimeoutSeconds - }; - serializeServers([...servers, newServer]); + headers: newServerHeaders.trim() || undefined + }); isAddingServer = false; newServerUrl = ''; newServerHeaders = ''; } - - function updateServer(id: string, updates: Partial) { - const nextServers = servers.map((server) => - server.id === id ? { ...server, ...updates } : server - ); - serializeServers(nextServers); - } - - function removeServer(id: string) { - serializeServers(servers.filter((server) => server.id !== id)); - } - - // Get display name for server - function getServerDisplayName(server: MCPServerSettingsEntry): string { - if (server.name) return server.name; - return extractServerNameFromUrl(server.url); - }
@@ -154,9 +121,9 @@ {server} displayName={getServerDisplayName(server)} faviconUrl={getFaviconUrl(server.url)} - onToggle={(enabled) => updateServer(server.id, { enabled })} - onUpdate={(updates) => updateServer(server.id, updates)} - onDelete={() => removeServer(server.id)} + onToggle={(enabled) => mcpStore.updateServer(server.id, { enabled })} + onUpdate={(updates) => mcpStore.updateServer(server.id, updates)} + onDelete={() => mcpStore.removeServer(server.id)} /> {/each}
diff --git a/tools/server/webui/src/lib/stores/chat.svelte.ts b/tools/server/webui/src/lib/stores/chat.svelte.ts index 75f94ae8e1..fe28ca3197 100644 --- a/tools/server/webui/src/lib/stores/chat.svelte.ts +++ b/tools/server/webui/src/lib/stores/chat.svelte.ts @@ -204,6 +204,10 @@ class ChatStore { return this.addFilesHandler; } + clearPendingEditMessageId(): void { + this.pendingEditMessageId = null; + } + getAllLoadingChats(): string[] { return Array.from(this.chatLoadingStates.keys()); } @@ -333,8 +337,8 @@ class ChatStore { export const chatStore = new ChatStore(); +// State access functions (getters only - use chatStore.method() for actions) export const activeProcessingState = () => chatStore.activeProcessingState; -export const clearEditMode = () => chatStore.clearEditMode(); export const currentResponse = () => chatStore.currentResponse; export const errorDialog = () => chatStore.errorDialogState; export const getAddFilesHandler = () => chatStore.getAddFilesHandler(); @@ -345,9 +349,4 @@ export const isChatLoading = (convId: string) => chatStore.isChatLoadingPublic(c export const isChatStreaming = () => chatStore.isStreaming(); export const isEditing = () => chatStore.isEditing(); export const isLoading = () => chatStore.isLoading; -export const setEditModeActive = (handler: (files: File[]) => void) => - chatStore.setEditModeActive(handler); export const pendingEditMessageId = () => chatStore.pendingEditMessageId; -export const clearPendingEditMessageId = () => (chatStore.pendingEditMessageId = null); -export const removeSystemPromptPlaceholder = (messageId: string) => - chatStore.removeSystemPromptPlaceholder(messageId); diff --git a/tools/server/webui/src/lib/stores/mcp.svelte.ts b/tools/server/webui/src/lib/stores/mcp.svelte.ts index 52ed7a1f1c..bc59f8fc26 100644 --- a/tools/server/webui/src/lib/stores/mcp.svelte.ts +++ b/tools/server/webui/src/lib/stores/mcp.svelte.ts @@ -20,16 +20,37 @@ */ import { browser } from '$app/environment'; -import { mcpClient, type HealthCheckState, type HealthCheckParams } from '$lib/clients'; -import type { - OpenAIToolDefinition, - ServerStatus, - ToolExecutionResult, - MCPToolCall -} from '$lib/types/mcp'; +import { mcpClient, type HealthCheckState } from '$lib/clients'; +import type { MCPServerSettingsEntry, McpServerUsageStats } from '$lib/types/mcp'; import type { McpServerOverride } from '$lib/types/database'; -import { buildMcpClientConfig } from '$lib/utils/mcp'; -import { config } from '$lib/stores/settings.svelte'; +import { buildMcpClientConfig, parseMcpServerSettings } from '$lib/utils/mcp'; +import { config, settingsStore } from '$lib/stores/settings.svelte'; +import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp'; + +/** + * Parses MCP server usage stats from settings. + * @param rawStats - The raw stats to parse + * @returns MCP server usage stats or empty object if invalid + */ +function parseMcpServerUsageStats(rawStats: unknown): McpServerUsageStats { + if (!rawStats) return {}; + + if (typeof rawStats === 'string') { + const trimmed = rawStats.trim(); + if (!trimmed) return {}; + + try { + const parsed = JSON.parse(trimmed); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + return parsed as McpServerUsageStats; + } + } catch { + console.warn('[MCP] Failed to parse mcpServerUsageStats JSON, ignoring value'); + } + } + + return {}; +} export type { HealthCheckState }; @@ -60,6 +81,10 @@ class MCPStore { mcpClient.setHealthCheckCallback((serverId, state) => { this._healthChecks = { ...this._healthChecks, [serverId]: state }; }); + + mcpClient.setServerUsageCallback((serverId) => { + this.incrementServerUsage(serverId); + }); } } @@ -104,67 +129,6 @@ class MCPStore { return mcpClient.getToolNames(); } - /** - * Ensure MCP is initialized with current config. - * @param perChatOverrides - Optional per-chat MCP server overrides - */ - async ensureInitialized(perChatOverrides?: McpServerOverride[]): Promise { - return mcpClient.ensureInitialized(perChatOverrides); - } - - /** - * Shutdown MCP connections and clear state - */ - async shutdown(): Promise { - return mcpClient.shutdown(); - } - - /** - * Get tool definitions for LLM (OpenAI function calling format) - */ - getToolDefinitions(): OpenAIToolDefinition[] { - return mcpClient.getToolDefinitionsForLLM(); - } - - /** - * Get status of all servers - */ - getServersStatus(): ServerStatus[] { - return mcpClient.getServersStatus(); - } - - /** - * Execute a tool call via MCP. - */ - async executeTool(toolCall: MCPToolCall, signal?: AbortSignal): Promise { - return mcpClient.executeTool(toolCall, signal); - } - - /** - * Execute a tool by name with arguments. - */ - async executeToolByName( - toolName: string, - args: Record, - signal?: AbortSignal - ): Promise { - return mcpClient.executeToolByName(toolName, args, signal); - } - - /** - * Check if a tool exists - */ - hasTool(toolName: string): boolean { - return mcpClient.hasTool(toolName); - } - - /** - * Get which server provides a specific tool - */ - getToolServer(toolName: string): string | undefined { - return mcpClient.getToolServer(toolName); - } - /** * Get health check state for a specific server */ @@ -179,13 +143,6 @@ class MCPStore { return serverId in this._healthChecks && this._healthChecks[serverId].status !== 'idle'; } - /** - * Run health check for a specific server - */ - async runHealthCheck(server: HealthCheckParams): Promise { - return mcpClient.runHealthCheck(server); - } - /** * Clear health check state for a specific server */ @@ -208,6 +165,107 @@ class MCPStore { clearError(): void { this._error = null; } + + /** + * + * Server Management (CRUD) + * + */ + + /** + * Get all configured MCP servers from settings + */ + getServers(): MCPServerSettingsEntry[] { + return parseMcpServerSettings(config().mcpServers); + } + + /** + * Add a new MCP server + */ + addServer( + serverData: Omit & { id?: string } + ): void { + const servers = this.getServers(); + const newServer: MCPServerSettingsEntry = { + id: serverData.id || (crypto.randomUUID ? crypto.randomUUID() : `server-${Date.now()}`), + enabled: serverData.enabled, + url: serverData.url.trim(), + name: serverData.name, + headers: serverData.headers?.trim() || undefined, + requestTimeoutSeconds: DEFAULT_MCP_CONFIG.requestTimeoutSeconds + }; + settingsStore.updateConfig('mcpServers', JSON.stringify([...servers, newServer])); + } + + /** + * Update an existing MCP server + */ + updateServer(id: string, updates: Partial): void { + const servers = this.getServers(); + const nextServers = servers.map((server) => + server.id === id ? { ...server, ...updates } : server + ); + settingsStore.updateConfig('mcpServers', JSON.stringify(nextServers)); + } + + /** + * Remove an MCP server by ID + */ + removeServer(id: string): void { + const servers = this.getServers(); + settingsStore.updateConfig('mcpServers', JSON.stringify(servers.filter((s) => s.id !== id))); + this.clearHealthCheck(id); + } + + /** + * Check if a server is enabled considering per-chat overrides + */ + isServerEnabled(server: MCPServerSettingsEntry, perChatOverrides?: McpServerOverride[]): boolean { + if (perChatOverrides) { + const override = perChatOverrides.find((o) => o.serverId === server.id); + if (override !== undefined) { + return override.enabled; + } + } + return server.enabled; + } + + /** + * Check if there are any enabled MCP servers + */ + hasEnabledServers(perChatOverrides?: McpServerOverride[]): boolean { + return Boolean(buildMcpClientConfig(config(), perChatOverrides)); + } + + /** + * + * Server Usage Stats + * + */ + + /** + * Get parsed usage stats for all servers + */ + getUsageStats(): McpServerUsageStats { + return parseMcpServerUsageStats(config().mcpServerUsageStats); + } + + /** + * Get usage count for a specific server + */ + getServerUsageCount(serverId: string): number { + const stats = this.getUsageStats(); + return stats[serverId] || 0; + } + + /** + * Increment usage count for a server + */ + incrementServerUsage(serverId: string): void { + const stats = this.getUsageStats(); + stats[serverId] = (stats[serverId] || 0) + 1; + settingsStore.updateConfig('mcpServerUsageStats', JSON.stringify(stats)); + } } export const mcpStore = new MCPStore(); @@ -244,6 +302,7 @@ export function mcpToolCount() { return mcpStore.toolCount; } +// State access functions (getters only - use mcpStore.method() for actions) export function mcpGetHealthCheckState(serverId: string) { return mcpStore.getHealthCheckState(serverId); } @@ -252,10 +311,25 @@ export function mcpHasHealthCheck(serverId: string) { return mcpStore.hasHealthCheck(serverId); } -export async function mcpRunHealthCheck(server: HealthCheckParams) { - return mcpStore.runHealthCheck(server); +export function mcpGetServers() { + return mcpStore.getServers(); } -export function mcpClearHealthCheck(serverId: string) { - return mcpStore.clearHealthCheck(serverId); +export function mcpIsServerEnabled( + server: MCPServerSettingsEntry, + perChatOverrides?: McpServerOverride[] +) { + return mcpStore.isServerEnabled(server, perChatOverrides); +} + +export function mcpHasEnabledServers(perChatOverrides?: McpServerOverride[]) { + return mcpStore.hasEnabledServers(perChatOverrides); +} + +export function mcpGetUsageStats() { + return mcpStore.getUsageStats(); +} + +export function mcpGetServerUsageCount(serverId: string) { + return mcpStore.getServerUsageCount(serverId); } diff --git a/tools/server/webui/src/lib/utils/mcp.ts b/tools/server/webui/src/lib/utils/mcp.ts index a0dc1a2163..ba817d2a30 100644 --- a/tools/server/webui/src/lib/utils/mcp.ts +++ b/tools/server/webui/src/lib/utils/mcp.ts @@ -2,8 +2,7 @@ import type { MCPTransportType, MCPClientConfig, MCPServerConfig, - MCPServerSettingsEntry, - McpServerUsageStats + MCPServerSettingsEntry } from '$lib/types/mcp'; import type { SettingsConfigType } from '$lib/types/settings'; import type { McpServerOverride } from '$lib/types/database'; @@ -21,6 +20,7 @@ export type HeaderPair = { key: string; value: string }; */ export function detectMcpTransportFromUrl(url: string): MCPTransportType { const normalized = url.trim().toLowerCase(); + return normalized.startsWith('ws://') || normalized.startsWith('wss://') ? 'websocket' : 'streamable_http'; @@ -34,6 +34,7 @@ export function generateMcpServerId(id: unknown, index: number): string { if (typeof id === 'string' && id.trim()) { return id.trim(); } + return `server-${index + 1}`; } @@ -46,12 +47,23 @@ export function extractServerNameFromUrl(url: string): string { const parsedUrl = new URL(url); const host = parsedUrl.hostname.replace(/^(www\.|mcp\.)/, ''); const name = host.split('.')[0] || 'Unknown'; + return name.charAt(0).toUpperCase() + name.slice(1); } catch { return 'New Server'; } } +/** + * Gets a display name for an MCP server. + * Returns server.name if set, otherwise extracts name from URL. + */ +export function getServerDisplayName(server: MCPServerSettingsEntry): string { + if (server.name) return server.name; + + return extractServerNameFromUrl(server.url); +} + /** * Gets a favicon URL for an MCP server using Google's favicon service. * Returns null if the URL is invalid. @@ -74,6 +86,7 @@ export function getFaviconUrl(serverUrl: string): string | null { */ export function parseHeadersToArray(headersJson: string): HeaderPair[] { if (!headersJson?.trim()) return []; + try { const parsed = JSON.parse(headersJson); if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { @@ -85,6 +98,7 @@ export function parseHeadersToArray(headersJson: string): HeaderPair[] { } catch { return []; } + return []; } @@ -94,11 +108,15 @@ export function parseHeadersToArray(headersJson: string): HeaderPair[] { */ export function serializeHeaders(pairs: HeaderPair[]): string { const validPairs = pairs.filter((p) => p.key.trim()); + if (validPairs.length === 0) return ''; + const obj: Record = {}; + for (const pair of validPairs) { obj[pair.key.trim()] = pair.value; } + return JSON.stringify(obj); } @@ -186,7 +204,8 @@ function buildServerConfig( } /** - * TODO - move stateful logic to store + * Checks if a server is enabled considering per-chat overrides. + * Per-chat override takes precedence over global setting. */ function isServerEnabled( server: MCPServerSettingsEntry, @@ -198,6 +217,7 @@ function isServerEnabled( return override.enabled; } } + return server.enabled; } @@ -241,7 +261,9 @@ export function buildMcpClientConfig( } /** - * TODO - move stateful logic to store + * Checks if there are any enabled MCP servers in the configuration. + * @param config - Global settings configuration + * @param perChatOverrides - Optional per-chat server overrides */ export function hasEnabledMcpServers( config: SettingsConfigType, @@ -249,51 +271,3 @@ export function hasEnabledMcpServers( ): boolean { return Boolean(buildMcpClientConfig(config, perChatOverrides)); } - -/** - * Parses MCP server usage stats from settings. - * @param rawStats - The raw stats to parse - * @returns MCP server usage stats or empty object if invalid - */ -export function parseMcpServerUsageStats(rawStats: unknown): McpServerUsageStats { - if (!rawStats) return {}; - - if (typeof rawStats === 'string') { - const trimmed = rawStats.trim(); - if (!trimmed) return {}; - - try { - const parsed = JSON.parse(trimmed); - if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { - return parsed as McpServerUsageStats; - } - } catch { - console.warn('[MCP] Failed to parse mcpServerUsageStats JSON, ignoring value'); - } - } - - return {}; -} - -/** - * Gets usage count for a specific server. - * @param config - Global settings configuration - * @param serverId - The server ID to get the usage count for - * @returns The usage count for the server - */ -export function getMcpServerUsageCount(config: SettingsConfigType, serverId: string): number { - const stats = parseMcpServerUsageStats(config.mcpServerUsageStats); - return stats[serverId] || 0; -} - -/** - * Increments usage count for a server and returns updated stats JSON. - * @param config - Global settings configuration - * @param serverId - The server ID to increment the usage count for - * @returns The updated stats JSON - */ -export function incrementMcpServerUsage(config: SettingsConfigType, serverId: string): string { - const stats = parseMcpServerUsageStats(config.mcpServerUsageStats); - stats[serverId] = (stats[serverId] || 0) + 1; - return JSON.stringify(stats); -}