tai-plugin.yml schema (PluginSpec).
Every installable TAI plugin ships a tai-plugin.yml — at its repo root and
as package-data inside the built wheel. The file names the listing
(namespace/name), the pip distribution that backs it, the tai42-contract
compatibility range, declared capabilities, and the item-level provides
index: one entry per tool/agent/extension/… the package registers, because
items are what users search for while the plugin is what gets installed.
The models here are the one schema shared by the marketplace registry’s
validator, the skeleton’s installer, and each plugin repo’s own spec test.
The YAML I/O helpers live above the contract (tai42_kit.plugins) — the
contract itself has no YAML dependency.
KIND_MANIFEST_BINDINGS is the single source for how each provided item
kind wires into a Manifest: which manifest
field an installer patches and with what shape (a config row, a module-list
entry, a single-module slot, a package-name entry, an mcp transport entry,
a connectors descriptor-list append, or no manifest field at all for the
env-selected config kind).
Version strings are validated as PEP 440 (an anchored regex of the spec’s
canonical pattern) and contract as a PEP 440 specifier set — shape-level
parseability only; evaluating whether a version satisfies a range is the
consumer’s concern.
display_name and icon are the optional marketplace display metadata: a
human UI title (the UI titleizes name when absent) and either a packaged
image path relative to the package root or an https URL (the UI falls back
to a generated monogram when absent).
DATA_BLOCK_BY_KIND
tai42_contract.plugins.bindings.DATA_BLOCK_BY_KIND
KIND_MANIFEST_BINDINGS
tai42_contract.plugins.bindings.KIND_MANIFEST_BINDINGS
ManifestBinding
tai42_contract.plugins.bindings.ManifestBinding
Manifest.
field names the manifest field an installer patches for an item of
this kind; mode is the patch shape; payload is the axis the item
carries — module (the item names an import path) or data (the item
carries its kind’s declarative block):
config_row— append a config row (tools/agents) whosemoduleis the item’s module.module_list— append the item’s module to a plain module list.scalar_module— set a single-module slot; the slot holds ONE module, so a second plugin claiming an occupied slot is a conflict the caller must reject loudly.package_list— append the plugin’s DISTRIBUTION name (not the item’s module) to a package list (studio_plugins).mcp_entry— append one{title: item.name, config: item.mcp}object to the manifestmcplist; uninstall removes the entry by title.descriptor_entry— append the item’sproviderdescriptor to the manifestconnectorslist; uninstall removes byprovider.id.env_selected— no manifest field: the kind is selected through the environment (aconfigprovider is named byTAI_CONFIG_MODEand imported by the config seam), sofieldisNone.
mcp_entry, descriptor_entry) carry payload == "data"; every other mode carries payload == "module".
Attributes
PluginItem
tai42_contract.plugins.item.PluginItem
provides index.
module is the import path whose import side-effect registers the item
(or, for env-selected kinds, the module the selecting seam imports); the
installer patches it into the manifest per KIND_MANIFEST_BINDINGS.
The item shape is table-driven on KIND_MANIFEST_BINDINGS (a model
validator enforces it, loud both ways): a data-payload kind carries exactly
the declarative block DATA_BLOCK_BY_KIND names for it — mcp
transport config for mcp-server, a provider descriptor for
connector — and no module; every module-payload kind carries
module and no data block. A connector item’s name MUST equal its
provider.id — that id is the manifest/uninstall key.
routes declares the item’s HTTP mount: REQUIRED for kind: router,
OPTIONAL for kind: channel, and FORBIDDEN (must be absent) for every
other kind.
group is an OPTIONAL logical family label the author may put on any item
of any kind: items sharing a value belong to one family, and a consumer may
summarize the provides index by group. It is purely a structural
self-description of the plugin — cross-kind groups and single-member groups
are both legal, and absent means the item stands alone. It carries a tag’s
charset discipline.
Attributes
PluginItemKind
tai42_contract.plugins.bindings.PluginItemKind
KIND_MANIFEST_BINDINGS maps
every member onto its manifest wiring; an unknown kind in a spec is a
loud validation reject, never a skipped row.
Attributes
PluginPermissions
tai42_contract.plugins.spec.PluginPermissions
False); an
unknown key is rejected loudly rather than silently ignored.
Attributes
PluginSpec
tai42_contract.plugins.spec.PluginSpec
tai-plugin.yml.
Frozen and extra="forbid": a typo’d key fails validation loudly. The
listing reference is namespace/name (ref); package is the
normalized pip distribution the listing points at, OPTIONAL because a spec
whose every item is a data item (mcp-server/connector) may ship no
package — a descriptor-only plugin that installs nothing but its manifest
entry. delivery derives the one word every surface shows
("package" when package is set, "descriptor" when absent); a
spec with any module item MUST name a package. version must equal the
built wheel’s version (each plugin repo’s spec test and the registry’s
ingest validation both pin that); contract is the tai42-contract
compatibility range as a PEP 440 specifier set, required unless every
provided item is kind mcp-server (such a package imports no contract),
and a spec may not mix mcp-server with any other kind. A connector
item’s provider.origin is system iff the listing namespace is
tai42 — a community listing may not claim the system label.
display_name and icon are optional marketplace display metadata;
premium marks a paid listing.
migrations is an OPT-IN, package-relative directory holding the plugin’s
ordered SQL schema chain — absent means the plugin owns no tables and is not
a migrations plugin (most plugins); it requires a package. The contract
validates only the path’s SHAPE; the directory’s existence in the installed
package is enforced at runner-discovery time, not here.
migrations_component optionally names the database component the chain
migrates, for a plugin whose feature store is a SEPARATE, off-unless-declared
component rather than the distribution itself — absent (the default) runs the
chain under the distribution name, byte-identical to prior behavior. Setting
it REQUIRES migrations (a component with no chain migrates nothing).
Attributes
Members
ref
tai42_contract.plugins.spec.PluginSpec.ref
namespace/name.
delivery
tai42_contract.plugins.spec.PluginSpec.delivery
"descriptor" when it ships no package, else "package".
A descriptor is an all-data, install-nothing listing.
RouteDecl
tai42_contract.plugins.routes.RouteDecl
path is /-prefixed with at least one segment; each segment is a
literal (ROUTE_LITERAL_SEGMENT_RE) or a {name} template
(ROUTE_PARAM_SEGMENT_RE, a python identifier with no converter
suffix). methods is a non-empty set of uppercase HTTP methods, unique
within the row. public states — with no default, so the declaration is
never silent — whether the resolved route answers unauthenticated.
Attributes
RouteMethod
tai42_contract.plugins.routes.RouteMethod
RoutesDecl
tai42_contract.plugins.routes.RoutesDecl
base and its rows.
base is RELATIVE — one or more ROUTE_BASE_SEGMENT_RE segments
joined by /, no leading/trailing slash, no templates. The resolved
absolute path of each row is /api/ + base + the row’s path; the
/api/ root is fixed platform-wide and only base is remappable.
paths is non-empty.
Attributes
data_kinds
tai42_contract.plugins.bindings.data_kinds
payload == "data".
These are the kinds a declarative (no-module) item is built for. Derived
from KIND_MANIFEST_BINDINGS so the payload table stays the one source.
