What happens
PUT /api/mcp/servers/{id} with a payload that touches one unrelated field silently
rewrites two other columns on the shared MCPServer row. On a row stored with
managed="external" and restart_policy="always":
BEFORE: restart_policy='always' auto_start=True
PUT : {"description": "a new description"}
AFTER : restart_policy='no' auto_start=None
The request returns 200. Nothing in the response or the UI says the restart settings
changed.
Mechanism
The route does not write the payload's fields onto the row. It rebuilds the whole
configuration and writes every field of the rebuilt object back, so any column the
round-trip drops comes back as a config default.
_build_server_config (src/xagent/web/api/mcp.py) starts from
server.to_config_dict() and merges the payload into it.
MCPServer.to_config_dict() (src/xagent/core/tools/core/mcp/model.py) emits
restart_policy, auto_start and the docker fields inside a
if self.managed == "internal": block. For an external row it emits none of them,
so the stored values never reach the config object.
MCPServerConfig (src/xagent/core/tools/core/mcp/data_config.py) then supplies its
own defaults for the absent keys: restart_policy: str = Field("no", ...) and
auto_start left as None.
_update_server_from_config (src/xagent/web/api/mcp.py) writes back every name in
_MCP_SERVER_CONFIGURABLE_FIELDS, and that tuple contains both restart_policy and
auto_start. The loop is an unconditional setattr per field — there is no
"only if the payload carried it" guard — so the defaults land on the row.
The same round-trip drops docker_url, volumes and bind_ports for an external row
by the same mechanism.
How to reproduce
- Store an MCP server row with
managed="external", restart_policy="always" and
auto_start=True.
PUT /api/mcp/servers/{id} with {"description": "a new description"}.
- Read the row back:
restart_policy is 'no' and auto_start is NULL.
Expected
A payload that does not name restart_policy or auto_start must not change them. More
generally, the rebuild should not be able to write a column whose stored value it never
read — either the round-trip carries every stored column through, or the write-back is
restricted to the fields the payload actually carried.
Related
What happens
PUT /api/mcp/servers/{id}with a payload that touches one unrelated field silentlyrewrites two other columns on the shared
MCPServerrow. On a row stored withmanaged="external"andrestart_policy="always":The request returns
200. Nothing in the response or the UI says the restart settingschanged.
Mechanism
The route does not write the payload's fields onto the row. It rebuilds the whole
configuration and writes every field of the rebuilt object back, so any column the
round-trip drops comes back as a config default.
_build_server_config(src/xagent/web/api/mcp.py) starts fromserver.to_config_dict()and merges the payload into it.MCPServer.to_config_dict()(src/xagent/core/tools/core/mcp/model.py) emitsrestart_policy,auto_startand the docker fields inside aif self.managed == "internal":block. For anexternalrow it emits none of them,so the stored values never reach the config object.
MCPServerConfig(src/xagent/core/tools/core/mcp/data_config.py) then supplies itsown defaults for the absent keys:
restart_policy: str = Field("no", ...)andauto_startleft asNone._update_server_from_config(src/xagent/web/api/mcp.py) writes back every name in_MCP_SERVER_CONFIGURABLE_FIELDS, and that tuple contains bothrestart_policyandauto_start. The loop is an unconditionalsetattrper field — there is no"only if the payload carried it" guard — so the defaults land on the row.
The same round-trip drops
docker_url,volumesandbind_portsfor anexternalrowby the same mechanism.
How to reproduce
managed="external",restart_policy="always"andauto_start=True.PUT /api/mcp/servers/{id}with{"description": "a new description"}.restart_policyis'no'andauto_startisNULL.Expected
A payload that does not name
restart_policyorauto_startmust not change them. Moregenerally, the rebuild should not be able to write a column whose stored value it never
read — either the round-trip carries every stored column through, or the write-back is
restricted to the fields the payload actually carried.
Related
managedto"external". Same function, sameround-trip, different columns; carrying the stored
managedthrough does not by itselfstop an
externalrow'srestart_policyfrom being reset, so the two need separatefixes.
is_activeand/oruser_envno longer rebuilds, so this loss no longer happens onthat path. Every payload that writes the shared definition row still rebuilds, so this
issue is untouched there. fix(connectors): lock the MCP server definition row on edit #2060 does not fix it and does not claim to; its description
records it as a known limitation.