Skip to main content
A plugin is how Aperium learns to talk to a system. It’s a Python package that exports one factory function returning a PluginManifest — a declaration of the plugin’s identity, how it authenticates, which capabilities and tools it exposes, and how the host should surface it. The runtime discovers plugins through the aperium.plugins entry-point group; transports (MCP, HTTP, in-process) attach as adapters on top of a manifest — they are never the plugin itself.
This page is for building a first-party plugin in the codebase. If you just want to wire Aperium to an external system that already speaks MCP, an admin can register it through the UI with no code — see Custom integrations.
The aperium-plugin-example plugin is the canonical copy-me template and the worked example throughout this page.

1. Scaffold

The slug matches ^[a-z][a-z0-9_-]*$ and must equal the entry-point name. The scaffolder lays out the package under the aperium.plugins.<slug> namespace and registers the entry point in the new package’s pyproject.toml:
Sync the workspace so the new package is importable:

2. Author the manifest

Every plugin exports a build_manifest(ctx) -> PluginManifest factory at its package top level. It is a pure factory: resolve secrets and wire dependencies, but do no I/O at factory-call time — I/O belongs in lifecycle hooks or tool handlers.
The manifest is where the real work is. The essentials:
1

Identity and auth

Declare who the plugin is (PluginIdentity) and how it authenticates. Use auth_shape and connection_shapes for new plugins so onboarding and health sweepers can project the setup card. Credentials are resolved at runtime via ctx.secrets — the manifest only declares what’s needed, never the values.
2

Capabilities

CapabilityMetadata entries are routing metadata: domains, entities, operations, services, routing_hints, and 2-3 routing_examples per user-facing capability. Tools reference a capability by name. Write-class tools must live under a capability with approval_required=True.
3

Tools

Each ToolSpec binds an input model, an output model, a handler, a RiskClass, and a capability. Handler signatures are validated strictly against the schemas at construction — a drift fails the manifest build.
4

A query profile

Every read-capable plugin must declare a query profile (enforced by the check_query_profile gate). The simplest path: expose one READ tool that returns a single list of flat, scalar-field rows — the SDK auto-adapts it into a queryable dataset. Providers with a native query language declare a DatasetPushdown instead.
See the Manifest reference for every field and its rules.

3. Validate

Validation builds the manifest through real entry-point discovery and runs the manifest invariants (unique tool/capability names, resolvable capability references, handler-schema agreement, write-class approval rules, query-profile coverage) plus import-linter’s plugin-isolation contract.

4. Test

Plugin tests live under your package’s tests/ directory. The SDK ships a public test harness (aperium.plugin_sdk.testing) with in-memory fakes so real handlers run unchanged:
Run them through the workspace test suite:
For plugins that replay recorded provider traffic, record cassettes with uv run dev plugins record my_integration.

5. Smoke

smoke loads every shipped plugin manifest the way the host does at startup — the final check that your plugin registers cleanly alongside the rest of the fleet.

The worked example

The plugins/aperium-plugin-example package is the smallest viable, copyable plugin. It’s a fixture-only reference (never auto-enabled on a tenant) that exercises the full manifest surface so you can read one working plugin end to end:
  • A build_manifest(ctx) factory with a complete PluginIdentity, auth_shape, connection_shapes, ui metadata, and a setup guide.
  • An echo READ tool and a list_echoes tool whose flat-row output auto-adapts into the plugin’s query profile.
  • One capability with routing hints, one background JobSpec, and a verified webhook spec.
Copy it, rename the slug and namespace, then replace the reference tools with your own.
Keep tool output models flat where you can — a single list of scalar-field rows is what lets the SDK auto-adapt a READ tool into a query dataset with no extra declaration.

Manifest reference

Every model and field on the PluginManifest surface.

Custom integrations

Register an external MCP server through the Admin Console — no code.