> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aperium.apps.hillspire.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin manifest reference

> The PluginManifest surface: identity, auth, capabilities, tools, connections, and the query-profile contract.

A plugin *is* a `PluginManifest`. This page is a distilled reference to the manifest
surface — enough to author from. For the full field-by-field catalog with every validation
rule, read the SDK's `docs/manifest-reference.md` in the codebase. For a guided build, see
[Build a plugin](/develop/build-a-plugin).

## PluginManifest

The top-level object returned by `build_manifest(ctx)`:

| Field                 | Purpose                                                                |
| --------------------- | ---------------------------------------------------------------------- |
| `identity`            | Who the plugin is and who owns it (`PluginIdentity`).                  |
| `auth` / `auth_shape` | How it authenticates. Prefer `auth_shape` for new plugins.             |
| `capabilities`        | Routing metadata; tools reference these by name.                       |
| `tools`               | The callable tools the plugin exposes.                                 |
| `resources`           | URI-addressable read-only data (`ResourceSpec`).                       |
| `health`              | Optional cheap credential / reachability check.                        |
| `lifecycle`           | Optional app-lifespan startup/shutdown hooks (in-memory only).         |
| `connection_shapes`   | Ownership model for connection records.                                |
| `profiles`            | Deployment variants (e.g. commercial vs. gov cloud, prod vs. sandbox). |
| `categories` / `ui`   | Onboarding and Integrations-page presentation.                         |
| `mcp` / `remote_mcp`  | Optional MCP exposure, or tools sourced *from* a remote MCP server.    |

**Construction-time invariants (a violation fails the build):** tool, capability, and
resource names are unique; every `ToolSpec.capability` resolves to a capability on the same
manifest; write-class tools live under an `approval_required` capability and declare
`idempotent` explicitly.

## Identity

`PluginIdentity` — `name` (slug matching `^[a-z][a-z0-9_-]*$`, equal to the entry-point
name), `version`, `display_name`, `description`, `owner`, optional `homepage`.

## Auth

`PluginAuth.type` is one of the `AuthType` shapes — describing the *shape* of credentials,
never values:

| `AuthType`                  | Shape                                     |
| --------------------------- | ----------------------------------------- |
| `NONE`                      | No credentials.                           |
| `API_KEY`                   | A single key.                             |
| `BASIC`                     | Username + password.                      |
| `OAUTH2_CLIENT_CREDENTIALS` | Service-to-service OAuth.                 |
| `OAUTH2_USER`               | Per-user OAuth.                           |
| `CUSTOM`                    | Anything else the plugin resolves itself. |

Credentials are resolved at runtime through the `SecretsProvider` on `PluginContext`; the
manifest only declares what's required. Prefer the canonical `auth_shape` over the legacy
`auth.type` for new plugins — onboarding, health sweepers, and connection setup all read it.

## Capabilities

`CapabilityMetadata` is the routing surface. A capability groups tools and tells the router
when to pick them:

| Field                                 | Purpose                                                                         |
| ------------------------------------- | ------------------------------------------------------------------------------- |
| `name`                                | Dot-hierarchical slug (e.g. `odoo.partners.read`).                              |
| `domains` / `entities` / `operations` | Canonical taxonomy (`domain.*`, `entity.*`, `operation.*`).                     |
| `services`                            | Canonical `service.*` IDs — the strongest product-compatibility routing signal. |
| `read_only` / `approval_required`     | Whether write-class tools may live here.                                        |
| `minimum_role`                        | Hard lower bound on the role that may invoke bound tools.                       |
| `routing_hints`                       | Natural-prose "what it does / when to use / when not to" guidance.              |
| `routing_examples`                    | 2-3 short user utterances that should route here.                               |

<Tip>
  Write `routing_hints` as plain sentences, not keyword bags — the ranker is BM25 over that
  prose, and it doubles as model-facing fallback guidance. Give every user-facing
  capability `routing_examples`.
</Tip>

### Role floor

