Agent Plugins: A Portable Package Format for Skills and MCP Servers

Amazon, Cursor, Microsoft, OpenAI and Vercel are driving a shared package format for Agent Skills and MCP servers. What Agent Plugins 1.0 standardizes and leaves open.

8 min read
  • #AI Engineering
  • #Mcp
  • #Software Architecture

Agent Skills and MCP already have specifications of their own. A skill is a directory with a SKILL.md and frontmatter, while an MCP server speaks a documented protocol. Both building blocks are intended to work across clients rather than being tied to one product.

What was missing was the layer above them: the package. If a skill and its accompanying MCP server had to be shipped together, authors still had to target the plugin format of each client. The actual components could be identical while directory layout, manifest and configuration were not.

Agent Plugins closes that gap with a deliberately small common denominator. Version 1.0.0 is the current specification. The official website currently labels it Working Draft, while the canonical specification source in the GitHub repository already says Published, so the two official sources are not yet in sync on that point. In substance, v1.0.0 defines a directory, a manifest, two fixed component locations and precise failure boundaries. What it does not standardize is just as important.

The smallest valid package

A plugin is a directory with a plugin.json at its root. Everything else is optional:

hello-plugin/
├── plugin.json
└── skills/
    └── greet/
        └── SKILL.md

The manifest can be reduced to two fields:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "hello-plugin"
}

What is notable is what is missing: the manifest does not enumerate components. There is no skills field pointing at directories and no MCP server registration. Clients discover skills under skills/ and MCP servers from mcp.json at the plugin root. Those locations are fixed.

A plugin containing both portable component types plus a client-specific extension can look like this:

my-plugin/
├── plugin.json
├── mcp.json
├── skills/
│   └── summarize/
│       ├── SKILL.md
│       ├── scripts/
│       └── references/
└── com.example.client/

Who is behind it

Agent Plugins is developed as an open, vendor-neutral standard. Its Technical Steering Committee currently consists of Core Maintainers affiliated with Amazon, Cursor, Microsoft, OpenAI and Vercel. The governance roles belong to the individuals, not the companies: the Technical Charter explicitly says there are no reserved corporate seats and no single vendor may control a majority of Core Maintainer seats. Specification text and documentation use CC BY 4.0, while code and schemas use Apache 2.0.

That is the more interesting part to me. Several direct competitors are not agreeing on a common runtime, but on the box around their extensions. The Charter explicitly names portability, competition and long-term stability as project goals. A shared package format lowers switching costs between clients without prescribing how installation, permissions or UI have to work.

There is now also an official Compatible Clients page. It currently lists VS Code, Cursor, GitHub Copilot, ChatGPT & Codex and Kiro. All five advertise Agent Skills and MCP support, with small differences in supported MCP transports. That is a much stronger signal than a specification that exists only on paper.

A directory, not an archive

The Design Decisions deliberately make a filesystem directory the package unit instead of .zip, .tar.gz or a registry bundle. The reasoning is pragmatic: a directory can be inspected with ls, cat and git, edited in place during development and versioned without special tooling.

The core standard therefore does not define an archive format, registry, package checksum or signature. That does not prevent a distribution system from adding those features. They simply live outside the portable package contract and remain the responsibility of the client or distribution layer.

The manifest is closed

plugin.json requires $schema and name, followed by a fixed set of optional metadata fields: version, description, author, homepage, repository, license, keywords and extensions. Any other top-level field violates the schema.

When migrating from a client-specific format, the obvious fields are often exactly the ones that do not belong there: hooks, agents, commands, mcpServers or lspServers. Client-specific data belongs under extensions, namespaced with a reverse-domain identifier.

The closed schema enables strict validation and prevents individual clients from quietly extending the shared namespace. There is one important exception: unknown top-level fields do not automatically make the plugin unusable. Clients must report and ignore them if the rest of the manifest is valid. Almost every other plugin.json schema violation is fatal to the whole plugin.

Only two component types

Version 1 defines exactly two portable component types: Agent Skills and MCP servers. Commands, hooks, agents, rules and LSP servers are explicitly outside the format.

The rationale is sound. Skills and MCP already have independent specifications and meaningful adoption across multiple clients. A package format can only make portable what already has a portable contract underneath it. Adding hooks would have required standardizing hook semantics at the same time.

PLUGIN_ROOT and PLUGIN_DATA

For stdio MCP servers, two variables matter:

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "deploy-platform": {
      "type": "stdio",
      "command": "node",
      "args": ["${PLUGIN_ROOT}/mcp/deploy-platform/server.mjs"],
      "env": {
        "CATALOG": "${PLUGIN_ROOT}/mcp/deploy-platform/data/environments.json",
        "STATE": "${PLUGIN_DATA}/deployments.json"
      }
    }
  }
}

