Start Debugging

Agent Plugins 1.0 vs Vendor-Specific Plugin Formats: What the Shared Standard Actually Covers

Agent Plugins 1.0.0 standardizes exactly two component types: skills and MCP servers. Slash commands, hooks, subagents, rules, LSP servers, permission config and secrets all stay vendor-specific. Here is the field-by-field matrix across Claude Code, Cursor, Copilot and Codex, and which layout to author in.

Author your plugin in the Agent Plugins 1.0.0 layout: plugin.json at the root, skills/ beside it, mcp.json beside that. Six clients read it as published. But be clear-eyed about the size of the win, because the standard covers exactly two component types, skills and MCP servers, and nothing else. Slash commands, hooks, subagents, rules, LSP servers, permission models, secret configuration and the entire install path are explicitly out of scope. The escape hatch is a reverse-domain directory (com.github.copilot/, com.example.client/) that each client reads only for itself. And one major client, Claude Code, does not read the standard layout at all: on 2.1.197 a root-only plugin.json is a hard error, not a warning.

So the real question is not “standard or vendor format.” It is “how much of my plugin fits in the portable half.” If your bundle is a skill plus an MCP server, the portable half is 100 percent of it. If your bundle is four subagents, a PreToolUse hook and a permission policy, the portable half is the manifest and nothing else.

The matrix

Versions in this table: Agent Plugins 1.0.0 (published August 6, 2026), Claude Code 2.1.197, Cursor 3.11, VS Code 1.133 with Copilot plugin support GA since August 12, 2026, and the Codex plugin docs as of August 2026.

