| 2060 | } |
| 2061 | |
| 2062 | static int cbm_upsert_json_named_mcp(const char *binary_path, const char *config_path, |
| 2063 | const char *const *object_path, size_t path_len, |
| 2064 | cbm_json_mcp_schema_t schema, const char *entry_name, |
| 2065 | const char *argument) { |
| 2066 | if (!binary_path || !config_path || !object_path || !entry_name || !entry_name[0]) { |
| 2067 | return CLI_ERR; |
| 2068 | } |
| 2069 | char *document = NULL; |
| 2070 | size_t document_length = 0U; |
| 2071 | int read_result = cbm_json_like_read_document(config_path, &document, &document_length); |
| 2072 | if (read_result < 0) { |
| 2073 | return CLI_ERR; |
| 2074 | } |
| 2075 | if (read_result == 0) { |
| 2076 | char *command = NULL; |
| 2077 | int ownership = cbm_json_mcp_snapshot_ownership( |
| 2078 | document, document_length, object_path, path_len, schema, entry_name, argument, |
| 2079 | binary_path, g_previous_managed_mcp_command, &command); |
| 2080 | /* An entry that already says what we would say, but carries extra keys |
| 2081 | * the client added, is ALREADY SATISFIED. Return success without |
| 2082 | * touching the file. |
| 2083 | * |
| 2084 | * We must not rewrite it: the editor replaces an entry wholesale, so |
| 2085 | * writing our canonical shape over it would delete those keys. Doing |
| 2086 | * nothing is both correct and lossless — the entry already points at |
| 2087 | * this binary with the right type, which is the whole content of the |
| 2088 | * install. |
| 2089 | * |
| 2090 | * This is #1630: OpenCode writes `"enabled": true` next to our |
| 2091 | * `command` and `type`, so every user who had toggled a server in the |
| 2092 | * UI hit `op=mcp_install` failure. Confirmed on Linux and Windows with |
| 2093 | * two independent configs. Merging our fields into an annotated entry |
| 2094 | * while preserving the rest is the fuller fix and is tracked there; |
| 2095 | * this makes the common case work without risking anyone's config. */ |
| 2096 | if (ownership == CBM_JSON_LIKE_OBJECT_MATCH_WITH_EXTRAS) { |
| 2097 | /* Owned via the PREVIOUS managed binary during a relocating |
| 2098 | * update: the annotated entry still names the old location, and a |
| 2099 | * wholesale rewrite would drop the client's keys — repair only the |
| 2100 | * command member (#1630's deferred field-merge). An entry already |
| 2101 | * naming the current binary needs nothing. */ |
| 2102 | bool via_previous = command && g_previous_managed_mcp_command && |
| 2103 | strcmp(command, g_previous_managed_mcp_command) == 0 && |
| 2104 | strcmp(command, binary_path) != 0; |
| 2105 | if (!via_previous) { |
| 2106 | free(command); |
| 2107 | free(document); |
| 2108 | return CLI_OK; |
| 2109 | } |
| 2110 | char *value = cbm_json_mcp_render_command_value(binary_path, schema); |
| 2111 | if (!value) { |
| 2112 | free(command); |
| 2113 | free(document); |
| 2114 | return CLI_ERR; |
| 2115 | } |
| 2116 | int repair = cbm_json_like_replace_field_raw_if_unchanged( |
| 2117 | config_path, object_path, path_len, entry_name, "command", value, document, |
| 2118 | document_length); |
| 2119 | free(value); |
no test coverage detected