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.
mcp.enabledhas 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.jsonhad"mcp": { "enabled": true, "port": 5152, "network": { ... } }. The service read it, started the server, bound the LAN address, and logged success:Five seconds apart.
config.config_service.mcp_enabledwasfalse, and the supervisor loop reconciles to it: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.jsonis where MCP network exposure is configured —listen,allowFrom,encryptedTokenare file-only and have no store equivalent. Somcp.networkis file-authoritative whilemcp.enabledsitting right beside it is store-authoritative. Same object, two different owners, no indication which is which.This is the same class as the
darling.jsonseeding 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
Enabledoverrides a differingconfig.Mcp.Enabled, say so once at the point of override, naming both sides: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.