The client sets PLUGIN_ROOT and PLUGIN_DATA for the subprocess and expands both placeholders once, non-recursively, in args, env values and cwd. Expansion does not happen in command, URLs, HTTP headers or key names.

PLUGIN_ROOT points at the shipped package. PLUGIN_DATA points at a client-managed writable directory whose contents survive plugin updates. Dependencies, caches and other persistent state therefore belong under PLUGIN_DATA; bundled scripts and configuration belong under PLUGIN_ROOT.

command is also restricted to a single executable token. "node" is valid, "node --inspect" is not. Arguments belong in args. An executable bundled inside the plugin must be referenced as a plugin-relative path beginning with ./.

One detail is easy to implement incorrectly: not every string that looks like a path is subject to containment checks. command and cwd are defined path fields and must remain inside the corresponding root. args and env, however, are opaque strings. A client must not treat them as package paths merely because they contain something like ../.

Failures stay as local as possible

The specification keeps failure boundaries narrow. An invalid mcp.json disables MCP for that plugin without affecting its skills. An invalid server entry is skipped without blocking other servers. The same applies when a server fails to start, connect, authenticate or complete the MCP handshake. An invalid skill is skipped while independent components continue to load.

The central manifest is stricter. If plugin.json is missing, targets an unsupported version or violates its schema outside the explicit exceptions, the client must reject the entire plugin. The two notable exceptions are unknown top-level fields and an extensions field with the wrong type; both are reported and ignored.

That split makes sense. A broken optional server should not take two working skills down with it. An invalid manifest, on the other hand, breaks the common contract the rest of the package depends on.

Client extensions: the escape hatch

Hooks are not a portable component type. A client can still add them through its own reverse-domain namespace:

my-plugin/
├── plugin.json
├── skills/
├── mcp.json
└── com.vendor.client/
    └── hooks/

The same namespace can appear under extensions in plugin.json. Clients that do not implement it ignore it, while the portable core remains usable.

This makes migrations additive rather than destructive. The limitation is equally clear: inventing a namespace does not create functionality. It only gains meaning once a client documents and implements that namespace.

What becomes obvious when building one

To make the specification concrete, I built a demo plugin with two skills, a stdio MCP server for a fictional deployment platform and a minimal client: github.com/dprinz/agent-plugins.

The fixed discovery rules are the first thing that stands out. Skills are discovered exactly one level below skills/. skills/greet/SKILL.md can be found; skills/greet/nested/SKILL.md cannot, because clients must not recurse. A discovered skill whose name does not match its directory name is invalid under the Agent Skills specification and has to be skipped.

The second trap is path semantics. Package files and fields explicitly defined as paths must stay inside the permitted root after filesystem resolution, including symlinks. At the same time, args and env are deliberately not path fields. A conformance checker that treats every string containing ${PLUGIN_ROOT} or ../ as a filesystem path is stricter than the specification.

These details show the value of keeping the format small. The rules fit into a compact specification, but they are precise enough that two independent clients do not have to guess completely different meanings for the same package.

What the specification leaves open

The project maintains FUTURE_CONSIDERATIONS.md, an explicitly non-normative list of possible topics for later versions. It includes permissions, sandboxing, signatures and provenance, secret handling, enterprise policies, audit events, plugin dependencies and testing conventions. None of those items is promised for a future release.

I consider the missing trust model manageable. A package format does not also need to define registries, signature verification and enterprise policy. A client installing plugins from the internet needs those layers anyway, and Agent Plugins intentionally leaves them to the client or distribution system.

Secrets are a more practical gap. Many MCP integrations need API keys or other credentials. Version 1 prohibits embedding them in configured env values and HTTP headers, but does not yet offer a portable alternative such as secret references or OAuth configuration. The package can be portable while credential provisioning still is not.

My assessment

Agent Plugins solves a narrow problem well. Restricting the portable format to Skills and MCP, while pushing client-specific behavior behind reverse-domain namespaces, keeps the standard focused on things that already have a shared contract. The failure boundaries and the split between PLUGIN_ROOT and PLUGIN_DATA are not minor details; they are what make portability usable at runtime.

The official client list changes the situation materially. The format is no longer just a specification with prominent backers. VS Code, Cursor, GitHub Copilot, ChatGPT & Codex and Kiro already list concrete support. The open question is whether Agent Plugins becomes the normal package format in those products or remains an additional compatibility loader beside native formats. In the latter case fragmentation gets smaller, but does not disappear.

For a plugin targeting multiple clients, I would still keep the portable core as the source of truth and add client extensions only where they are actually required. Distribution, permissions and secrets may remain client-specific for now, but at least the divergence then lives at the edge rather than in the middle of the skills and MCP configuration.