ui: keep MCP connections stable across policy switches

ensureInitialized folds the policy into its config signature, so
alternating two conversations with different policies tore down and
reconnected every server with health checks included. Tool collection
already filters by the flow policy, so initialize every
settings-enabled server instead and never pass a policy into the MCP
config. The duplicated policy-server check becomes one accessor on
ConversationPreferences.

Assisted-by: pi
This commit is contained in:
Aleksander Grygier
2026-08-27 11:19:21 +02:00
parent 9b521a7caf
commit 3d964e84f2
3 changed files with 25 additions and 24 deletions
@@ -329,20 +329,12 @@ class AgenticStore {
const disabledToolCategories = new Set(
toolPolicy?.disabledToolCategories ?? toolsStore.disabledToolCategories
);
// servers usable under this flow's policy: globally enabled, category on,
// and their server-scoped group key not disabled
const policyEnabledServerIds = new Set(
disabledToolCategories.has(ToolSource.MCP)
? []
: mcpStore
.getServers()
.filter((s) => s.enabled && !disabledTools.has(toolsStore.getMcpServerToolsKey(s.id)))
.map((s) => s.id)
);
const hasMcpServers = policyEnabledServerIds.size > 0;
// initialize every settings-enabled server; tool collection filters by this
// flow's policy, so switching policies never re-initializes connections
const hasMcpServers = conversationsStore.preferences.policyEnabledServerIds().length > 0;
if (hasMcpServers) {
const initialized = await mcpStore.ensureInitialized(policyEnabledServerIds);
const initialized = await mcpStore.ensureInitialized();
if (!initialized) {
console.log('[AgenticStore] MCP not initialized');
@@ -15,6 +15,7 @@ import { REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY } from '$lib/constants';
import { ReasoningEffort, ToolSource } from '$lib/enums';
import { DatabaseService } from '$lib/services/database.service';
// direct imports between stores, not via the barrel, to avoid circular deps
import { mcpStore } from '$lib/stores/mcp/index.svelte';
import { toolsStore } from '$lib/stores/tools.svelte';
import type { DatabaseConversation, ToolEntry, ToolGroup } from '$lib/types';
@@ -180,6 +181,21 @@ export class ConversationPreferences {
);
}
/**
* MCP servers usable under the effective policy: globally enabled, url set,
* MCP category on and the server-scoped key not disabled.
*/
policyEnabledServerIds(): string[] {
if (!this.isCategoryEnabled(ToolSource.MCP)) return [];
return mcpStore
.getServers()
.filter(
(server) => server.enabled && server.url.trim() && this.isServerToolsEnabled(server.id)
)
.map((server) => server.id);
}
/** Reload persisted defaults, e.g. when the active conversation is cleared. */
resetPending(): void {
this.pendingReasoningEffort = loadReasoningEffortDefault();
+5 -12
View File
@@ -307,17 +307,15 @@ class MCPStore implements McpHealthHost {
}
/**
* Initialize connections. Callers can restrict which servers connect by
* passing their ids (e.g. a conversation's tool policy); a different set
* changes the config signature and re-initializes, same as a settings
* change.
* Initialize every settings-enabled server. Policy filtering happens at tool
* collection time, so switching conversation policies never re-initializes.
*/
async ensureInitialized(serverIds?: ReadonlySet<string>): Promise<boolean> {
async ensureInitialized(): Promise<boolean> {
if (!browser) {
return false;
}
const mcpConfig = this.buildMcpClientConfig(settingsStore.config, serverIds);
const mcpConfig = this.buildMcpClientConfig(settingsStore.config);
const signature = mcpConfig ? JSON.stringify(mcpConfig) : null;
if (!signature) {
@@ -1119,10 +1117,7 @@ class MCPStore implements McpHealthHost {
/**
* Builds MCP client configuration from settings.
*/
private buildMcpClientConfig(
cfg: SettingsConfigType,
serverIds?: ReadonlySet<string>
): MCPClientConfig | undefined {
private buildMcpClientConfig(cfg: SettingsConfigType): MCPClientConfig | undefined {
const rawServers = parseMcpServerSettings(cfg.mcpServers);
if (!rawServers.length) {
@@ -1134,8 +1129,6 @@ class MCPStore implements McpHealthHost {
for (const [index, entry] of rawServers.entries()) {
if (!entry.enabled) continue;
if (serverIds && !serverIds.has(entry.id)) continue;
const normalized = this.buildServerConfig(entry);
if (normalized) servers[this.generateServerId(entry.id, index)] = normalized;