`minimum_role` (`None` by default) is a hard floor. `None` lets any actor on the
tenant-reach ladder invoke; `TENANT_ADMIN` restricts to tenant/super admins (use for
tenant-shared credentials, financial finalization, bulk/admin operations, irreversible
deletes); `SUPER_ADMIN` restricts to the platform operator. Tenants can tighten further but
cannot loosen below the floor.

## Tools

`ToolSpec` binds a callable to the manifest:

| Field                            | Purpose                                                                                |
| -------------------------------- | -------------------------------------------------------------------------------------- |
| `name`                           | Slug (`^[a-z][a-z0-9_]*$`).                                                            |
| `input_schema` / `output_schema` | Pydantic model classes; the handler's annotations must match exactly.                  |
| `handler`                        | `handler(inp)` or `handler(ctx, inp)` — validated against the schemas at construction. |
| `risk`                           | `READ`, `WRITE_LOW`, `WRITE_HIGH`, or `DESTRUCTIVE`.                                   |
| `capability`                     | Name of a capability on the same manifest.                                             |
| `idempotent`                     | Required on write-class tools so the runtime knows whether retry-on-error is safe.     |
| `when_to_use` / `examples`       | Optional post-routing, model-facing invocation guidance.                               |

READ tools are idempotent by definition. WRITE-class tools must declare `idempotent`
explicitly — `False` for create-\* tools that mint a new resource each call, `True` for
update/delete/set-state tools whose post-condition depends only on the input.

## Connections and profiles

* **`connection_shapes`** declares who owns a connection record: user-owned, tenant-owned,
  either, a layered user-plus-tenant prerequisite, or none.
* **`profiles`** declares deployment variants (commercial/gov cloud, production/sandbox).
  Hosts project one setup target per profile; `config_keys` are safe to surface as hints
  and never carry secret values.

## Query-profile and pushdown contract

Every read-capable plugin (one or more `READ` tools) must declare a **query profile** —
enforced fleet-wide by the `check_query_profile` gate. Satisfy it one of three ways, in
order of preference:

<Steps>
  <Step title="Pushdown">
    For providers with a native query language, declare a `DatasetPushdown` so the host
    pushes a plan prefix (filter / project / sort / limit and declared aggregates) down to
    the source instead of fetching everything and computing host-side.
  </Step>

  <Step title="Bounded fetch">
    Expose a dataset the host can resolve to completeness — an explicit `datasets` entry, or
    a READ tool returning a single list of flat scalar-field rows, which the SDK
    auto-adapts into a queryable dataset.
  </Step>

  <Step title="Recorded opt-out">
    When the read surface is genuinely non-tabular (relevance search, ops snapshots), record
    a rationale in `[tool.aperium.query_surface]`. An empty rationale or a stale key fails
    the gate.
  </Step>
</Steps>

`DatasetSpec` validates the pairing at build time: a dynamic dataset (no fixed schema)
declaring `pushdown` must supply a `schema_probe`; a fixed-schema dataset must not. An
ordinary refusal degrades to fetch-then-compute; a `PlanPrefixRetryError` preserves terminal
model remediation without that fallback.

## MCP exposure

Most first-party plugins are direct-call and leave `mcp` unset. Two opt-in surfaces exist:

* **`mcp` (`McpConfig`)** — expose this plugin's tools *as* an MCP server (`STDIO`, `SSE`,
  or `HTTP`; `port` required for `HTTP`/`SSE`). Opt in only for MCP-adapter examples or
  plugins deliberately exposed to non-Aperium MCP clients.
* **`remote_mcp` (`RemoteMcpToolsetSpec`)** — the inverse: the plugin's tools come *from* a
  remote MCP server, introspected at runtime. The host provisions a per-tenant managed
  server row from a credential-rendered URL template.

## JSON wire shape

`PluginManifest.model_dump_json()` emits a metadata view for external tooling: identity,
auth type, and capabilities dump in full; input/output/config schemas emit as JSON Schema;
handler, resource, and health-check callables are excluded — rehydrating executable code
from JSON is intentionally unsupported.
