Python API reference
Import the top-level API as agent_plugins:
import agent_plugins as apThe installed agent-plugins distribution includes separate consumer and packaging skills:
print(ap.read("agent-plugins"))
print(ap.read("agent-plugins", skill="package-agent-plugin"))The distribution supports Python 3.10 through 3.14 and ships a py.typed marker.
| Task | Entry point |
|---|---|
| Read a skill with its installed package context | read() |
| Preview selected package files | build_plan() |
| Add a plugin to an existing wheel | attach_wheel() |
| Find plugins in this environment | locate() and installed() |
| Inspect authored files | Plugin.from_project() or Plugin(path) |
| Read instructions and resources | Skill |
| Inspect plugin metadata | Manifest |
| Inspect tool configuration and prepare a launch | MCPConfig |
| Wrap a Python build backend | BuildBackend |
Read a briefing
read(distribution_name, *, skill=None)
def read(distribution_name: str, *, skill: str | None = None) -> str: ...Returns a Markdown briefing from the current interpreter's installation. The briefing includes distribution and plugin metadata, the interpreter and resource root, environment guidance, a bounded inventory, MCP summaries, and the complete selected SKILL.md source. It also includes Documentation and Documentation Index URLs declared in the distribution's Project-URL metadata.
distribution_nameis the installed Python distribution name.skillis the structural skill directory name. Omitted orNoneusesdistribution_nameexactly, including its spelling. An unavailable name raises an error listing the available skills.
The function returns text without printing, importing the target package, or activating its components. Raises AgentPluginError for an absent or unusable installation or unavailable skill. Invalid plugin documents raise ValidationError.
The CLI read uses the same renderer but includes all skills by default. Use --skill NAME for the equivalent single-skill output.
To expose a core skill through help(my_package.agent), set the optional agent module's __doc__ to ap.read("my-package"). Read the bundled package-agent-plugin skill for the complete authoring pattern.
Build planning
build_plan(project=".")
def build_plan(project: str | Path = ".") -> BuildPlan: ...Reads [tool.agent-plugins] from project/pyproject.toml, resolves the authored plugin root, and returns the complete ordered file plan.
project can be a string or Path. It must identify the Python project directory.
Raises AgentPluginError for missing configuration, configuration I/O failures, invalid TOML syntax or encoding, invalid settings, an unusable plugin root, unsafe patterns, directory symlinks, missing required files, or selected files that resolve outside the plugin root.
BuildPlan
@dataclass(frozen=True, slots=True)
class BuildPlan:
project: Path
root: Path
files: tuple[FileMapping, ...]projectis the resolved Python project directory.rootis the resolved authored or staged plugin root.filesis the ordered source-to-target mapping.
FileMapping
@dataclass(frozen=True, slots=True)
class FileMapping:
source: Path
target: PurePosixPathsource is an absolute local path. target is relative to the plugin root inside an artifact.
For a supplied BuildPlan, attach_wheel() checks that every source is a readable regular file and that targets are unique portable paths. Targets must include plugin.json. NUL characters, escaping paths, and file-directory collisions raise AgentPluginError before rewriting.
Wheel attachment
attach_wheel(wheel, *, project=None, plan=None, output_dir=None)
def attach_wheel(
wheel: str | Path,
*,
project: str | Path | None = None,
plan: BuildPlan | None = None,
output_dir: str | Path | None = None,
) -> WheelAttachment: ...Attaches the selected Agent Plugin to one existing .whl file and returns the completed artifact details.
wheelidentifies an existing wheel file.projectidentifies the Python project containing[tool.agent-plugins]. It defaults to the current directory whenplanis omitted.plansupplies a validatedBuildPlanfrom the caller's configuration source. Attachment reads no project configuration when this value is present.output_diridentifies an existing directory for the attached artifact. The output keeps the input wheel filename.
Passing both project and plan raises AgentPluginError before artifact access. With no output_dir, attachment writes a temporary wheel beside the input and atomically replaces the input after the complete rewrite succeeds. With output_dir, it preserves the input and refuses to overwrite an existing destination.
The rewrite validates the wheel structure and member paths, replaces the owned plugin payload and marker, removes invalidated RECORD.jws and RECORD.p7s signatures, and rebuilds RECORD. It preserves the outer file mode, archive comment, and non-owned ZIP members. Running the same plan again replaces the owned payload deterministically.
Planning, validation, plugin-file reads, ZIP writes, and replacement failures leave the input bytes unchanged. Expected project, plan, wheel, ZIP, and filesystem failures raise AgentPluginError with the affected path or archive member and a recovery action.
import agent_plugins as ap
result = ap.attach_wheel(
"dist/example-1.0.0-py3-none-any.whl",
project="packages/python",
)
print(result.output)WheelAttachment
@dataclass(frozen=True, slots=True)
class WheelAttachment:
source: Path
output: Path
dist_info: PurePosixPath
plugin_root: PurePosixPath
files: tuple[PurePosixPath, ...]
replaced_existing_plugin: bool
removed_signatures: tuple[PurePosixPath, ...]| Field | Behavior |
|---|---|
source | Resolved input wheel path |
output | Resolved attached wheel path |
dist_info | Top-level wheel metadata directory |
plugin_root | Installed .agent-plugin directory |
files | Plugin-root-relative targets in BuildPlan order |
replaced_existing_plugin | True when the input contained its marker or plugin payload |
removed_signatures | Removed RECORD.jws and RECORD.p7s archive paths |
Installed discovery
locate(distribution_name)
def locate(distribution_name: str) -> Plugin: ...Finds an installed Python distribution, reads its agent_plugins.json marker, validates the selected file inventory, and returns a new Plugin handle.
Pass the Python distribution name used by pip and importlib.metadata. An empty name, absent distribution, absent marker, outdated marker, invalid marker, unusable root, or invalid selected file raises AgentPluginError.
installed()
def installed() -> dict[str, Plugin]: ...Returns marked distributions keyed by Python distribution name and sorted without regard to case. For equivalent distribution names, the first installation in Python metadata discovery order wins, matching locate(). An unmarked first installation shadows later installations of the same distribution.
Discovery is fail-fast. An invalid marked distribution raises AgentPluginError and stops the scan.
Plugin
Plugin(path: str | os.PathLike[str])Creates a handle for an authored plugin directory. Construction resolves the root, recursively inventories every regular file, and requires root-level plugin.json. It creates document and skill handles but does not read their content.
Raises AgentPluginError when the root cannot be resolved, is not a directory, lacks a regular plugin.json, or contains a discovered file that cannot be resolved inside the root.
Installed Plugin handles returned by discovery use the exact marker-selected inventory.
Plugin.from_project(project=".")
@classmethod
def from_project(
cls,
project: str | os.PathLike[str] = ".",
) -> Plugin: ...Calls build_plan(project) and returns a Plugin containing exactly the selected target paths. It honors the staged .agent-plugin/ root during a source-distribution rebuild.
Use this constructor when Python package selection defines the source boundary. Use Plugin(path) to inventory every current regular file below a plugin directory.
Build-plan and selected-inventory failures raise AgentPluginError.
Properties
| Property | Type | Behavior |
|---|---|---|
path | Path | Resolved absolute plugin root |
files | tuple[Path, ...] | Absolute paths in this handle's selected inventory |
manifest | Manifest | Stable lazy handle for plugin.json |
skills | tuple[Skill, ...] | Immediate selected Agent Skill directories |
mcp | MCPConfig | None | Stable lazy handle when root-level mcp.json is selected |
plugin.skill(name)
plugin.skill(name: str) -> SkillReturns the cached immediate Skill whose structural directory name matches name exactly. name must be one non-empty directory component. Absolute values, separators, ., .., and Windows drive-qualified values raise AgentPluginError.
An unavailable name raises AgentPluginError with the requested name and sorted available names.
tree()
plugin.tree(*, max_depth: int | None = 4, max_files: int | None = 100) -> strReturns a deterministic ASCII rendering. None removes a bound. Negative values raise ValueError.
str(plugin) and repr(plugin) return the default tree. Path(plugin) and os.fspath(plugin) return the plugin root. Notebook display uses an escaped <pre> rendering.
Two Plugin handles compare equal and have the same hash when their resolved root and selected relative filenames match. File content and loaded document values do not participate.
Skill
Skill(path: str | os.PathLike[str])Creates a handle for an authored skill directory. Construction inventories every regular file and requires exact-case SKILL.md.
Raises AgentPluginError when the root cannot be resolved, is not a directory, lacks a regular SKILL.md, or contains a discovered file that cannot be resolved inside the root.
Properties
| Property | Type | Behavior |
|---|---|---|
name | str | Structural directory name used by plugin.skill(name) |
path | Path | Resolved absolute skill root |
files | tuple[Path, ...] | Absolute paths in the selected skill inventory |
frontmatter | str | Raw text between the --- delimiter lines |
body | str | Raw Markdown after the closing delimiter |
source | str | Complete exact SKILL.md text |
The first access to source, frontmatter, or body reads UTF-8 text, checks delimiter structure, and caches all three strings or the first ValidationError. source preserves delimiters, line endings, spacing, and final newline state. YAML fields are not parsed.
skill.file(relative_path)
skill.file(relative_path: str | os.PathLike[str]) -> PathReturns one selected regular file below the skill root. The portable relative path must match the selected inventory exactly. Absolute paths, empty paths, ., parent traversal, backslashes, directories, unselected files, and paths that resolve outside the root raise AgentPluginError.
The method rechecks the current file and containment before returning its absolute inventory path. A file created after the Skill handle was constructed remains unselected.
skill / "references" / "api.md"The / operator delegates to ordinary unchecked pathlib.Path joining. Use skill.file() when selection and containment are required.
Skill.tree(), native path conversion, and display follow the Plugin contracts. Two Skill handles compare equal and have the same hash when their structural names, resolved roots, and selected relative filenames match.
Manifest
Manifest(path: str | os.PathLike[str])Creates a lazy file-backed plugin.json handle. Construction resolves an existing regular file. path, string conversion, native path conversion, and repr() leave JSON unread.
Raises AgentPluginError during construction when the path cannot be resolved to a regular file. Content failures raise ValidationError on first data access.
Any data property triggers one UTF-8 JSON read and validation. The normalized value or exception is cached for the handle.
| Property | Type |
|---|---|
path | Path |
schema | str |
name | str |
version | str | None |
description | str | None |
author | Author | None |
homepage | str | None |
repository | str | None |
license | str | None |
keywords | tuple[str, ...] |
extensions | Mapping[str, Mapping[str, object]] |
issues | tuple[ValidationIssue, ...] |
See plugin.json for field validation.
MCPConfig
MCPConfig(path: str | os.PathLike[str], manifest: Manifest)Creates a lazy file-backed mcp.json handle. The manifest's parent directory supplies the plugin root for path-containment checks.
Raises AgentPluginError during construction when the MCP path cannot be resolved to a regular file. Manifest or MCP content failures raise ValidationError on first data access.
First data access validates the manifest, then reads and validates the MCP document. The normalized result or exception is cached.
| Property | Type |
|---|---|
path | Path |
schema | str |
servers | Mapping[str, MCPServer] |
issues | tuple[ValidationIssue, ...] |
String and native path conversion return the configuration path without loading JSON.
mcp.resolve_stdio(name, *, data_dir, base_env=None)
mcp.resolve_stdio(
name: str,
*,
data_dir: str | os.PathLike[str],
base_env: Mapping[str, str] | None = None,
) -> ResolvedStdioServerSelects one validated stdio server and returns immutable subprocess inputs. A bare command remains one token. A ./ command becomes a contained absolute path to a regular plugin file. When the MCPConfig came from a Plugin, the command must also belong to that plugin's selected inventory.
The resolver expands ${PLUGIN_ROOT} and ${PLUGIN_DATA} once in arguments, configured environment values, and cwd. It copies base_env, overlays configured values, then sets the two reserved variables. Environment-name matching follows platform rules.
data_dir must already exist as a directory. The caller chooses and retains that location. The caller also owns process creation, permissions, logging, MCP transport, and lifecycle.
An unknown server, non-stdio transport, missing data directory, unavailable plugin-relative command, or invalid working directory raises AgentPluginError. Manifest and MCP document failures retain their cached ValidationError behavior.
See mcp.json for server rules and partial validation.
Normalized values
Author
Author(name: str | None = None, email: str | None = None, url: str | None = None)Frozen author metadata returned by Manifest.author.
StdioServer
StdioServer(
command: str,
args: tuple[str, ...] = (),
env: Mapping[str, str] = {},
cwd: str | None = None,
)Frozen stdio configuration with class attribute type == "stdio". Construction copies args to a tuple and env to a read-only mapping.
StreamableHTTPServer
StreamableHTTPServer(url: str, headers: Mapping[str, str] = {})Frozen Streamable HTTP configuration with class attribute type == "streamable-http" and a copied read-only header mapping.
SSEServer
SSEServer(url: str, headers: Mapping[str, str] = {})Frozen legacy SSE configuration with class attribute type == "sse" and a copied read-only header mapping.
MCPServer
MCPServer = StdioServer | StreamableHTTPServer | SSEServerThis is a type alias for normalized server values.
ResolvedStdioServer
@dataclass(frozen=True, slots=True)
class ResolvedStdioServer:
command: str
args: tuple[str, ...]
env: Mapping[str, str]
cwd: PathContains shell-free subprocess inputs resolved from one stdio declaration. Construction copies args to a tuple and env to a read-only mapping.
Direct value construction provides immutability and normalization of tuple or mapping containers. Obtain values through MCPConfig.servers when schema validation is required.
Diagnostics
AgentPluginError
Base exception for build planning, packaging, discovery, filesystem, and document failures.
ValidationIssue
ValidationIssue(location: tuple[str | int, ...], message: str)Frozen diagnostic value used by fatal errors and non-fatal issue collections.
ValidationError
ValidationError(path: Path, issues: tuple[ValidationIssue, ...])Subclass of AgentPluginError for fatal document validation. issues must contain at least one value. The exception string renders the first issue.
See Errors and issues for handling patterns.
BuildBackend
Import the low-level adapter from the build package:
from agent_plugins.build import BuildBackend
backend = BuildBackend("example_backend.build")BuildBackend is a frozen value with one public field, module: str. It exposes build_wheel, build_sdist, build_editable, and the corresponding three get_requires_for_build_* hooks.
backend.build_wheel(
wheel_directory: str,
config_settings: dict[str, object] | None = None,
metadata_directory: str | None = None,
) -> str
backend.build_sdist(
sdist_directory: str,
config_settings: dict[str, object] | None = None,
) -> str
backend.build_editable(
wheel_directory: str,
config_settings: dict[str, object] | None = None,
metadata_directory: str | None = None,
) -> str
backend.get_requires_for_build_wheel(
config_settings: dict[str, object] | None = None,
) -> list[str]
backend.get_requires_for_build_sdist(
config_settings: dict[str, object] | None = None,
) -> list[str]
backend.get_requires_for_build_editable(
config_settings: dict[str, object] | None = None,
) -> list[str]Each build hook computes a plan from Path.cwd(), calls the delegated module, augments the returned artifact, and returns the delegated filename. build_wheel() uses the same regular-wheel operation as attach_wheel(). Editable marker attachment remains internal to the adapter. The delegate must expose all six hooks.
wheel_directory and sdist_directory identify the output directory supplied by the build frontend. config_settings and metadata_directory pass through to the delegate unchanged. Each get_requires_for_build_* method copies the delegate's sequence into a new list.
The three artifact hooks can raise AgentPluginError while planning or rewriting. Delegate imports, missing delegate hooks, and delegate failures keep their original Python exceptions. A rewrite failure preserves the artifact bytes returned by the delegate.