Skip to content

Commit 4733260

Browse files
authored
Merge pull request #9 from zircote/feat/mcp-dedup
Add MCP server deduplication to spec
2 parents 39ab20c + bc298d8 commit 4733260

10 files changed

Lines changed: 876 additions & 83 deletions

docs/plans/2026-02-14-ccpkg-design.md

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ nav_order: 3
66

77
# ccpkg Design Document
88

9-
| Field | Value |
9+
| Field | Value |
1010
|---------|--------------|
11-
| Date | 2026-02-14 |
12-
| Status | Draft |
13-
| Authors | Allen R. |
11+
| Date | 2026-02-14 |
12+
| Status | Draft |
13+
| Authors | Allen R. |
1414

1515
---
1616

@@ -161,11 +161,11 @@ Plugins frequently need configuration: API keys, file paths, feature flags, serv
161161

162162
```json
163163
{
164-
"config": {
165-
"API_KEY": { "type": "secret", "required": true, "description": "Service API key" },
166-
"MAX_RESULTS": { "type": "number", "default": 10 },
167-
"OUTPUT_FORMAT": { "type": "enum", "values": ["json", "text"], "default": "json" }
168-
}
164+
"config": {
165+
"API_KEY": { "type": "secret", "required": true, "description": "Service API key" },
166+
"MAX_RESULTS": { "type": "number", "default": 10 },
167+
"OUTPUT_FORMAT": { "type": "enum", "values": ["json", "text"], "default": "json" }
168+
}
169169
}
170170
```
171171

@@ -258,14 +258,14 @@ ccpkg packages install as Claude Code plugins. This is the fundamental integrati
258258

259259
```json
260260
{
261-
"extraKnownMarketplaces": {
262-
"ccpkg": {
263-
"source": {
264-
"source": "directory",
265-
"path": "~/.ccpkg/plugins"
266-
}
267-
}
268-
}
261+
"extraKnownMarketplaces": {
262+
"ccpkg": {
263+
"source": {
264+
"source": "directory",
265+
"path": "~/.ccpkg/plugins"
266+
}
267+
}
268+
}
269269
}
270270
```
271271

@@ -362,7 +362,7 @@ The ccpkg format targets multiple AI coding assistant hosts, but each host has f
362362

363363
---
364364

365-
### 15. Instructions Assembly Base + Per-Host Overlays
365+
### 15. Instructions Assembly. Base + Per-Host Overlays
366366

367367
**Decision**: The `components.instructions` field supports both a simple string form (single file) and a structured form declaring a base file with optional per-host overlay files. Overlays declare their assembly position (`append`, `prepend`, or `insert` at a named marker) via YAML frontmatter. The installer assembles the final instructions output per host at install time.
368368

@@ -372,7 +372,19 @@ The ccpkg format targets multiple AI coding assistant hosts, but each host has f
372372

373373
1. **Separate instruction files per host** -- Each host gets its own complete file (`claude-instructions.md`, `copilot-instructions.md`). Simple to implement but leads to content duplication. When shared content changes, authors must update N files. Rejected because it undermines the DRY principle and scales poorly with host count.
374374
2. **Template language with conditionals** -- Use a templating syntax (e.g., Handlebars, Jinja) with `{{#if host == "claude"}}` blocks. Powerful but introduces a template engine dependency, makes the raw files hard to read, and is overkill for what is typically "shared base + small per-host additions." Rejected for complexity.
375-
3. **mappings.json only (previous design)** -- Map a single canonical file to host-specific filenames without any content variation. Already proven insufficient — the filename mapping exists via `targets.*.instructions_file`, but the content is identical everywhere. Superseded by the assembly model which adds content variation on top of filename mapping.
375+
3. **mappings.json only (previous design)** -- Map a single canonical file to host-specific filenames without any content variation. Already proven insufficient. the filename mapping exists via `targets.*.instructions_file`, but the content is identical everywhere. Superseded by the assembly model which adds content variation on top of filename mapping.
376+
377+
### 16. MCP server deduplication at install time
378+
379+
When multiple packages bundle the same MCP server, the installer deduplicates at install time rather than requiring packages to declare shared dependencies or a shared MCP directory.
380+
381+
**Why install-time dedup?** Packages stay self-contained. Authors do not need to change anything. The dedup is transparent -- the installer is smarter about what it writes to the host config. This preserves Principle #1 (self-contained) and Principle #8 (no inter-package deps).
382+
383+
**Why not shared directory with refcounting?** Reference counting introduces a new complexity vector. Crashes mid-uninstall corrupt counts. It breaks the "each plugin is self-contained in its directory" model hosts expect.
384+
385+
**Why not a server_id manifest field?** Requires schema change and author adoption. Existing packages would not benefit. The key_name + origin tuple provides sufficient identity without opt-in.
386+
387+
**Identity model:** (key_name, origin) tuple. Origin is derived from server mode: command string for Mode 1, bundle path for Mode 2, source URL for Mode 3. Version resolution: highest wins. User override: per-server or global.
376388

