1
Find the plugin ref
Every listing is addressed as
namespace/name. Find it with
search — tai plugins search uuid names the listing
that ships the item you want.2
Install
--version (or a version field in the body); with no
version the server resolves the newest version compatible with the core it
is running — a core-aware pick, so an install never lands a version your
running core cannot load. A version you pin explicitly is still checked
against the server’s installed tai42-contract — one that does not fit refuses
to install, and the error names the mismatch.3
Verify
tai tools, its extensions under tai extensions.What install does
- Resolves the ref against the registry — with no pinned version, the newest version compatible with the server’s installed tai42-contract (core-aware resolution) — and checks advisories. A version that does not fit the contract, one that is killed, or one under a non-withdrawn critical advisory refuses before anything changes.
- Installs the resolved version by source: a GitHub-sourced plugin downloads
the release tarball, verifies its SHA-256 against the digest the registry
recorded at ingest, and installs only that verified file — a re-pointed tag
is refused; a PyPI-sourced plugin pins the exact version (
package==version) and relies on PyPI’s file immutability for that version. - Patches the server manifest once per provided item — a
tool module lands under
tools, an extension module underextensions_modules, and so on, exactly as the plugin’stai-plugin.ymldeclares. - Reloads the server, and records the marketplace ref and version locally.
Install writes into the server’s own environment and manifest. On a self-hosted
deployment, keep both on persistent storage — the plugin-prefix directory and
the manifest file — so a container recreation does not drop the plugin. See
the persistent mounts.
Provide required environment
Some plugins need environment values to run — an MCP server’s!ENV markers, an
OAuth connector’s client id and secret. The server
decides what a given install requires: the install preview computes the listing’s
required_env — each variable as {name, secret} — and the missing_env still
absent from this server’s environment. Every surface reads that one server-computed
answer; none of them re-derives it.
- CLI —
--env KEY=VALUEsupplies a value, and it is the only flag that carries a value.--secret KEYis a bare mark (a key, no value) that keeps that key masked in the environment and out of logs; a value you want hidden is still passed with--env KEY=VALUEand additionally marked with--secret KEY. A connector’s client secret is auto-marked, so the mark is optional there. Both flags are repeatable. - Studio — the install dialog lists the missing variables, prompts for them,
and seeds each secret toggle from
required_env, so you fill exactly what the server says is missing.
--dry-run shows the
delivery and the required env without installing.
Mount the routes
A plugin that mounts HTTP routes — arouter item, or a channel with an inbound
webhook — declares them relative to a
base prefix, and install is where you see the resulting absolute paths, remap the
prefix if you want, and accept any route the plugin serves without a credential.


The Studio install dialog: each route resolved to its full path, a per-item base to remap the prefix, and the acceptance checkbox for the routes served without authentication.
/api/... path, flags which are public,
and lists any collision with a route the core or an already-installed plugin owns.
--dry-run prints that table and exits non-zero if anything collides.
Remap the base. Every route-carrying item mounts under a default base; move it
with --mount item=base (repeatable), or a route_mounts object in the body. The
whole item’s routes shift together — only the base moves, never the /api/ root
or a route’s own path:
public answers
without authentication — an inbound webhook has to. Install refuses until you
say so: without acceptance it returns 400 PUBLIC_ROUTES_NOT_ACCEPTED and lists the
routes, so opening an unauthenticated door is always a deliberate step. Accept with
--accept-public-routes (or "accept_public_routes": true). On an update, only
a route that is newly public — not one you already approved on the installed
version — needs accepting again.
The install receipt returns the mounted routes — each item, full path, methods, and
public flag — so you can confirm what opened where.
A collision is a remap
Two plugins can never own the same route. If a plugin’s resolved paths clash with a route the core or another installed plugin already owns, install stops with409 ROUTE_COLLISION and names the clashing paths; nothing is installed. The remedy
is to remap the colliding item onto a free base and install again:
base
alongside the install record, so a reload or a container recreation brings every
route back up at the same path — a remapped route stays remapped, with no re-entry
of the mount. Keep the install record and manifest on
persistent storage so a recreated
container does not lose them.
Update
tai plugins installed shows update availability per plugin; update resolves
the new version and runs the same install flow. Pin a target with --version,
or omit it for the latest.
To move the whole installed roster onto versions compatible with the running
core in one step — the flow after a self-hosted image upgrade:
Uninstall
From the Studio
The Marketplace screen’s plugin detail offers the same install, update, and uninstall actions with progress surfaced in place — it drives these routes.See also
- Browse and search — find the ref.
- Advisories — what a killed version means.
- The manifest — what the patch touches.

