Skip to main content
Install a marketplace plugin into a running server in one command. Install is a server-side operation: the server itself resolves the listing, installs the package, patches its manifest, and reloads — so the CLI, the Studio, and the HTTP API all drive the same flow.
1

Find the plugin ref

Every listing is addressed as namespace/name. Find it with searchtai plugins search uuid names the listing that ships the item you want.
2

Install

Pin a version with --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

The plugin appears with its installed version, and its items are live — its tools list under tai tools, its extensions under tai extensions.

What install does

  1. 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.
  2. 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.
  3. Patches the server manifest once per provided item — a tool module lands under tools, an extension module under extensions_modules, and so on, exactly as the plugin’s tai-plugin.yml declares.
  4. Reloads the server, and records the marketplace ref and version locally.
If any step fails, the whole install aborts and the error names the step — never a half-installed plugin. The manifest, registration, and install record fully revert; package changes the underlying pip run already made are pip’s own boundary. Registry-dispatched items — tools, agents, extensions, storages, and a channel’s registration and outbound delivery — are live the moment that reload lands, and so are the HTTP surfaces. A router or middleware plugin, and a Tier-2 channel’s inbound webhook route, come live on the next reload / config-apply: the epoch rebuild re-imports the plugin package and its route-sibling modules and re-registers the declared routes, so a route-carrying plugin is serving on the same reload as the rest — no full process restart required (Tier-1 confirm/external channels register no inbound route). See self-hosted upgrades.
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=VALUE supplies a value, and it is the only flag that carries a value. --secret KEY is 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=VALUE and 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.
An install that would leave a required variable unresolved is refused before anything is written, and the error names the variables. --dry-run shows the delivery and the required env without installing.

Mount the routes

A plugin that mounts HTTP routes — a router 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 marketplace install dialog showing the resolved routes for a route-carrying plugin, a base-prefix input to remap them, and a checkbox accepting the public routes served without authentication.The marketplace install dialog showing the resolved routes for a route-carrying plugin, a base-prefix input to remap them, and a checkbox accepting the public routes served without authentication.

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.

Preview first. Ask the server what a given install would mount — no side effects, nothing written:
The preview resolves each route to its full /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:
Accept the public routes. A route the plugin declares 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 with 409 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:
The same check runs on the manifest patch, so a collision is caught in pre-flight, before any package is fetched. Mounts persist across restart. The server stores each item’s chosen 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

Uninstall reverses the manifest patch, reloads to converge the live registration with the stripped manifest, and then uninstalls the package. It converges: a failure names exactly what remains, and re-running uninstall completes the removal.
Install, update, and uninstall mutate the running server’s environment. Scope the /api/marketplace/* routes with access control like any other mutating surface.

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