377389
---
378390

@@ -429,7 +441,7 @@ Dev mode uses a symmetric pair of operations that mirror the install/uninstall l
429441
1. Validate the directory contains a valid `manifest.json`
430442
2. Prompt for required config values (same as install)
431443
3. Generate `.claude-plugin/plugin.json` inside the source directory (from manifest metadata)
432-
4. Create symlink: `~/.ccpkg/plugins/{name}` source directory
444+
4. Create symlink: `~/.ccpkg/plugins/{name}` -> source directory
433445
5. Add `{name}@ccpkg` to `enabledPlugins` in `settings.json`
434446
6. Render MCP/LSP templates with config substitution
435447
7. Write lockfile entry with `"source": "link:/absolute/path"`, `"linked": true`, and `"generated_plugin_json": true` (if plugin.json was created by ccpkg, not pre-existing)

docs/plans/2026-02-14-spec-grounding-revision.md

Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ccpkg Spec Grounding Revision Implementation Plan
1+
# ccpkg Spec Grounding Revision. Implementation Plan
22

33
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
44
@@ -21,35 +21,35 @@ Claude Code's plugin system provides automatic namespacing via `.claude-plugin/p
2121
- Plugin with `{"name": "my-package"}` + skill in `skills/my-skill/SKILL.md` = `/my-package:my-skill`
2222
- Namespace prefix: driven by `plugin.json` `name` field (NOT directory name, NOT SKILL.md frontmatter)
2323
- Component name within namespace: driven by skill **directory name** (NOT frontmatter `name`)
24-
- User-level skills (`~/.claude/skills/`) CANNOT be namespaced subdirectories flatten
25-
- User-level commands (`~/.claude/commands/`) CANNOT be namespaced subdirectories flatten
24+
- User-level skills (`~/.claude/skills/`) CANNOT be namespaced. subdirectories flatten
25+
- User-level commands (`~/.claude/commands/`) CANNOT be namespaced. subdirectories flatten
2626
- There is NO programmatic API to register namespaced components outside the plugin system
2727

2828
**Implication:** ccpkg packages MUST be installed as Claude Code plugins to get namespacing.
2929

3030
### Finding 2: extraKnownMarketplaces Is the Official Registration Entry Point
3131

32-
Claude Code supports third-party plugin sources via `extraKnownMarketplaces` in `settings.json`. This is the documented, supported API NOT `known_marketplaces.json` (which is internal).
32+
Claude Code supports third-party plugin sources via `extraKnownMarketplaces` in `settings.json`. This is the documented, supported API. NOT `known_marketplaces.json` (which is internal).
3333

3434
Seven source types are supported:
35-
1. `github` GitHub repositories
36-
2. `git` any Git URL
37-
3. `url` hosted marketplace.json
38-
4. `npm` npm packages
39-
5. `file` local marketplace.json file
40-
6. `directory` local directory of plugins
41-
7. `hostPattern` regex host matching
35+
1. `github`. GitHub repositories
36+
2. `git`. any Git URL
37+
3. `url`. hosted marketplace.json
38+
4. `npm`. npm packages
39+
5. `file`. local marketplace.json file
40+
6. `directory`. local directory of plugins
41+
7. `hostPattern`. regex host matching
4242

4343
**Implication:** ccpkg registers as a marketplace using the `directory` source type, pointing at `~/.ccpkg/plugins/`. Claude Code auto-discovers plugins in that directory.
4444

4545
### Finding 3: Six Plugin Loading Mechanisms Exist
4646

