Skip to content

mcp.enabled is store-authoritative but mcp.network is file-authoritative, and the disagreement logs as success #2389

Description

@erikdarlingdata

mcp.enabled has two authorities that can disagree, the store silently wins, and the only evidence is one INFO line after a success message. Setting the config file alone looks like it worked for about five seconds.

Hit while wiring up the MCP on prod-sql-use1-pgmonitor-01.

What it looks like

darling.json had "mcp": { "enabled": true, "port": 5152, "network": { ... } }. The service read it, started the server, bound the LAN address, and logged success:

22:58:51 [INFO] [DarlingMcpHostService] Starting MCP server on http://10.149.45.159:5152
                (LAN-exposed to 10.150.0.0/16 behind a bearer token + in-app CIDR; loopback also bound)
22:58:56 [INFO] [DarlingMcpHostService] MCP server disabled via the control plane - stopping (no restart needed)

Five seconds apart. config.config_service.mcp_enabled was false, and the supervisor loop reconciles to it:

var enabled = published?.Enabled ?? config.Mcp.Enabled;

The store wins whenever a published row exists, which on any seeded store it always does. The file value is effectively dead.

Why this is worth a diagnostic rather than a doc note

The failure presents as success. An operator edits the config, restarts, greps the log, finds Starting MCP server on http://… and stops reading — that line is true, and the server really did bind. The contradiction is five seconds later at the same INFO level with no error, no warning, and nothing tying it back to the file they just edited.

It also inverts the usual mental model on this box family. darling.json is where MCP network exposure is configured — listen, allowFrom, encryptedToken are file-only and have no store equivalent. So mcp.network is file-authoritative while mcp.enabled sitting right beside it is store-authoritative. Same object, two different owners, no indication which is which.

This is the same class as the darling.json seeding trap already documented elsewhere ("the store, not the config file, is authoritative for servers") — but that one at least fails by doing nothing visible, whereas this one actively reports the opposite of what happens.

Suggested fix

Warn on the disagreement, not just the outcome. When a published Enabled overrides a differing config.Mcp.Enabled, say so once at the point of override, naming both sides:

MCP is enabled in darling.json but DISABLED in the control plane (config.config_service.mcp_enabled);
the store wins. Set it in the store, or the file value will keep being ignored.

Cheap, fires only on the mismatch, and lands exactly where someone is looking. Same treatment would suit port (published?.Port ?? config.Mcp.Port), which has the identical shape and would produce a server on an unexpected port with no explanation.

Worth considering whether the success log should be deferred until after the supervisor's first reconcile, so "Starting MCP server on …" is not printed for a server that is about to be stopped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions