Skip to main content

Crate agent_plugins

Crate agent_plugins 

Source
Expand description

An implementation of the Agent Plugins specification v1.0.0 — the open, vendor-neutral format for packaging Agent Skills and MCP servers into distributable plugins.

§Design

The crate is built around three ideas, each expressed in its types:

Parse, don’t validate. Constraint-carrying values get their own types: a PluginName proves §5.5, a RelativePath proves ./-rooted containment, an McpUrl proves the §7.2.1 URL rules, a Cwd can only be one of the three permitted working-directory forms. If you hold the value, the check already happened.

Failure boundaries as return types. The spec is precise about how far a failure spreads: a bad manifest rejects the plugin (Rejection), a bad mcp.json disables one component (McpOutcome::Disabled), a bad skill or server entry skips only itself (a Diagnostic). Nothing fails silently and nothing fails wider than the spec allows.

Sans-IO. The Loading state machine speaks the specification and never touches a filesystem. It asks Questions; you answer from wherever the plugin lives. Batteries are included for the common cases — load_dir for real directories, MemSource for tests and archives, the Source trait for anything else synchronous — and the machine itself for async or exotic IO.

§Loading from a directory

use agent_plugins::McpOutcome;

let plugin = agent_plugins::load_dir("path/to/my-plugin")?;

println!("{} v{}", plugin.manifest.name,
         plugin.manifest.version.as_deref().unwrap_or("unversioned"));
for skill in &plugin.skills {
    println!("skill {}: {}", skill.meta.name, skill.meta.description);
}
if let McpOutcome::Configured(mcp) = &plugin.mcp {
    println!("{} MCP server(s)", mcp.servers.len());
}
for diagnostic in &plugin.diagnostics {
    eprintln!("note: {diagnostic}");
}

§Loading from anywhere: drive the machine yourself

The loader asks four kinds of questions. Answer them from an archive, a database, an HTTP range request, an async runtime — the machine cannot tell the difference:

use agent_plugins::{Answer, Loading, Question};

let mut loading = Loading::start();
while let Some(question) = loading.question() {
    let answer = match &question {
        Question::Kind(path) => stat(path).await,
        Question::Confined(_) => Answer::Confined(true), // archives have no symlinks
        Question::Read(path) => fetch(path).await,
        Question::List(path) => index(path).await,
    };
    loading.answer(answer)?;
}
let plugin = loading.finish()?;

§Launching a stdio MCP server

Turn validated configuration into a runnable process with the §9.1 environment contract already sequenced:

use agent_plugins::{Anchors, McpOutcome, McpServer};

let anchors = Anchors::new("/plugins/my-plugin", "/plugins/data/my-plugin");
if let McpOutcome::Configured(mcp) = &plugin.mcp {
    for entry in &mcp.servers {
        if let McpServer::Stdio(server) = &entry.server {
            std::fs::create_dir_all(&anchors.plugin_data).ok(); // §9.1
            let mut child = server.launch_plan(&anchors).to_command().spawn()?;
        }
    }
}

§Skill bodies are documents, not strings

A skill’s instructions arrive as a Document: a preamble plus a tree of Sections. Skills load progressively, and this lets a client hand a model one section rather than the whole file — and follow the LinkTarget::InPackage links to the references/ and scripts/ files the body points at.

let skill = Document::parse(
    "# Deploy\n\nShip it.\n\n## Rollback\n\nSee [the runbook](references/runbook.md).\n",
);

for (depth, section) in skill.outline() {
    println!("{:indent$}{}", "", section.heading(), indent = depth * 2);
}

let rollback = skill.section(["Deploy", "Rollback"]).unwrap();
assert_eq!(rollback.slug(), "rollback");
assert_eq!(
    rollback.links()[0].target,
    LinkTarget::InPackage("references/runbook.md".into()),
);

§Conformance

This crate implements the full client-side checklist of the specification (Appendix A): manifest validation with the exact §5.2 fatal/non-fatal split, fixed-location component discovery, per-boundary failure isolation, §7.2.1 transport validation, and §9 placeholder expansion. The test suite runs a fixture corpus shared with the sibling TypeScript implementation.

Structs§

