Plugins are how you extend Koris without touching its core. There are three plugin
families — channels, tools, and MCP servers —
that share the same shape and the same kernel, described below. plugins/ also holds
skills, but skills are a different kind of extension — plain Markdown,
synced by SkillSyncService rather than the kernel this page describes.
plugins/registry.ts holds the family-agnostic core: ExtensionPoint,
PluginRegistry, and buildRegistry. All three families build on it. At startup
core/src/app.ts builds one shared PluginRegistry from the channel plugins,
tool plugins, and MCP server plugins together.
A plugin imports only from its own family's contracts.ts and the shared
plugins/registry.ts. It never imports from core/src/, and never from another
family's contracts.ts.
Core depends on the SDKs and injects concrete services the other way, through a
PluginContext (channels), ToolPluginContext (tools), or McpPluginContext (mcps)
assembled at the composition root, core/src/app.ts. Each plugin folder exposes
a create(context) function that receives that context.
The one documented exception is plugins/tools/create-tool/, which reaches into
scripts/scaffold-tool.ts to generate new tool folders.
The scanner loads every subdirectory of a family folder — plugins/channels/,
plugins/tools/, and plugins/mcps/. Files that sit directly in those folders,
such as contracts.ts, are skipped. Adding a plugin means adding a folder; for
tools, pnpm scaffold:tool <name> creates one for you. No core changes are
required. (plugins/skills/ sits alongside these, but isn't scanned here — see
Skills for how it's synced.)
Enablement lives in the database, in the plugin_settings table: family, name, and
enabled, with a primary key of (family, name). resolvePluginEnabled reads the row
and falls back to a code-level default when no row exists yet.
Toggling is live — no restart. PluginCatalogSingleton keeps every registered
{ family, name } pair so the admin API can list plugins without rescanning disk. See
the admin dashboard for the Plugins panel.
Channel and MCP plugins still keep their secrets and endpoint configuration in
each folder's config.yml; only the enabled flag moved to the database.