47-
1. **Marketplace installation** via `/plugin` UI or `claude plugin install`
48-
2. **`--plugin-dir` CLI flag** session-only, no persistence, for quick testing
49-
3. **CLI commands** `claude plugin install/uninstall/enable/disable`
50-
4. **Interactive `/plugin` UI** 4-tab manager (Discover, Installed, Marketplaces, Errors)
51-
5. **Project-level `.claude/settings.json`** team-shared, committed to git
52-
6. **Managed settings** enterprise/org level, highest precedence, read-only
47+
1. **Marketplace installation**. via `/plugin` UI or `claude plugin install`
48+
2. **`--plugin-dir` CLI flag**. session-only, no persistence, for quick testing
49+
3. **CLI commands**. `claude plugin install/uninstall/enable/disable`
50+
4. **Interactive `/plugin` UI**. 4-tab manager (Discover, Installed, Marketplaces, Errors)
51+
5. **Project-level `.claude/settings.json`**. team-shared, committed to git
52+
6. **Managed settings**. enterprise/org level, highest precedence, read-only
5353

5454
Scope precedence: Managed > Local > Project > User
5555

@@ -74,9 +74,9 @@ Confirmed: no hot-reload mid-session. Changes to plugin registration take effect
7474
- Pack/verify commands via ZIP + manifest validation
7575

7676
**Aspirational (requires Claude Code application changes):**
77-
- Hot-reload after install (no session restart) no API to notify Claude Code of new plugins mid-session
78-
- Host-aware lockfile loading Claude Code does not read `ccpkg-lock.json` at startup
79-
- Runtime component state machine (Idle Active Idle) no host-managed state tracking
77+
- Hot-reload after install (no session restart). no API to notify Claude Code of new plugins mid-session
78+
- Host-aware lockfile loading. Claude Code does not read `ccpkg-lock.json` at startup
79+
- Runtime component state machine (Idle -> Active -> Idle). no host-managed state tracking
8080

8181
### Finding 6: Install Architecture
8282

@@ -105,13 +105,13 @@ Confirmed: no hot-reload mid-session. Changes to plugin registration take effect
105105
6. Remove lockfile entry
106106
7. Inform user: session restart to deactivate
107107

108-
### Finding 7: Dev Mode Symmetric Link/Unlink
108+
### Finding 7: Dev Mode. Symmetric Link/Unlink
109109

110110
**`/ccpkg:link ~/Projects/my-plugin`:**
111111
1. Validate directory has valid `manifest.json`
112112
2. Prompt for required config values
113113
3. Generate `.claude-plugin/plugin.json` inside source directory (from manifest)
114-
4. Create symlink `~/.ccpkg/plugins/{name}` source directory
114+
4. Create symlink `~/.ccpkg/plugins/{name}` -> source directory
115115
5. Add to `enabledPlugins`, render templates, write lockfile with `"source": "link:/absolute/path"`
116116
6. Record `generated_plugin_json: true` in lockfile if plugin.json was created (not pre-existing)
117117

@@ -128,24 +128,24 @@ Confirmed: no hot-reload mid-session. Changes to plugin registration take effect
128128
The lockfile serves dual purpose: reproducibility record AND uninstall manifest.
129129

130130
Expanded fields per package entry:
131-
- `installed_files` list of all files written during install
132-
- `merged_mcp_servers` MCP server names merged into `.mcp.json`
133-
- `merged_hooks` hook entries merged into settings
134-
- `config_keys` config variable names stored in settings
135-
- `generated_plugin_json` boolean, whether `.claude-plugin/plugin.json` was generated
136-
- `linked` boolean, whether this is a dev-linked package
137-
- `source` URL, file path, or `link:/path` for dev mode
131+
- `installed_files`. list of all files written during install
132+
- `merged_mcp_servers`. MCP server names merged into `.mcp.json`
133+
- `merged_hooks`. hook entries merged into settings
134+
- `config_keys`. config variable names stored in settings
135+
- `generated_plugin_json`. boolean, whether `.claude-plugin/plugin.json` was generated
136+
- `linked`. boolean, whether this is a dev-linked package
137+
- `source`. URL, file path, or `link:/path` for dev mode
138138

139139
### Finding 9: Two Lockfiles, Two Audiences
140140

141-
- `installed_plugins.json` Claude Code's internal plugin registry (for host discovery)
142-
- `ccpkg-lock.json` ccpkg's lifecycle manifest (source URLs, checksums, config hashes, provenance)
141+
- `installed_plugins.json`. Claude Code's internal plugin registry (for host discovery)
142+
- `ccpkg-lock.json`. ccpkg's lifecycle manifest (source URLs, checksums, config hashes, provenance)
143143

