diff --git a/AGENTS.md b/AGENTS.md index 9187505..84c425f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,9 @@ two parity-checked manifests. Sibling repo to [devantler-tech/agent-skills](http By design the marketplace is **not scoped to skills-only** — a plugin may bundle any agent resource (agent skills today; [MCP](https://modelcontextprotocol.io) servers and custom agents as they prove out -across the supported tools), which is what keeps it a tool-neutral, industry-standard marketplace. +across the supported tools), which is what keeps it a tool-neutral, industry-standard marketplace. The +cross-tool capability matrix and the manifest/CI plan for the first non-skill resource are recorded in +[ADR 0001](docs/adr/0001-bundling-mcp-servers-and-custom-agents.md). This file is the single canonical instructions file for the repository. It is read natively by GitHub Copilot, and by Cursor, Codex, and Claude (via `CLAUDE.md` → `@AGENTS.md`). diff --git a/docs/adr/0001-bundling-mcp-servers-and-custom-agents.md b/docs/adr/0001-bundling-mcp-servers-and-custom-agents.md new file mode 100644 index 0000000..0b6b264 --- /dev/null +++ b/docs/adr/0001-bundling-mcp-servers-and-custom-agents.md @@ -0,0 +1,224 @@ +# ADR 0001 — Bundling MCP servers and custom agents (cross-tool prove-out) + +- **Status:** Proposed +- **Date:** 2026-06-30 +- **Deciders:** devantler-tech maintainers +- **Issue:** [#39](https://github.com/devantler-tech/agent-plugins/issues/39) (Part of roadmap epic [#38](https://github.com/devantler-tech/agent-plugins/issues/38), Theme 1 — *realize "not skills-only"*) + +> 🤖 Generated by the Daily AI Assistant + +## Context + +`AGENTS.md` and the README already commit this marketplace to being **tool-neutral and not +skills-only** — *"a plugin may bundle any agent resource (agent skills today; MCP servers and custom +agents as they prove out across the supported tools)."* That direction is settled maintainer policy +([#15](https://github.com/devantler-tech/agent-plugins/issues/15) was closed *"I do not want this +[skills-only scoping]"*; [#7](https://github.com/devantler-tech/agent-plugins/issues/7) rescoped the +repo cross-tool). + +Yet **all six published plugins are skills-only**, so the marketplace's defining cross-tool capability +is unrealized, and the CI gate (`scripts/validate-manifests.sh`) actively *assumes* skills-only — it +hard-requires `"skills": "skills/"` plus a non-empty `skills/` directory for **every** plugin. Before we +ship the first non-skill resource we need a grounded answer to two questions: + +1. **What is genuinely tool-neutral** about bundling an MCP server or a custom agent across the three + supported tools (Claude Code, GitHub Copilot CLI, VS Code), and what is tool-specific? +2. **What must the manifests and CI change** to describe and validate a non-skill plugin without + weakening the parity guarantees that make the marketplace trustworthy? + +This ADR answers both (AC#1 + AC#2 of #39). The first concrete bundle (AC#3) is a follow-up child so +this change stays documentation-only and low-blast-radius. + +## Cross-tool capability matrix + +How each supported tool consumes the three resource types. Grounded in the official docs (Claude Code +[plugins reference](https://code.claude.com/docs/en/plugins-reference); GitHub Copilot +[about plugins](https://docs.github.com/en/copilot/concepts/agents/about-plugins) + +[CLI plugin reference](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference); +VS Code [MCP config](https://code.visualstudio.com/docs/agents/reference/mcp-configuration) / +[custom agents](https://code.visualstudio.com/docs/agent-customization/custom-agents)). + +> **Correction (2026-06-30, on maintainer review):** an earlier draft of this ADR claimed only Claude +> Code bundles MCP servers / custom agents and that *"there is no cross-tool plugin-bundle standard."* +> That is **wrong**. **GitHub Copilot CLI (and the Copilot cloud agent) ship a first-class plugin +> system** — a `plugin.json` manifest plus a `marketplace.json` catalog that bundles skills, **agents, +> MCP servers**, hooks, commands and LSP servers as one installable unit — and its schema **overlaps +> Claude Code's almost exactly**. Copilot CLI's manifest search order even *includes* +> `.claude-plugin/plugin.json`, so **this repo's existing `.claude-plugin/marketplace.json` is already a +> valid Copilot CLI marketplace.** The real surface that *can't* bundle is **VS Code** (plugins aren't a +> VS Code concept; it consumes MCP/agents as per-workspace/repo config). The matrix and analysis below +> are the corrected version. + +| Capability | Claude Code | GitHub Copilot CLI / cloud agent | VS Code (Copilot) | +|---|---|---|---| +| **Plugin / marketplace bundle** | ✅ `.claude-plugin/marketplace.json` + a plugin dir (`plugin.json`) bundling skills, `agents/`, `.mcp.json`, hooks, commands — installed as one unit | ✅ **`plugin.json` + `marketplace.json`** bundling `skills`, `agents`, `mcpServers`, `hooks`, `commands`, `lspServers`; installed via `copilot plugin install` or `enabledPlugins` in `~/.copilot/settings.json` / `.github/copilot/settings.json` | ❌ Not a plugin surface; consumes MCP/agents as per-workspace/repo config | +| **Agent Skills** | ✅ `skills//SKILL.md` ([agentskills.io](https://agentskills.io) open spec) | ✅ `skills` field (default `skills/`), `SKILL.md` | ✅ Agent Skills | +| **MCP servers** | ✅ **bundled** — `.mcp.json` at plugin root (or inline `mcpServers` in `plugin.json`), auto-discovered | ✅ **bundled** — `mcpServers` field → `.mcp.json` / `.github/mcp.json` (or inline), key `mcpServers` | ✅ consumed, **not bundled** — `.vscode/mcp.json` / user `mcp.json`, key `servers` | +| **Custom agents** | ✅ **bundled** — `agents/*.md` (md + YAML frontmatter), auto-discovered, namespaced `plugin:agent` | ✅ **bundled** — `agents` field (default `agents/`), `*.agent.md`, ID from filename | ✅ consumed, **not bundled** — `.github/agents/*.agent.md` (renamed from `.chatmode.md`) | + +### What is tool-neutral vs tool-specific + +- **Tool-neutral (the marketplace's contract — now spanning *two* native plugin systems):** + - **The plugin-bundle format itself.** Claude Code and Copilot CLI share a near-identical model: a + `plugin.json` with the same optional component-path fields (`skills`, `agents`, `mcpServers`, + `hooks`, `commands`) and the same on-disk defaults (`skills/`, `agents/`, `.mcp.json`), catalogued by + a `marketplace.json` whose plugin entries carry the same `name`/`source`/`description`/`version`. + Copilot CLI even searches `.claude-plugin/plugin.json`, so one plugin dir is consumed by both tools + **without per-tool restructuring**. + - **Skills** — a real open standard (Agent Skills, released 2025-12-18, adopted by 26+ tools). Already + how every plugin works today; nothing changes. + - **The MCP *server definition*** — the per-server shape converges across all three tools: stdio = + `{ command, args, env }`; remote = `{ type, url, headers }`. One canonical model describes a server + for every tool. + - **The custom-agent *body*** — markdown + YAML frontmatter with the neutral core `name` + + `description` + `tools` + `model`. Converged de-facto across all three. +- **Tool-specific (the residual divergence — now small):** + - **VS Code is not a plugin surface.** It is the one supported tool with no plugin/marketplace bundle; + it consumes MCP via `.vscode/mcp.json` (key **`servers`**, not `mcpServers`) and agents via + `.github/agents/*.agent.md`. A bundled MCP/agent must be delivered to VS Code as documented config. + - **The MCP wrapper key:** `mcpServers` (Claude Code, Copilot) vs `servers` (VS Code); the `type` enum + (`stdio`/`local`); VS Code's `inputs`/`oauth`/`sandbox`; Copilot's per-agent `tools` allow-list. + - **Custom-agent file convention:** `agents/*.md` (Claude Code) vs `agents/*.agent.md` (Copilot, where + the ID derives from the filename) vs `.github/agents/*.agent.md` (VS Code), plus Copilot-only + frontmatter (`target`, `mcp-servers`, `user-invocable`). + +**The load-bearing consequence (corrected):** bundling an MCP server or a custom agent is **natively +supported by both Claude Code *and* Copilot CLI** from a single plugin dir — the marketplace's committed +"not skills-only" promise is realizable as a *true cross-tool bundle*, not a Claude-only bundle plus +docs. Only **VS Code** needs the same resource delivered as documented per-workspace config. So a +non-skill plugin has **one bundled path serving both plugin-native tools** (from the canonical +`.mcp.json` / `agents/`) and **one documented path for VS Code** — and the marketplace must be honest +that VS Code is the sole consume-via-config surface, not imply that Copilot needs hand-configuration. + +## Decision + +### D1 — Resource model: a plugin may provide skills, MCP servers, and/or custom agents + +A plugin is valid if it provides **at least one** recognized resource. The three resource types and +their on-disk conventions use the component-path fields **shared by both the Claude Code and Copilot CLI +plugin schemas** (same field names, same defaults — no tool-specific invention): + +| Resource | On-disk | `plugin.json` | +|---|---|---| +| Skills | `skills//SKILL.md` (+ provenance frontmatter) | `"skills": "skills/"` (default `skills/` in both schemas) | +| MCP servers | `.mcp.json` at plugin root (`{ "mcpServers": { … } }`) | optional `"mcpServers": ".mcp.json"` (Copilot resolves `mcpServers`→`.mcp.json`/`.github/mcp.json`; Claude Code auto-discovers `.mcp.json`) | +| Custom agents | `agents/.md` (md + YAML frontmatter; `.agent.md` for Copilot) | optional `"agents": "agents/"` (default `agents/` in both schemas) | + +`plugin.json` keeps `name` / `description` / `version` / `author`. The `"skills": "skills/"` field +becomes **conditional** — present only when the plugin actually ships skills (both tools treat the +component-path fields as optional and fall back to the default dirs, so an absent field is safe). The +component-path fields are explicit here so the manifest is self-describing for the CI gate (D3). + +### D2 — Canonical MCP model → bundled (Claude Code + Copilot CLI) + documented (VS Code) + +A bundled MCP server is authored **once** as the plugin's `.mcp.json` (`mcpServers` map). That file is +the canonical model, consumed natively by both plugin-native tools and documented for the one that isn't: + +- **Claude Code:** the `.mcp.json` is bundled and auto-loaded with the plugin. Use `${CLAUDE_PLUGIN_ROOT}` + for any plugin-relative paths so it survives updates. +- **Copilot CLI / cloud agent:** the **same** `.mcp.json` is bundled — `plugin.json`'s `mcpServers` + field resolves to it (and the plugin installs via `copilot plugin install` or `enabledPlugins`). No + hand-configuration; the server ships *inside* the plugin exactly as on Claude Code. +- **VS Code:** the only consume-via-config surface — the plugin README documents the equivalent + `.vscode/mcp.json` entry under the **`servers`** key (key-renamed from the canonical `mcpServers`; + same per-server shape). + +The README install snippet is the parity mechanism for VS Code alone — generated from the one canonical +`.mcp.json`, never independently authored, so it cannot drift in substance. Claude Code and Copilot CLI +need no snippet; they bundle the canonical file directly. + +### D3 — CI / manifest changes (the gate stays the contract) + +`scripts/validate-manifests.sh` (and its self-test `validate-manifests.test.sh`) change as follows. +**No check is weakened** — each is generalized to the resource the plugin actually ships, and a new +check is added for MCP well-formedness: + +1. **`validate_plugin_json`** — replace the unconditional `"skills" == "skills/"` + non-empty `skills/` + requirement with: *the plugin must ship ≥1 recognized resource.* Per resource present, keep/add: + - `skills/` present → keep all existing skill checks (the `skills/` field equals `"skills/"`, ≥1 + `SKILL.md`, and the existing per-skill provenance + agentskills.io spec checks). **Unchanged for + every plugin shipping today.** + - `.mcp.json` present → it must be valid JSON with a non-empty `.mcpServers` object, and each server + must have a `command` (stdio) **or** a `url` (remote). (New check.) + - `agents/` present → ≥1 `agents/*.md`, each with YAML frontmatter carrying `name` + `description`. + (New check.) +2. **`validate_readme_parity`** — generalize the README **Skills** column to a **Resources** column that + lists the plugin's resources (skill dir names, MCP server names, and/or agent names), and match it + against on-disk resources. Skill-only rows are unaffected in substance (the column simply lists the + same skill names under a broader header). +3. **Marketplace manifests** — **no entry-schema change**: plugin entries stay `name` / `description` / + `version` / `source` and the byte-for-byte parity diff is untouched. Only the top-level + `metadata.description` prose broadens from *"Curated agent skills bundled by category"* to cover + *agent resources (skills, MCP servers, custom agents)*. Both manifests change together, as always. +4. **README scope + table header** broaden to match (the "Scope" section already says "any agent + resource"; the table header changes Skills → Resources). + +All four land **with the first concrete bundle** (the follow-up child), each paired with a new +`validate-manifests.test.sh` fixture (a PASS case for an MCP-only plugin and a FAIL case for an empty / +malformed `.mcp.json`), so the gate's behavior stays pinned by its self-test and the change is provably +non-weakening before any consumer sees it. + +### D4 — First concrete bundle (prove-out → follow-up child) + +Ship the **Flux MCP (`flux-operator-mcp`)** bundled into the **existing `gitops-kubernetes` plugin** as +the first proof. Rationale: the bundled `gitops-cluster-debug` skill already declares +`compatibility: Requires flux-operator-mcp` and, on failure, tells the user *"the `flux-operator-mcp` +server is not running. Provide the install command."* Bundling the server makes that skill +**self-sufficient on Claude Code** and is the highest-utility, lowest-divergence first bundle — a skill +and the MCP it already depends on, shipped together. Copilot CLI / VS Code get the documented config per +D2. This is exactly the "MCP paired with an existing plugin where the skills already assume it" candidate +named in #39. + +## Considered alternatives + +- **Add a new `mcpServers` / `agents` field to the marketplace.json plugin entry.** Rejected — although + the Copilot CLI marketplace-entry schema *does* permit `mcpServers`/`agents`/`skills` on the entry, the + entry is kept intentionally minimal (`name`/`description`/`version`/`source`) and both tools resolve a + plugin's resources from the plugin dir (`plugin.json` + on-disk files), not the marketplace entry. + Adding resource fields to the entry would duplicate on-disk truth and create a second drift surface for + no consumer benefit. +- **A new standalone `mcp` plugin (MCP-only, unpaired).** Rejected as the *first* proof — less useful and + higher-divergence than pairing with a skill that already requires the server. Viable later for servers + with no skill counterpart. +- **Bundle a custom agent first instead of an MCP server.** Deferred — custom-agent packaging diverges + more across tools (extension, dir, Copilot-only frontmatter) and the marketplace has no agent whose + bundling is already implied by an existing plugin, so an MCP bundle is the cleaner first prove-out. + Custom agents follow once the MCP path is proven (the CI model in D3 already covers `agents/`). +- **Generate the Copilot/VS Code MCP config from a build step.** Deferred as premature — with one bundled + server, a documented README snippet from the canonical `.mcp.json` is sufficient and reviewable; a + generator is worth revisiting once several servers ship. + +## Consequences + +**Positive** + +- Realizes the committed "not skills-only" direction with a concrete, grounded plan instead of prose. +- **Doubles the marketplace's native reach for free:** because Copilot CLI's plugin schema overlaps + Claude Code's (and its manifest search order includes `.claude-plugin/plugin.json`), the existing + `.claude-plugin/marketplace.json` is *already* a valid Copilot CLI marketplace — bundled MCP/agents + ship natively to both plugin-native tools with no per-tool packaging. (A possible follow-up is to + document/test Copilot CLI installation explicitly; the format work is already done.) +- Keeps the manifests' parity contract and every existing skill check **intact** — changes are additive + and generalize rather than weaken (pinned by the self-test). +- One canonical MCP definition serves all three tools; cross-tool parity is explicit, not assumed. +- The first bundle removes a real papercut (a skill that today needs a manual MCP install). + +**Negative / risks** + +- **Asymmetric delivery (VS Code only):** MCP/agent bundles are auto-installed on both Claude Code and + Copilot CLI; **VS Code** is the sole surface that requires a documented manual config step. This must + be stated plainly in each such plugin's README so cross-tool expectations stay honest — but the + asymmetry is now one tool, not two. +- **CI surface grows:** the validator gains MCP/agent branches and new self-test fixtures — more to + maintain, but bounded and self-tested. +- **Versioning:** a bundled MCP server pins a server version/source; keeping it fresh is new maintenance + (a `dependabot`/`renovate` or pinning convention to decide when the first server lands). + +## Follow-up + +- **AC#3 child ([#42](https://github.com/devantler-tech/agent-plugins/issues/42), next):** implement D3 + (validator + self-test generalization) **and** D4 (the `flux-operator-mcp` bundle in + `gitops-kubernetes`: `.mcp.json`, README bundled + Copilot/VS Code config snippets, manifest/README + prose broadening) in one PR — the design here is the contract it implements. +- Custom-agent bundling (the `agents/` path in D1/D3) proves out after the MCP path, as a later child of + epic #38 Theme 1.