feat: Simplify MCP server enabling logic per chat

Refactors MCP server enabling logic to remove the dependency on global settings.

This simplifies the logic by directly checking the per-chat override status, and removes the need to pass the global enabled state as a parameter.

Additionally:
- Only shows MCP servers that are enabled in settings in the selector.
- Sorts the servers by whether they are enabled for the current chat.
This commit is contained in:
Aleksander Grygier 2026-01-19 16:43:53 +01:00
parent 62ed7f112d
commit 54192b05fb
5 changed files with 63 additions and 48 deletions

View File

@ -433,14 +433,12 @@ export class ConversationsClient {
/**
* Checks if an MCP server is enabled for the active conversation.
* Per-chat override takes precedence over global setting.
* @param serverId - The server ID to check
* @param globalEnabled - The global enabled state from settings
* @returns True if server is enabled for this conversation
*/
isMcpServerEnabledForChat(serverId: string, globalEnabled: boolean): boolean {
isMcpServerEnabledForChat(serverId: string): boolean {
const override = this.getMcpServerOverride(serverId);
return override !== undefined ? override.enabled : globalEnabled;
return override?.enabled ?? false;
}
/**
@ -500,18 +498,17 @@ export class ConversationsClient {
/**
* Toggles MCP server enabled state for the active conversation.
* @param serverId - The server ID to toggle
* @param globalEnabled - The global enabled state from settings
*/
async toggleMcpServerForChat(serverId: string, globalEnabled: boolean): Promise<void> {
const currentEnabled = this.isMcpServerEnabledForChat(serverId, globalEnabled);
async toggleMcpServerForChat(serverId: string): Promise<void> {
const currentEnabled = this.isMcpServerEnabledForChat(serverId);
await this.setMcpServerOverride(serverId, !currentEnabled);
}
/**
* Resets MCP server to use global setting (removes per-chat override).
* @param serverId - The server ID to reset
* Removes MCP server override for the active conversation.
* @param serverId - The server ID to remove override for
*/
async resetMcpServerToGlobal(serverId: string): Promise<void> {
async removeMcpServerOverride(serverId: string): Promise<void> {
await this.setMcpServerOverride(serverId, undefined);
}

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from 'svelte';
import { ChevronDown, Settings } from '@lucide/svelte';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { Switch } from '$lib/components/ui/switch';
@ -12,7 +11,8 @@
import type { MCPServerSettingsEntry } from '$lib/types';
import { HealthCheckStatus } from '$lib/enums';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { mcpClient } from '$lib/clients/mcp.client';
import { onMount } from 'svelte';
import { mcpClient } from '$lib/clients';
interface Props {
class?: string;
@ -24,8 +24,9 @@
let searchQuery = $state('');
// Only show servers that are enabled in settings (available for use)
let mcpServers = $derived.by(() => {
return parseMcpServerSettings(settingsStore.config.mcpServers);
return parseMcpServerSettings(settingsStore.config.mcpServers).filter((s) => s.enabled);
});
let hasMcpServers = $derived(mcpServers.length > 0);
@ -36,12 +37,8 @@
return mcpUsageStats[serverId] || 0;
}
function isServerEnabledForChat(server: MCPServerSettingsEntry): boolean {
return conversationsStore.isMcpServerEnabledForChat(server.id, server.enabled);
}
function hasPerChatOverride(serverId: string): boolean {
return conversationsStore.getMcpServerOverride(serverId) !== undefined;
function isServerEnabledForChat(serverId: string): boolean {
return conversationsStore.isMcpServerEnabledForChat(serverId);
}
function getServerLabel(server: MCPServerSettingsEntry): string {
@ -49,7 +46,7 @@
}
let enabledMcpServersForChat = $derived(
mcpServers.filter((s) => isServerEnabledForChat(s) && s.url.trim())
mcpServers.filter((s) => isServerEnabledForChat(s.id) && s.url.trim())
);
let healthyEnabledMcpServers = $derived(
@ -63,8 +60,10 @@
let sortedMcpServers = $derived(
[...mcpServers].sort((a, b) => {
// First: globally enabled servers come first
if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
// First: enabled for chat servers come first
const aEnabled = isServerEnabledForChat(a.id);
const bEnabled = isServerEnabledForChat(b.id);
if (aEnabled !== bEnabled) return aEnabled ? -1 : 1;
// Then sort by usage count (descending)
const usageA = getServerUsageCount(a.id);
@ -91,8 +90,8 @@
let extraServersCount = $derived(Math.max(0, healthyEnabledMcpServers.length - 3));
async function toggleServerForChat(serverId: string, globalEnabled: boolean) {
await conversationsStore.toggleMcpServerForChat(serverId, globalEnabled);
async function toggleServerForChat(serverId: string) {
await conversationsStore.toggleMcpServerForChat(serverId);
}
let mcpFavicons = $derived(
@ -162,8 +161,7 @@
{#each filteredMcpServers() as server (server.id)}
{@const healthState = mcpStore.getHealthCheckState(server.id)}
{@const hasError = healthState.status === HealthCheckStatus.Error}
{@const isEnabledForChat = isServerEnabledForChat(server)}
{@const hasOverride = hasPerChatOverride(server.id)}
{@const isEnabledForChat = isServerEnabledForChat(server.id)}
<div class="flex items-center justify-between gap-2 px-2 py-2">
<div class="flex min-w-0 flex-1 items-center gap-2">
@ -182,17 +180,12 @@
<span class="shrink-0 rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive"
>Error</span
>
{:else if server.enabled}
<span class="shrink-0 rounded bg-primary/15 px-1.5 py-0.5 text-xs text-primary"
>Global</span
>
{/if}
</div>
<Switch
checked={isEnabledForChat}
onCheckedChange={() => toggleServerForChat(server.id, server.enabled)}
onCheckedChange={() => toggleServerForChat(server.id)}
disabled={hasError}
class={hasOverride ? 'ring-2 ring-primary/50 ring-offset-1' : ''}
/>
</div>
{/each}

View File

@ -211,12 +211,24 @@ class ConversationsStore {
return this.client.getMcpServerOverride(serverId);
}
isMcpServerEnabledForChat(serverId: string, globalEnabled: boolean): boolean {
/**
* Get all MCP server overrides for the current conversation.
* Returns pending overrides if no active conversation.
*/
getAllMcpServerOverrides(): McpServerOverride[] {
if (this.activeConversation?.mcpServerOverrides) {
return this.activeConversation.mcpServerOverrides;
}
return this.pendingMcpServerOverrides;
}
isMcpServerEnabledForChat(serverId: string): boolean {
if (!this.client) {
const override = this.pendingMcpServerOverrides.find((o) => o.serverId === serverId);
return override !== undefined ? override.enabled : globalEnabled;
return override?.enabled ?? false;
}
return this.client.isMcpServerEnabledForChat(serverId, globalEnabled);
return this.client.isMcpServerEnabledForChat(serverId);
}
async setMcpServerOverride(serverId: string, enabled: boolean | undefined): Promise<void> {
@ -224,14 +236,14 @@ class ConversationsStore {
return this.client.setMcpServerOverride(serverId, enabled);
}
async toggleMcpServerForChat(serverId: string, globalEnabled: boolean): Promise<void> {
async toggleMcpServerForChat(serverId: string): Promise<void> {
if (!this.client) return;
return this.client.toggleMcpServerForChat(serverId, globalEnabled);
return this.client.toggleMcpServerForChat(serverId);
}
async resetMcpServerToGlobal(serverId: string): Promise<void> {
async removeMcpServerOverride(serverId: string): Promise<void> {
if (!this.client) return;
return this.client.resetMcpServerToGlobal(serverId);
return this.client.removeMcpServerOverride(serverId);
}
clearPendingMcpServerOverrides(): void {

View File

@ -210,7 +210,16 @@ class MCPStore {
}
/**
* Check if there are any enabled MCP servers
* Check if there are any available MCP servers (enabled in settings).
* Used to determine if McpSelector should be shown.
*/
hasAvailableServers(): boolean {
const servers = parseMcpServerSettings(config().mcpServers);
return servers.some((s) => s.enabled && s.url.trim());
}
/**
* Check if there are any MCP servers enabled for the current chat.
*/
hasEnabledServers(perChatOverrides?: McpServerOverride[]): boolean {
return Boolean(buildMcpClientConfig(config(), perChatOverrides));

View File

@ -192,22 +192,26 @@ function buildServerConfig(
}
/**
* Checks if a server is enabled considering per-chat overrides.
* Per-chat override takes precedence over global setting.
* Checks if a server is enabled for the current chat.
* Server must be available (server.enabled) AND have a per-chat override enabling it.
* Pure helper function - no side effects.
*/
export function checkServerEnabled(
server: MCPServerSettingsEntry,
perChatOverrides?: McpServerOverride[]
): boolean {
if (perChatOverrides) {
const override = perChatOverrides.find((o) => o.serverId === server.id);
if (override !== undefined) {
return override.enabled;
}
// Server must be available in settings first
if (!server.enabled) {
return false;
}
return server.enabled;
// Then check if it's enabled for this chat via override
if (perChatOverrides) {
const override = perChatOverrides.find((o) => o.serverId === server.id);
return override?.enabled ?? false;
}
return false;
}
/**