144144
ccpkg writes to BOTH during install. They serve different systems.
145145

146146
---
147147

148-
## Task 1: Revise Specification Install Lifecycle Section
148+
## Task 1: Revise Specification. Install Lifecycle Section
149149

150150
**Files:**
151151
- Modify: `spec/specification.md` (lines ~730-830, Install Lifecycle section)
@@ -154,7 +154,7 @@ ccpkg writes to BOTH during install. They serve different systems.
154154

155155
Replace the current Mermaid sequence diagram. The new diagram must:
156156
- Remove the `Host->>User: Components available (no restart required)` step
157-
- Add explicit `Installer->>Installer: Generate .claude-plugin/plugin.json`
157+
- Add explicit `Installer->>Installer: Generate.claude-plugin/plugin.json`
158158
- Add explicit `Installer->>Host: Add to enabledPlugins in settings.json`
159159
- End with `Installer->>User: Installation complete. Restart session to activate.`
160160

@@ -191,7 +191,7 @@ Replace the current 5-step process with the symmetric link/unlink design:
191191

192192
---
193193

194-
## Task 2: Revise Specification Add Host Integration Section
194+
## Task 2: Revise Specification. Add Host Integration Section
195195

196196
**Files:**
197197
- Modify: `spec/specification.md` (new section after Install Lifecycle, before Lockfile Format)
@@ -219,15 +219,15 @@ Document:
219219
Document:
220220
- User scope: `extraKnownMarketplaces` in `~/.claude/settings.json`
221221
- Project scope: `extraKnownMarketplaces` in `{project}/.claude/settings.json`
222-
- Project settings are committed to git team members get prompted to install
222+
- Project settings are committed to git. team members get prompted to install
223223
- Managed scope: enterprise admins can allowlist ccpkg via `strictKnownMarketplaces`
224224
- Scope precedence: Managed > Local > Project > User
225225

226226
**Step 4: Commit**
227227

228228
---
229229

230-
## Task 3: Revise Specification Rewrite Lazy Loading Section
230+
## Task 3: Revise Specification. Rewrite Lazy Loading Section
231231

232232
**Files:**
233233
- Modify: `spec/specification.md` (lines ~896-923, Lazy Loading section)
@@ -238,7 +238,7 @@ Replace the current 5-step host-reads-lockfile description with:
238238
- State that Claude Code already implements lazy loading for skills: frontmatter (name + description) is loaded at startup, full SKILL.md body is loaded on invocation via the Skill tool
239239
- ccpkg leverages this existing behavior by placing well-formed SKILL.md files in standard plugin directories
240240
- No custom host-level lockfile reader is required
241-
- The host discovers ccpkg plugins via `extraKnownMarketplaces` directory source standard plugin component discovery
241+
- The host discovers ccpkg plugins via `extraKnownMarketplaces` -> directory source -> standard plugin component discovery
242242

243243
**Step 2: Keep "On-Demand Loading" table**
244244

@@ -256,7 +256,7 @@ Add a note: "These behaviors are provided by the host application's existing plu
256256

257257
---
258258

259-
## Task 4: Revise Specification Expand Lockfile Schema
259+
## Task 4: Revise Specification. Expand Lockfile Schema
260260

261261
**Files:**
262262
- Modify: `spec/specification.md` (lines ~831-895, Lockfile Format section)
@@ -285,7 +285,7 @@ Show a complete example with all new fields, including one installed package and
285285

286286
---
287287

288-
## Task 5: Revise Specification Add Aspirational Appendix
288+
## Task 5: Revise Specification. Add Aspirational Appendix
289289

290290
**Files:**
291291
- Modify: `spec/specification.md` (new Appendix D after Appendix C)
@@ -294,26 +294,26 @@ Show a complete example with all new fields, including one installed package and
294294

295295
Move all aspirational content here with clear framing:
296296

297-
Section D.1 Hot-Reload After Install:
297+
Section D.1. Hot-Reload After Install:
298298
- Description: Components become available immediately after install without session restart
299299
- Requires: A host API or file-watch mechanism to detect new plugins mid-session
300300
- Current behavior: Session restart required
301301

