Skip to content

fix: prevent MCP servers from restarting on every settings file re-read#5897

Merged
markijbema merged 2 commits intoKilo-Org:mainfrom
evanjacobson:fix/mcp-config-restart-loop
Feb 16, 2026
Merged

fix: prevent MCP servers from restarting on every settings file re-read#5897
markijbema merged 2 commits intoKilo-Org:mainfrom
evanjacobson:fix/mcp-config-restart-loop

Conversation

@evanjacobson
Copy link
Contributor

Context

MCP servers restart every time a settings file is re-read, even when the configuration hasn't actually changed. This causes disruptive reconnection loops during normal usage (e.g. switching windows, saving unrelated files).

Fixes #5781, #5791

Implementation

The root cause is an asymmetric comparison in McpHub.updateServerConnections. The stored config (currentConnection.server.config) has been through both Zod validation (which applies defaults for timeout, alwaysAllow, disabledTools, cwd, type) and injectVariables (which resolves ${env:...} and ${workspaceFolder} placeholders). The incoming config from the settings file is raw — neither validated nor injected.

Since a typical user config omits optional fields like timeout and alwaysAllow, the Zod defaults alone guarantee the two sides of deepEqual can never match, triggering deleteConnection + connectToServer on every re-read.

The fix applies injectVariables to the already-validated config before comparison, so both sides are in the same normalized form:

// Before (raw config vs stored injected config — always mismatches)
} else if (!deepEqual(JSON.parse(currentConnection.server.config), config)) {

// After (injected validated config vs stored injected config — correct comparison)
} else if (
  !deepEqual(
    JSON.parse(currentConnection.server.config),
    await injectVariables(validatedConfig, {
      env: process.env,
      workspaceFolder: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "",
    }),
  )
) {

This is a single-line-to-8-line change in src/services/mcp/McpHub.ts.

Screenshots

N/A — backend logic change, no UI impact.

How to Test

  1. Configure an MCP server in your settings with a variable placeholder (e.g. ${workspaceFolder} in a path, or omit optional fields like timeout)
  2. Open a workspace and let the MCP server connect
  3. Trigger a settings file re-read (e.g. switch focus away and back, or touch the settings file)
  4. Before fix: The server disconnects and reconnects on every re-read (visible in MCP server status flashing)
  5. After fix: The server stays connected as long as the effective config hasn't changed

Get in Touch

@evanjacobson on the Kilo Code Discord

The config comparison in updateServerConnections compared the stored
validated+injected config against the raw config from the settings file.
Since Zod validation applies defaults (timeout, alwaysAllow, disabledTools,
cwd, type), the two sides were never deep-equal for typical configs that
omit optional fields, causing every file re-read to trigger unnecessary
server restarts.

Fix by applying both validation and variable injection to the incoming
config before comparing, so both sides of the deepEqual check are in
the same normalized form.

Fixes Kilo-Org#5781, Kilo-Org#5791
@changeset-bot
Copy link

changeset-bot bot commented Feb 16, 2026

🦋 Changeset detected

Latest commit: 0a3fae1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
kilo-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where MCP servers restart unnecessarily on every settings file re-read, even when the configuration hasn't actually changed. The root cause is an asymmetric comparison in McpHub.updateServerConnections where the stored config (which has been through Zod validation and variable injection) was being compared against raw config from the settings file (not validated or injected). The PR applies injectVariables to the validated config before comparison to normalize both sides.

Changes:

  • Modified the config comparison logic in McpHub.updateServerConnections to inject variables into the validated config before comparing with the stored config

Comment on lines +1777 to +1785
} else if (
!deepEqual(
JSON.parse(currentConnection.server.config),
await injectVariables(validatedConfig, {
env: process.env,
workspaceFolder: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "",
}),
)
) {
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code change should be marked with a kilocode_change comment. According to the project's guidelines for maintaining a fork of Roo, all changes should be marked to enable easier merging. Since this is a multi-line change, it should be wrapped with // kilocode_change start and // kilocode_change end comments.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1777 to +1785
} else if (
!deepEqual(
JSON.parse(currentConnection.server.config),
await injectVariables(validatedConfig, {
env: process.env,
workspaceFolder: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "",
}),
)
) {
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparison assumes that the stored config is always injected with variables, but this is only true for connected servers (line 1401). For placeholder connections created for disabled servers or when MCP is globally disabled (line 1093 in createPlaceholderConnection), the config is stored without variable injection. This creates an asymmetry: the stored config for placeholders is validated but not injected, while the new config being compared is both validated and injected. This means disabled servers will be incorrectly detected as having changed configs on every settings re-read, causing unnecessary deleteConnection and connectToServer calls. To fix this, either: (1) also inject variables when storing placeholder connection configs, or (2) conditionally inject variables in the comparison only for connected connections.

Copilot uses AI. Check for mistakes.
@markijbema markijbema merged commit 0068983 into Kilo-Org:main Feb 16, 2026
12 checks passed
@markijbema
Copy link
Contributor

Thanks! this was a very annoying problem!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCPs stuck in a restart loop after settings changes

2 participants

Comments