You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(spec): address Copilot review feedback on MCP dedup
- Fix Mode 1 origin to use full args array instead of args[0]
- Add deterministic null version comparison rules
- Define non-interactive conflict resolution (fail with error)
- Specify package-scoped keying for dedup:false entries
- Clarify uninstall reassignment for linked packages
- Align cache paths in lockfile.mdx with Astro docs convention
- Update merged_mcp_servers field description in spec
- Mode 1 (command+args): `command::{command} {args_joined}`, where `{args_joined}` is all elements of the `args` array joined with a single space. If `args` is empty or omitted, the origin is `command::{command}` (no trailing space). For example, `command: "npx"`, `args: ["-y", "@anthropic/context7-mcp"]` yields `command::npx -y @anthropic/context7-mcp`.
811
811
- Mode 2 (embedded mcpb): `bundle::{bundle_path}` normalized to the archive-relative path.
812
812
- Mode 3 (referenced mcpb): the `source` URL verbatim.
813
813
@@ -817,7 +817,8 @@ Two servers are considered the same when both key_name and origin match.
817
817
818
818
- Same identity, incoming version higher: replace. Re-render MCP config from the incoming package's template.
819
819
- Same identity, incoming version equal or lower: skip rendering. Track in lockfile only.
820
-
- Same key_name, different origin: conflict. The installer MUST warn the user. In interactive mode, the installer SHOULD offer to keep the existing server, replace it, or install both under distinct keys.
820
+
- When comparing versions, treat `null`/unknown as lower than any concrete semver version. If both are `null`/unknown, keep the existing server (treat as equal).
821
+
- Same key_name, different origin: conflict. In interactive mode, the installer MUST warn the user and offer to keep the existing server, replace it, or install both under distinct keys. In non-interactive mode, the installer MUST fail with a descriptive error (implementations MAY support a preconfigured conflict policy).
821
822
822
823
**User Override.** Deduplication is the default behavior. Installers MUST provide a mechanism for users to override deduplication:
823
824
@@ -1059,13 +1060,19 @@ sequenceDiagram
1059
1060
1060
1061
b. If no matching entry exists in `shared_mcp_servers`: render the template, merge into the host config, and add the server to `shared_mcp_servers` with `declared_by` set to the current package.
1061
1062
1062
-
c. If a match exists and the incoming version is higher: re-render using the incoming package's template, update `active_source` and `version`, and append the package to `declared_by`.
1063
+
c. If a match exists, compare versions. Treat `null`/unknown versions as lower than any concrete semver version. If both are `null`/unknown, treat them as equal.
1063
1064
1064
-
d. If a match exists and the incoming version is equal or lower: skip rendering and append the package to `declared_by` only.
1065
+
- If the incoming version is higher: re-render using the incoming package's template, update `active_source`and `version`, and append the package to `declared_by`.
1065
1066
1066
-
e. If the key_name matches but the origin differs: warn the user and offer resolution options (keep, replace, or install both under distinct keys).
1067
+
- If the incoming version is equal or lower (including both `null`): skip rendering and append the package to `declared_by` only.
1067
1068
1068
-
f. If the user has disabled dedup for this server (`dedup: false`), skip dedup checks and install the server as a separate entry.
1069
+
d. If the key_name matches but the origin differs (conflict):
1070
+
1071
+
-**Interactive mode**: warn the user and offer resolution options: keep the existing server, replace it, or install both under distinct keys.
1072
+
1073
+
-**Non-interactive mode**: fail the install with a non-zero exit status and a descriptive error. Implementations MAY allow a preconfigured conflict policy (e.g., via CLI flags) to resolve without prompting.
1074
+
1075
+
e. If the user has disabled dedup for this server (`dedup: false`), skip dedup checks and install the server under a package-scoped key (e.g., `{key_name}#{package_name}`). The installer MUST record the concrete key in the lockfile so uninstall can identify the correct entry.
1069
1076
1070
1077
Rendered `.lsp.json` files are written to the install location without deduplication (LSP server dedup is deferred to a future spec version).
1071
1078
@@ -1089,7 +1096,7 @@ Uninstalling a package reverses the install process:
1089
1096
1090
1097
a. If this package is the only entry in the server's `declared_by` list: remove the server from the host config and from `shared_mcp_servers`.
1091
1098
1092
-
b. If other packages remain in `declared_by`: remove this package from the list. If this package was the `active_source`, select the remaining package with the highest version, re-extract its MCP template from the archive cache, re-render with that package's config values, and update `active_source`. If this package was not the active source, no config change is needed.
1099
+
b. If other packages remain in `declared_by`: remove this package from the list. If this package was the `active_source`, select the remaining package with the highest version, re-materialize its MCP template, re-render with that package's config values, and update `active_source`. For archive-backed packages, re-extract from the archive cache. For linked packages (`source: link:...`), read the template directly from the linked directory. If this package was not the active source, no config change is needed.
1093
1100
1094
1101
c. If the server has `dedup: false`: remove only this package's copy. Other packages' copies are independent and unaffected.
1095
1102
4.**Remove lockfile entry.** Remove the package entry from `ccpkg-lock.json`.
@@ -1308,7 +1315,7 @@ The lockfile records the state of all installed packages at a given scope. It en
1308
1315
|`generated_plugin_json`|`boolean`| Whether `.claude-plugin/plugin.json` was generated by ccpkg during install/link. Controls cleanup on uninstall/unlink — if `true`, the generated file is removed; if `false`, it is left in place. |
1309
1316
|`enabled_plugins_key`|`string`| The key written to the host's `enabledPlugins` (e.g., `"api-testing@ccpkg"`). Used for clean deregistration on uninstall. |
1310
1317
|`installed_files`|`string[]`| List of all files written during install, relative to the package directory. Enables deterministic uninstall. Empty for linked packages. |
1311
-
|`merged_mcp_servers`|`string[]`| MCP server names merged into the host's `.mcp.json` during install. Used for clean removal on uninstall. |
1318
+
|`merged_mcp_servers`|`string[]`| MCP server names merged into the host's `.mcp.json` during install. Superseded by `shared_mcp_servers` for dedup-aware uninstall tracking. Retained for packages that do not participate in dedup (single-server packages with no shared MCP servers). |
1312
1319
|`config_keys`|`string[]`| Config variable names stored in the host's settings. Used for clean removal on uninstall. |
1313
1320
|`components`|`object`| Mirror of the manifest `components` object for quick reference. |
1314
1321
|`remote_sources`|`object`| Map of component path to remote source metadata. Only present for packages with remote component references. Keys are component identifiers; values are objects with `url`, `checksum`, `fetched_at`, and `cache_ttl`. |
- Mode 1 (command+args): `command::{command} {args_joined}`, where `{args_joined}` is all elements of the `args` array joined with a single space. If `args` is empty or omitted, the origin is `command::{command}` (no trailing space). For example, `command: "npx"`, `args: ["-y", "@anthropic/context7-mcp"]` yields `command::npx -y @anthropic/context7-mcp`.
296
296
- Mode 2 (embedded mcpb): `bundle::{bundle_path}` normalized to the archive-relative path.
297
297
- Mode 3 (referenced mcpb): the `source` URL verbatim.
298
298
@@ -302,7 +302,8 @@ Two servers are considered the same when both key_name and origin match.
302
302
303
303
- Same identity, incoming version higher: replace. Re-render MCP config from the incoming package's template.
304
304
- Same identity, incoming version equal or lower: skip rendering. Track in lockfile only.
305
-
- Same key_name, different origin: conflict. The installer MUST warn the user. In interactive mode, the installer SHOULD offer to keep the existing server, replace it, or install both under distinct keys.
305
+
- When comparing versions, treat `null`/unknown as lower than any concrete semver version. If both are `null`/unknown, keep the existing server (treat as equal).
306
+
- Same key_name, different origin: conflict. In interactive mode, the installer MUST warn the user and offer to keep the existing server, replace it, or install both under distinct keys. In non-interactive mode, the installer MUST fail with a descriptive error (implementations MAY support a preconfigured conflict policy).
306
307
307
308
**User Override.** Deduplication is the default behavior. Installers MUST provide a mechanism for users to override deduplication:
Copy file name to clipboardExpand all lines: src/content/docs/specification/install-lifecycle.mdx
+11-5Lines changed: 11 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,13 +64,19 @@ sequenceDiagram
64
64
65
65
b. If no matching entry exists in `shared_mcp_servers`: render the template, merge into the host config, and add the server to `shared_mcp_servers` with `declared_by` set to the current package.
66
66
67
-
c. If a match exists and the incoming version is higher: re-render using the incoming package's template, update `active_source` and `version`, and append the package to `declared_by`.
67
+
c. If a match exists, compare versions. Treat `null`/unknown versions as lower than any concrete semver version. If both are `null`/unknown, treat them as equal.
68
68
69
-
d. If a match exists and the incoming version is equal or lower: skip rendering and append the package to `declared_by` only.
69
+
- If the incoming version is higher: re-render using the incoming package's template, update `active_source`and `version`, and append the package to `declared_by`.
70
70
71
-
e. If the key_name matches but the origin differs: warn the user and offer resolution options (keep, replace, or install both under distinct keys).
71
+
- If the incoming version is equal or lower (including both `null`): skip rendering and append the package to `declared_by` only.
72
72
73
-
f. If the user has disabled dedup for this server (`dedup: false`), skip dedup checks and install the server as a separate entry.
73
+
d. If the key_name matches but the origin differs (conflict):
74
+
75
+
-**Interactive mode**: warn the user and offer resolution options: keep the existing server, replace it, or install both under distinct keys.
76
+
77
+
-**Non-interactive mode**: fail the install with a non-zero exit status and a descriptive error. Implementations MAY allow a preconfigured conflict policy (e.g., via CLI flags) to resolve without prompting.
78
+
79
+
e. If the user has disabled dedup for this server (`dedup: false`), skip dedup checks and install the server under a package-scoped key (e.g., `{key_name}#{package_name}`). The installer MUST record the concrete key in the lockfile so uninstall can identify the correct entry.
74
80
75
81
Rendered `.lsp.json` files are written to the install location without deduplication (LSP server dedup is deferred to a future spec version).
76
82
@@ -89,7 +95,7 @@ Uninstalling a package reverses the install process:
89
95
90
96
a. If this package is the only entry in the server's `declared_by` list: remove the server from the host config and from `shared_mcp_servers`.
91
97
92
-
b. If other packages remain in `declared_by`: remove this package from the list. If this package was the `active_source`, select the remaining package with the highest version, re-extract its MCP template from the archive cache, re-render with that package's config values, and update `active_source`. If this package was not the active source, no config change is needed.
98
+
b. If other packages remain in `declared_by`: remove this package from the list. If this package was the `active_source`, select the remaining package with the highest version, re-materialize its MCP template, re-render with that package's config values, and update `active_source`. For archive-backed packages, re-extract from the archive cache. For linked packages (`source: link:...`), read the template directly from the linked directory. If this package was not the active source, no config change is needed.
93
99
94
100
c. If the server has `dedup: false`: remove only this package's copy. Other packages' copies are independent and unaffected.
0 commit comments