Agent Plugins¶
ai-rulez packages a project's skills and MCP servers as an Agent Plugins plugin directory, so
any client that conforms to the standard can load them. The same code builds the package, validates it and reads it back
(internal/agentplugins); it checks plugin.json and mcp.json against the official JSON Schemas, which are vendored and
pinned for each supported version.
| Spec | Status | $schema of plugin.json |
|---|---|---|
| 1.0.0 | published, the default | https://agent-plugins.org/schemas/1.0.0/plugin.schema.json |
| 1.1.0 | working draft | https://agent-plugins.org/schemas/1.1.0/plugin.schema.json |
mcp.json carries the matching mcp.schema.json identifier. The two files always name the same version.
Enable it¶
[plugin]
name = "acme.tools"
version = "1.2.0"
description = "Acme review tooling."
runtimes = ["agent-plugins"]
spec = "1.1.0" # optional; 1.0.0 when unset
ai-rulez generate --plugin # writes plugin.json, skills/ and mcp.json at the project root
ai-rulez verify --plugin # fails when a generated file was edited or no longer matches its sources
ai-rulez validate --strict # AR9O0-AR9O5 for anything a conformant client would skip
The copilot runtime and the codex runtime with manifest = "root" write the same package, so they follow spec too.
Field mapping¶
| ai-rulez | Package | Notes |
|---|---|---|
[plugin] name |
plugin.json name |
1-64 characters from a-z0-9.-, starting and ending alphanumeric, no -- or .. |
[plugin] version, description, homepage, repository, license, keywords |
the same keys | |
[plugin.author] name, email, url |
author |
an empty author is omitted |
[plugin] spec |
$schema of plugin.json and mcp.json |
|
[plugin.interface] (codex root layout) |
extensions["com.openai"].interface |
keys are written sorted |
skills (.ai-rulez/skills/<name>/) |
skills/<name>/SKILL.md, with scripts/, references/ and other files |
SKILL.md is copied byte for byte; only the immediate children of skills/ are read by clients |
[[mcp_servers]] or [[plugin.mcp]], stdio |
mcp.json mcpServers.<name> with type = "stdio", command, args, env |
see MCP servers |
[[mcp_servers]] or [[plugin.mcp]], transport = "http" |
type = "streamable-http", url |
url must be HTTPS, or HTTP to localhost |
transport = "sse" |
type = "sse", url |
only when configured explicitly |
enabled = false |
not packaged | the format has no flag; reported as AR9O4 |
headers |
headers (remote servers) |
plugin bundles do not carry project server headers today |
| agents, commands, hooks | not written | they belong in a client's extension namespace, see Extensions |
| rules, context | not written | the io.github.goldziher.ai-rulez namespace is read on import |
MCP servers¶
The specification expands only ${PLUGIN_ROOT} and ${PLUGIN_DATA}, and only in args, env values and cwd.
commandis one token: a bare name (npx) or a./path inside the plugin. A${PLUGIN_ROOT}/bin/servercommand is rewritten to./bin/server(a lossless rewrite, reported as info).- An
enventry that only forwards a variable of the same name (API_KEY = "${API_KEY}") is left out: no client expands it, so the client has to supplyAPI_KEYitself. This is reported asAR9O3(warning). - Any other
${VAR}(inargs,env,url,headers) is rejected: the server is not packaged and the finding is an error (AR9O3).generate --pluginlogs it,validate --strictfails on it andpublishstops. PLUGIN_ROOTandPLUGIN_DATAmay not be set inenv: the client supplies them.
Extensions¶
Content that only one client understands lives under a reverse-domain namespace, either as manifest data
(plugin.json extensions.<namespace>) or as files (<namespace>/). Clients ignore namespaces they do not implement.
| Namespace | Content |
|---|---|
com.anthropic.claude-code |
agents/<name>.md, commands/<name>.md, hooks/hooks.json |
com.openai |
the Codex interface block (manifest data) |
io.github.goldziher.ai-rulez |
rules/<name>.md, context/<name>.md |
com.github.copilot |
agents/<name>.agent.md (the copilot runtime) |
generate --plugin writes the Codex block and the Copilot agents; the other namespaces are read on import and written by
the library when a caller supplies them.
Determinism and verification¶
The package is sorted, has no timestamps and is rendered with two-space JSON and a trailing newline. Every generated file
is recorded with its Content-Hash in .ai-rulez-generated.json, so generate --check and verify --plugin cover
plugin.json, mcp.json and the skills, and publish pins the whole bundle in its manifest and SHA256SUMS.
Validation and the AR9O codes¶
validate --strict builds the package in memory and validates it as a client loads it. publish runs the same check on
the bundle it packages, in --dry-run as well, and fails with the first error's code and exit status 2.
| Code | Meaning |
|---|---|
AR9O0 |
plugin.json is missing or invalid, the spec is unsupported, or an extension namespace is invalid |
AR9O1 |
a skill breaks the Agent Skills rules (name, description, frontmatter) or is not at skills/<name>/SKILL.md |
AR9O2 |
mcp.json or a server entry fails the schema or the rules for command, url and headers |
AR9O3 |
an unsupported ${VAR} placeholder |
AR9O4 |
a field or server that cannot be packaged was left out |
AR9O5 |
a path or link that resolves outside the plugin root, or a file that cannot be read |
validate --explain AR9O3 prints the rule. Only single-plugin projects are checked by validate --strict; the members of
a marketplace are checked when they are published.
Publish¶
The specification defines no archive, registry or signature. ai-rulez reuses its publish pipeline: the
bundle with the agent-plugins runtime travels in the release archive, the npm package or the OCI artifact, and is signed
with the rest of the release (--sign-key, --sign-keyless). These distribution steps sit outside the specification.
--emit agent-plugins (or [[publish.emitters]] name = "agent-plugins") writes each plugin as a clean directory,
emit/agent-plugins/<name>/, holding only the Agent Plugins files (no .claude-plugin/, no other runtime). The directory
is read back and written again by the library, so anything a client would skip is reported rather than shipped. The option
spec converts the package to another version: options = { spec = "1.1.0" }.
Import¶
convert reads an Agent Plugins directory into .ai-rulez/: plugin.json becomes the [plugin] block (with
runtimes = ["agent-plugins"] and spec when it is not 1.0.0), skills keep their scripts/ and references/, mcp.json
becomes [[mcp_servers]], and the Claude Code, Copilot and ai-rulez namespaces become agents, commands and rules. The
report lists what has no equivalent: bundled root files, unknown namespaces, hooks/hooks.json and a server cwd. A
manifest without version or description cannot form a [plugin] block, so none is written and the report says so. The
provenance header ai-rulez writes into generated Markdown is removed on import.
Export, import and export again give identical bytes; a test pins this for 1.0.0 and 1.1.0.
Differences from earlier output¶
The agent-plugins runtime used to write the manifest without checking it. Building with the library changes the output
only where the specification requires:
- A skill without a
description, or whosenamediffers from its directory, is not packaged (clients skip it). - An MCP server with a
${VAR}placeholder other than${PLUGIN_ROOT}and${PLUGIN_DATA}is not packaged. - A disabled server is not packaged (it used to be bundled enabled).
- A
enventryKEY = "${KEY}"is left out. - The Codex
interfaceblock is written with sorted keys.