Anchors
The two path anchors a client provides to a plugin subprocess (§9.1).
AnswerMismatch
The reply did not match the pending question — a driver bug, not a plugin problem.
Author
Author metadata: a closed object of three optional strings (§5.4).
Diagnostic
One reported, non-fatal loading decision.
DirEntry
One entry from a directory listing.
DirSource
A plugin package rooted at a real directory.
Document
A parsed Markdown document: a preamble and a tree of sections.
Extensions
Client extension data from the manifest extensions field (§8.1).
HeadingLevel
A heading depth: 1–6, as Markdown defines and no more.
LaunchPlan
Everything needed to launch one stdio MCP server.
Link
An inline Markdown link, [text](target).
LoadedPlugin
A fully loaded plugin: the §11.1 conformance outcome.
Loading
The Agent Plugins loading conversation.
Manifest
A validated plugin manifest.
McpConfig
A validated mcp.json: the servers that survived per-entry validation, in declaration order.
McpUrl
An MCP endpoint URL that satisfied §7.2.1: absolute http/https, no user information, no fragment, and http only toward loopback.
MemSource
A plugin package held entirely in memory.
PackagePath
A /-separated path to a file or directory inside a plugin package, relative to the plugin root.
PluginName
A validated plugin name (§5.5): 1–64 characters of a-z, 0-9, -, .; alphanumeric at both ends; no -- or .. runs.
RelativePath
A configured plugin-relative path: begins with ./ and, resolved purely lexically, never leaves the plugin root (§4.1).
RemoteServer
A remote server: Streamable HTTP or legacy SSE (§7.2.1).
Section
One heading and everything beneath it.
ServerEntry
One named server from mcpServers.
Skill
One valid skill discovered under skills/.
SkillMeta
Validated SKILL.md frontmatter per the Agent Skills specification.
Slug
A URL-safe identifier derived from heading text, unique within a document.
StdioServer
A stdio server: how to launch the subprocess (§7.2.1).
Template
A configured string from args, env, or cwd, parsed for expansion.

Enums§

Answer
The caller’s reply to the pending Question.
Command
The command field: one executable token, never a shell string, never expanded (§7.2.1).
Cwd
The working directory of a stdio server, in the three permitted forms of §7.2.1 — plus the default. Any other shape never becomes a Cwd.
FileKind
What kind of entry a package path resolves to.
InvalidName
Exactly which §5.5 constraint a candidate name broke.
LinkTarget
Where a link points, once classified.
ManifestRejection
A fatal manifest problem: the plugin MUST be rejected and none of its components discovered or executed (§5.2, §5.3).
McpDisabledReason
Why MCP was disabled for the whole plugin (§7.2.2 rule 2, §6.2).
McpOutcome
What became of the MCP component type (§6.2, §7.2.2).
McpServer
The closed union of §7.2.1 server variants, discriminated by type.
Origin
Where in the package a diagnostic points.
Placeholder
The two placeholders §9.2 defines — and the only two it permits.
Program
The resolved program to execute.
Question
A question the loader needs answered before it can continue.
Rejection
The plugin was rejected outright — nothing may be discovered or executed (§5.2, §5.3, §4.1 rule 1).
RelativePathError
Why a string was refused as a plugin-relative path.
Rule
The closed set of non-fatal report conditions defined by the specification.
Segment
One parsed piece of a configured string.
ServerInvalid
Why one server entry was skipped (§7.2.2 rule 3).
SkillInvalid
Why a discovered skill was skipped (§7.1).
SpecVersion
The Agent Plugins specification versions this crate recognizes.
Transport
The three declared transports.
UrlInvalid
Why a URL failed §7.2.1.

Constants§

MCP_SCHEMA_1_0_0
The canonical $schema identifier for Agent Plugins 1.0.0 MCP configuration (§7.2.1).
PLUGIN_SCHEMA_1_0_0
The canonical $schema identifier for Agent Plugins 1.0.0 manifests (§5.2).

Traits§

Source
A synchronous supplier of plugin package bytes.

Functions§

load
Load a plugin by running the full loading conversation against a source.
load_dir
Load the plugin rooted at path from the filesystem.
parse_skill_md
Parse and validate the contents of a SKILL.md found at skills/<directory>/SKILL.md.