1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! 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.
//!
//! [Agent Plugins specification]: https://agent-plugins.org/specification
//!
//! # 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 [`Question`]s; 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
//!
//! ```no_run
//! 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}");
//! }
//! # Ok::<(), agent_plugins::Rejection>(())
//! ```
//!
//! # 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:
//!
//! ```no_run
//! use agent_plugins::{Answer, Loading, Question};
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # async fn stat(p: &agent_plugins::PackagePath) -> Answer { todo!() }
//! # async fn fetch(p: &agent_plugins::PackagePath) -> Answer { todo!() }
//! # async fn index(p: &agent_plugins::PackagePath) -> Answer { todo!() }
//! 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()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Launching a stdio MCP server
//!
//! Turn validated configuration into a runnable process with the §9.1
//! environment contract already sequenced:
//!
//! ```no_run
//! use agent_plugins::{Anchors, McpOutcome, McpServer};
//!
//! # let plugin = agent_plugins::load_dir("p")?;
//! 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()?;
//! }
//! }
//! }
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Skill bodies are documents, not strings
//!
//! A skill's instructions arrive as a [`Document`]: a preamble plus a tree of
//! [`Section`]s. 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.
//!
//! ```
//! # use agent_plugins::{Document, LinkTarget};
//! 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.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use MemSource;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;