feat: split MCP servers into their own plugins, add per-target restrictions
Generate plugin manifests / validate (pull_request) Successful in 18s
Generate plugin manifests / generate (pull_request) Skipped

Split the GitHub/Gitea MCP servers out of core-essentials into their own
github-mcp/gitea-mcp plugins, so a user can install either, both, or
neither independently instead of always getting both bundled together.

Add an optional "targets" field, honored at both levels:
  - plugin.json: restricts the whole plugin (including any hooks/mcp/
    rules/commands/agents it bundles) to specific targets.
  - a skill's own SKILL.md frontmatter: restricts just that one skill,
    independent of its plugin's sibling skills (narrowed to, never wider
    than, the plugin's own targets).

Omitting it (the default everywhere) means every target in manifest.yaml,
so this is fully backward compatible. A plugin left with no content at
all for a given target is now skipped entirely for that target rather
than emitting an empty output directory / marketplace entry.

Applies this to the actual motivating case: delegate-agy only makes sense
run from Claude Code (it shells out to `agy`), so it's now marked
targets: [claude-code] and no longer ships into dist/agy at all — it was
previously being installed into agy for no reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YVWWAnvR5TDQzmfY3RXG9
This commit is contained in:
2026-08-24 20:28:36 -04:00
co-authored by Claude Sonnet 5
parent 68fbadbfec
commit c6f40bcf6b
21 changed files with 223 additions and 103 deletions
+45 -1
View File
@@ -19,11 +19,14 @@ from one source tree and published as a native marketplace for both tools.
- [technical-devlog-scribe](#technical-devlog-scribe)
- [ship-it](#ship-it)
- [core-essentials](#core-essentials)
- [github-mcp](#github-mcp)
- [gitea-mcp](#gitea-mcp)
- [Installation](#installation)
- [Claude Code](#claude-code)
- [Antigravity CLI (agy)](#antigravity-cli-agy)
- [Repository Structure](#repository-structure)
- [Anatomy of a Plugin](#anatomy-of-a-plugin)
- [Restricting a Plugin (or a Skill) to Specific Targets](#restricting-a-plugin-or-a-skill-to-specific-targets)
- [How Generation Works](#how-generation-works)
- [Private Overlay Builds](#private-overlay-builds)
- [License](#license)
@@ -130,7 +133,28 @@ Two sequential phases — Phase 2 is blocked until Phase 1 succeeds:
Currently bundles `delegate-agy`, which hands a subtask off to the
Antigravity CLI (`agy`) in headless mode — useful for a second opinion,
external grounded research, or a large multi-file audit (>500 lines) that
would otherwise bloat the current context.
would otherwise bloat the current context. Only makes sense run *from*
Claude Code, so its `SKILL.md` declares `targets: [claude-code]` — this
plugin has no agy output at all.
---
### `github-mcp`
**Purpose:** Connect Claude Code/agy to GitHub via the official
`@modelcontextprotocol/server-github`, authenticated with a
`GITHUB_PERSONAL_ACCESS_TOKEN` you set locally — never stored in the repo.
---
### `gitea-mcp`
**Purpose:** Connect Claude Code/agy to a Gitea instance via the official
`gitea-mcp` server, authenticated with `GITEA_HOST`/`GITEA_ACCESS_TOKEN`
you set locally.
Split from `github-mcp` into its own plugin so you can install either,
both, or neither independently.
---
@@ -218,6 +242,26 @@ verbatim where the two tools' schemas diverge:
target; `commands/`/`agents/` have no agy equivalent and are skipped for
that target.
### Restricting a Plugin (or a Skill) to Specific Targets
Some content only makes sense for one tool — `delegate-agy` (Claude Code
shelling out to `agy`) has no reason to exist *inside* agy, for instance.
Declare a `targets` list wherever it's needed:
- In `plugin.json`, `"targets": ["claude-code"]` restricts the **whole
plugin** — including any hooks/mcp/rules/commands/agents it bundles —
to just the listed targets.
- In a skill's own `SKILL.md` frontmatter, `targets: [claude-code]`
restricts **just that skill**, independent of its sibling skills in the
same plugin. A skill's targets are narrowed to, never wider than, its
plugin's own targets.
Omitting `targets` (the default everywhere) means "every target in
`manifest.yaml`" — today's behavior for every existing plugin. A plugin
left with no content at all for a given target (every skill excluded, and
nothing of its own) is skipped entirely for that target — no empty output
directory, no marketplace entry.
### How Generation Works
`scripts/generate_plugins.py` reads `manifest.yaml` (marketplace metadata,