Skip to content

Package an extension for Apizr

0.4 development — not released

These commands require a wheel built from the development source, not the published 0.3.0 package. Follow the development installation and record its source commit.

A plugin is trusted code in a separate Python environment. Its public interface is a wheel manifest plus the existing apizr.extension/v1 request/response contract. No registration decorator, SDK or internal core import is needed.

Declare and package the entry point

Include apizr-extension.json at the wheel root:

{
  "schema": "apizr.extension-manifest/v1",
  "name": "apizr-extension-probe",
  "version": "0.0.0",
  "module": "apizr_extension_probe.runtime",
  "protocol": "apizr.extension/v1"
}

The canonical name and exact version must agree with wheel filename and METADATA. The module must be runnable with python -m. Build a wheel, not an editable checkout. The existing packaged example shows the build configuration, manifest and a dependency-free implementation. Its runtime.py accepts typed DescribeArguments, returns a structured result, refuses an unknown operation and writes diagnostics only to stderr. The old prototype __main__ entry point is retained for historical tests; use the manifest's runtime module, not that private prototype protocol.

Receive one request and return one response

Follow the wire contract for the exact request, success and error fields. Copy the protocol, request ID and operation from the validated request. Read a bounded UTF-8 JSON object from stdin through EOF; produce exactly one finite JSON response on stdout and exit. Never log to stdout. Do not echo secrets in diagnostics or error objects.

An unsupported operation returns status: "error" with error.code and error.message; it is not a successful result. The core validates correlation, protocol, duplicate keys and finite JSON, and maps remote failures to a redacted PluginFailed. It never forwards raw plugin stderr or error contents.

The caller controls argument/output limits, deadline and cancellation through the invocation API. No secret environment variables are inherited by CLI invocation. A process group is cleaned on failure, but independently detached descendants are outside that guarantee. This is not a sandbox; a plugin runs with the user's privileges.

Declare dependencies without installing into the core

Put Python requirements in wheel METADATA. For installation with dependencies, provide an exact, hashed requirements file and local wheelhouse containing the complete transitive closure. Use the existing supported requirements format and archive rules: no source builds, editable paths, direct dependency URLs, startup .pth files, sitecustomize or usercustomize. Only the primary plugin needs an Apizr manifest. uv installs offline into the plugin environment and checks dependency constraints.

A catalog entry can then be generated from those actual artifacts. External tools such as Docker are prerequisites, not Python dependencies that the catalog installs. Do not treat profiles as permission policies.

Run the example outside the checkout

Use installed uv and Python with the existing packaging qualification:

uv run --locked python scripts/smoke_extension_packaging.py \
  --work-dir /tmp/apizr-author-proof

Choose a new temporary path. The driver builds real wheels, installs core and example in separate environments, invokes the manifest module from outside the checkout and checks that the core is unchanged. invoke_installed.py demonstrates successful describe and refusal of an unknown operation using only public APIs. The example requires no optional core imports.

This one-request extension protocol is not MCP. The optional apizr-mcp plugin additionally exposes a persistent stdio server, managed by apizr mcp serve. A generated business MCP server is a separate bundle; neither transport changes this extension contract.