302-
Section D.2 Host-Aware Lockfile Loading:
302+
Section D.2. Host-Aware Lockfile Loading:
303303
- Description: Host reads `ccpkg-lock.json` at startup for optimized package discovery
304304
- Requires: Host application to understand ccpkg lockfile format
305305
- Current behavior: Host discovers packages via `extraKnownMarketplaces` and standard plugin directories
306306

307-
Section D.3 Runtime Component State Machine:
308-
- Description: Components track Idle Active Idle lifecycle states
307+
Section D.3. Runtime Component State Machine:
308+
- Description: Components track Idle -> Active -> Idle lifecycle states
309309
- Requires: Host-managed activation tracking per component
310310
- Current behavior: Components are either installed (files exist in plugin directory) or not
311311

312312
**Step 2: Commit**
313313

314314
---
315315

316-
## Task 6: Revise Specification Update Archive Directory Structure
316+
## Task 6: Revise Specification. Update Archive Directory Structure
317317

318318
**Files:**
319319
- Modify: `spec/specification.md` (lines ~136-169, Directory Structure)
@@ -322,14 +322,14 @@ Section D.3 — Runtime Component State Machine:
322322

323323
The archive structure itself does not change. But add a note after the diagram explaining:
324324
- At install time, the installer generates `.claude-plugin/plugin.json` from `manifest.json` metadata
325-
- This generated file is NOT part of the archive it is a host-specific artifact created during installation
326-
- The mapping: manifest `name` plugin.json `name`, manifest `version` plugin.json `version`, manifest `description` plugin.json `description`, manifest `author` plugin.json `author`
325+
- This generated file is NOT part of the archive. it is a host-specific artifact created during installation
326+
- The mapping: manifest `name` -> plugin.json `name`, manifest `version` -> plugin.json `version`, manifest `description` -> plugin.json `description`, manifest `author` -> plugin.json `author`
327327

328328
**Step 2: Commit**
329329

330330
---
331331

332-
## Task 7: Revise Design Document Add Plugin System Integration
332+
## Task 7: Revise Design Document. Add Plugin System Integration
333333

334334
**Files:**
335335
- Modify: `docs/plans/2026-02-14-ccpkg-design.md`
@@ -378,10 +378,10 @@ Replace with the symmetric link/unlink design:
378378

379379
**Step 1: Review schema for any required changes**
380380

381-
The manifest schema itself should NOT change significantly the manifest is the package author's contract. The `.claude-plugin/plugin.json` generation is an installer concern, not a manifest concern.
381+
The manifest schema itself should NOT change significantly. the manifest is the package author's contract. The `.claude-plugin/plugin.json` generation is an installer concern, not a manifest concern.
382382

383383
However, verify:
384-
- The `scope` enum still makes sense (it does `user`, `project`, `any`)
384+
- The `scope` enum still makes sense (it does. `user`, `project`, `any`)
385385
- The `targets` object can accommodate plugin.json generation hints if needed
386386
- No fields reference the old `~/.claude/packages/` path
387387

@@ -424,7 +424,7 @@ Capture the following to mnemonic:
424424
- `_semantic/decisions`: ccpkg installs as Claude Code plugins via extraKnownMarketplaces directory source
425425
- `_semantic/decisions`: Namespacing handled by plugin system, not file editing
426426
- `_semantic/knowledge`: installed_plugins.json is read-only at startup, no hot-reload
427-
- `_procedural/patterns`: Symmetric link/unlink manages .claude-plugin/plugin.json lifecycle
427+
- `_procedural/patterns`: Symmetric link/unlink manages.claude-plugin/plugin.json lifecycle
428428
- `_semantic/knowledge`: Six plugin loading mechanisms in Claude Code (marketplace, --plugin-dir, CLI, /plugin UI, project settings, managed settings)
429429

430430
---
@@ -461,7 +461,7 @@ Each task ends with a commit. Commit messages follow:
461461

462462
### What This Plan Does NOT Cover
463463

464-
- Implementation of ccpkg CLI/skills (pack, install, verify, etc.) that is a separate plan
465-
- Implementation of the registry protocol that is a separate plan
466-
- Testing no tests exist yet for the spec itself
467-
- GitHub Pages deployment the deploy.yml workflow already exists
464+
- Implementation of ccpkg CLI/skills (pack, install, verify, etc.). that is a separate plan
465+
- Implementation of the registry protocol. that is a separate plan
466+
- Testing. no tests exist yet for the spec itself
467+
- GitHub Pages deployment. the deploy.yml workflow already exists

0 commit comments

Comments
 (0)