ComponentAgent Plugins 1.0.0Claude Code 2.1.xCursor 3.xCopilot (VS Code / CLI)Codex / ChatGPT
Manifestplugin.json (root).claude-plugin/plugin.jsonplugin.json or .cursor-plugin/plugin.jsonplugin.json (root).codex-plugin/plugin.json
Skillsskills/<name>/SKILL.mdsamesamesamesame
MCP serversmcp.json.mcp.jsonmcp.jsonmcp.json.mcp.json
Slash commandsnot standardizedcommands/commands/com.github.copilot/commands/not documented
Subagentsnot standardizedagents/agents/com.github.copilot/agents/*.agent.mdnot documented
Hooksnot standardizedhooks/hooks.jsonhooks/hooks.jsoncom.github.copilot/hooks/hooks.jsonhooks/hooks.json
Rulesnot standardizedCLAUDE.md (not a plugin component)rules/ (.mdc)com.github.copilot/rules/not documented
LSP serversout of scope, named.lsp.jsonnonenonenone
Path placeholders${PLUGIN_ROOT}, ${PLUGIN_DATA}${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}none documentedstandard placeholdersPLUGIN_ROOT, PLUGIN_DATA as hook env vars
User-supplied secretsout of scopeuserConfig (typed, sensitive: true)variables (JSON Schema, stored in dashboard)nonenone
Install / distributionout of scopemarketplaces, claude plugin installCustomize page, .cursor-plugin/marketplace.jsonAwesome Copilot, Chat: Install Plugin From Sourcecodex plugin marketplace add

Two rows carry the whole argument. The skills row is identical everywhere, including in Claude Code. The manifest row disagrees in four different ways. That asymmetry is not an accident: skills were already converging on SKILL.md before the standard existed, so the spec ratified reality. Everything else was still moving, so the spec left it alone.

What 1.0.0 actually pins down

The portable core is four things. First, a manifest with two required fields:

// plugin.json, Agent Plugins 1.0.0, at the plugin root
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "changelog-tools"
}

That is a complete, valid plugin manifest. version, description, author, homepage, repository, license and keywords are all optional, and the spec goes out of its way to say clients must not reject a manifest just because version is not valid SemVer.

Second, skill discovery, defined narrowly enough that two clients cannot disagree: each immediate child directory of skills/ that contains a regular file named exactly SKILL.md is one skill. Recursive searching is prohibited, so a skills/team/backend/deploy/SKILL.md is not a skill anywhere.

Third, mcp.json, with a closed union of three server types:

// mcp.json, Agent Plugins 1.0.0
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "notes": {
      "type": "stdio",
      "command": "node",
      "args": ["${PLUGIN_ROOT}/bin/server.js"]
    },
    "remote": {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}

command must be a single executable token, not a shell string, so "command": "node bin/server.js" is invalid even though several vendor formats historically tolerated it. Legacy sse is in the union for compatibility and nothing else.

Fourth, exactly two placeholders, ${PLUGIN_ROOT} and ${PLUGIN_DATA}, expanded only in args, env and cwd, as a single non-recursive textual replacement. Not in command. Not in url. Not in headers.

What it deliberately does not cover

The spec is blunt about the boundary. Component types beyond skills and MCP servers, in the spec’s own words, “remain too client-specific for a stable portable contract.” The named exclusions are commands, hooks, agents, rules and LSP servers. Beyond component types, 1.0.0 also declines to define an install mechanism, a distribution protocol, a central registry, a permission model, sandboxing requirements, trust or provenance verification, OAuth and credential configuration, an archive format (a plugin is a directory, full stop), and fallback behaviour when a transport fails to connect.

That list is longer than the list of things it does cover, and it is worth sitting with. A plugin standard with no permission model and no provenance verification means “portable” describes the file layout and not the trust decision. Whether your team is allowed to run the MCP server inside a portable plugin is still a per-client policy question, and on the Claude Code side that is allowedMcpServers and deniedMcpServers, which no standard touches.

The namespaced escape hatch

Everything non-portable goes in a reverse-domain directory at the plugin root, with matching data under the manifest’s extensions key. Copilot’s is com.github.copilot, and the VS Code docs spell out the full layout:

my-plugin/
  plugin.json                 # portable: Agent Plugins 1.0.0
  skills/
    test-runner/
      SKILL.md
      run-tests.sh
  mcp.json                    # portable
  com.github.copilot/         # Copilot-only, ignored by every other client
    agents/
      test-reviewer.agent.md
    commands/
    rules/
    hooks/
      hooks.json
// plugin.json with a client extension block
{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "changelog-tools",
  "version": "1.0.0",
  "extensions": {
    "com.github.copilot": { "canvases": false }
  }
}

Clients must ignore namespaces they do not implement, without validating the contents. This is the part that makes the standard usable rather than merely tidy: you are not forced to drop your four Copilot subagents to be portable, you just move them one directory down. The cost is that the subagents are still four files you maintain per client, and the standard has done nothing to reduce that. A Cursor subagent in agents/ and a Copilot subagent in com.github.copilot/agents/ are still two authoring jobs, which is the same sorting problem covered in migrating Cursor rules to skills, subagents, and plugins.

Claude Code is the holdout, and it fails loudly

Anthropic is not on the technical steering committee (Amazon, Cursor, Microsoft, OpenAI and Vercel founded it, with Google joining as a core maintainer), and Claude Code was not in the launch client list. I checked what that means in practice against 2.1.197 with a plugin built in pure spec layout: plugin.json, skills/changelog-notes/SKILL.md, mcp.json, and a com.github.copilot/ directory.

# Claude Code 2.1.197, plugin dir in pure Agent Plugins 1.0.0 layout
$ claude plugin validate ./portable-demo
Validating plugin manifest: /tmp/portable-demo

 Found 1 error:

 directory: No manifest found in directory. Expected
    .claude-plugin/marketplace.json or .claude-plugin/plugin.json

 Validation failed

Not a warning about an unrecognized layout. The manifest at the root is invisible, so the directory reads as “not a plugin” and the skills inside it never get considered. The fix is two extra files, which packaging skills and an MCP server as one agent plugin walks through in full: a .claude-plugin/plugin.json and a .mcp.json alongside the portable pair.

The interesting half is what happens when you copy the standard manifest verbatim into .claude-plugin/plugin.json:

# Claude Code 2.1.197, standard manifest copied into .claude-plugin/
$ claude plugin validate ./portable-demo --strict
 Found 2 warnings:

 extensions: Unknown field 'extensions'. Claude Code ignores it at load time.
 author: No author information provided...

 Validation failed (--strict treats warnings as errors)

$schema passes silently. extensions warns but is tolerated at runtime. So a single manifest body works in both places, and the only genuinely duplicated content is the file itself. Note that --strict turns that extensions warning into a CI failure, which is worth knowing before you wire claude plugin validate --strict into a pipeline for a plugin you deliberately made portable.

The gotcha that picks for you

The MCP half is where a dual-layout plugin silently half-works, and no tool catches it. I pointed Claude Code’s manifest at the spec-shaped file:

// .claude-plugin/plugin.json
{ "name": "portable-demo", "mcpServers": "./mcp.json" }
$ claude plugin validate ./portable-demo --strict
 Found 1 warning:
 author: No author information provided...

No complaint about mcp.json. claude plugin validate is a metadata linter: it reads the manifest, checks field names and types, and does not open the MCP config or look at the placeholders inside it. That config declares "args": ["${PLUGIN_ROOT}/bin/server.js"], and Claude Code expands ${CLAUDE_PLUGIN_ROOT}, not ${PLUGIN_ROOT}. The unexpanded string is passed to node as a literal path, the process exits, and you get a connection failure at session start instead of a validation error at build time. The same class of failure is behind MCP error -32000: Client Closed.

Which means: keep two MCP config files, not one. mcp.json with ${PLUGIN_ROOT} for the standard-reading clients, .mcp.json with ${CLAUDE_PLUGIN_ROOT} for Claude Code, and a check in CI that the two agree on server names and commands. Four files of duplication total (plugin.json, .claude-plugin/plugin.json, mcp.json, .mcp.json), no duplicated logic, and every skill and every script shared.

When to author in the standard layout

When to stay on a vendor format

The recommendation, restated

Make the Agent Plugins 1.0.0 layout your source of truth, put every client-specific component in its reverse-domain directory, and add the .claude-plugin/plugin.json plus .mcp.json shim as a build step rather than a hand-maintained pair. Treat the standard as what it says it is: a package format and an interoperability floor, not a capability contract. It will not make your hooks run in Cursor or your subagents show up in ChatGPT, and if a plugin’s value lives in those components, the standard has bought you a manifest and a naming convention. If a plugin’s value lives in a skill and a tool, it has bought you six clients for the price of one.

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Back