Manifest System
Every MaiBot plugin must include a _manifest.json file in its root directory to declare the plugin's metadata, version compatibility, dependencies, and capability requirements. The ManifestValidator on the Host side strictly validates this file before loading.
Plugin metadata and runtime configuration
_manifest.json: Declares the plugin ID, version, dependencies, and capabilities and is validated and managed by the Hostconfig_modelinplugin.py: Declares the configuration structure, defaults, and WebUI metadataconfig.toml: Stores the current installation's runtime configuration and is generated and maintained by the Runner fromconfig_model
_manifest.json Structure
Below is a complete Manifest example:
{
"manifest_version": 2,
"id": "com.example.my-plugin",
"version": "1.0.0",
"name": "My Plugin",
"description": "An example plugin",
"author": {
"name": "Developer",
"url": "https://github.com/developer"
},
"license": "MIT",
"urls": {
"repository": "https://github.com/developer/my-plugin",
"homepage": "https://example.com",
"documentation": "https://docs.example.com",
"issues": "https://github.com/developer/my-plugin/issues"
},
"host_application": {
"min_version": "1.0.0",
"max_version": "1.99.99"
},
"sdk": {
"min_version": "1.0.0",
"max_version": "2.99.99"
},
"dependencies": [],
"plugin_type": "tool",
"display": {
"icon": {
"type": "lucide",
"value": "wrench"
}
},
"capabilities": ["send.text", "send.emoji", "config.get"],
"i18n": {
"default_locale": "zh-CN",
"locales_path": "i18n",
"supported_locales": ["zh-CN", "en-US"]
}
}Required Fields
manifest_version2— Manifest protocol version, currently fixed at2idstring— Unique plugin identifier, formatted as lowercase letters/numbers, separated by dots or hyphens (e.g.,com.author.plugin)versionstring— Plugin version number, must be a strict three-part semantic version (e.g.,1.0.0)namestring— Plugin display namedescriptionstring— Plugin descriptionauthorobject— Plugin author information, containingname(author name) andurl(author homepage, must be an HTTP/HTTPS URL)licensestring— Plugin licenseurlsobject— Collection of plugin-related links (see below)host_applicationobject— Host compatibility range (see below)sdkobject— SDK compatibility range (see below)capabilitiesstring[]— List of capability requests declared by the plugin; empty values are not allowed. Capability names are per item (such assend.text,emoji.get_random,config.get) and the Host matches them exactly: you may only call what you declared, and an undeclared call is rejected with "capability not granted". See the capability groups in the API Reference.i18nobject— Internationalization configuration (see below)
Optional Fields
plugin_type Plugin Type
plugin_type is used to declare the primary role of the plugin, for WebUI display, filtering, and default icon selection. This field is optional and does not require upgrading manifest_version; if omitted, it defaults to extension.
Possible values:
adapter— Message platform or protocol adaptertool— Tools, commands, or model-callable capabilitiesprovider— LLM, TTS, API, or other service providersmanagement— Management, permissions, group administration, or backend pluginsdata— Statistics, memory, knowledge base, import/export, and other data-related pluginsmedia— Image, voice, video, emoji, and other media processinggame— Games or entertainment interactionsintegration— External platforms, search, Webhooks, and other integrationsextension— General extensionsother— Other
display Display Metadata
display.icon is used to declare the plugin icon. This field only affects WebUI display and does not participate in plugin runtime behavior.
{
"display": {
"icon": {
"type": "local",
"value": "assets/icon.png",
"fallback": "package",
"background": "#1f2937"
}
}
}type:lucide,emoji, orlocalvalue: Icon value.lucideuses the icon name,emojiuses a single emoji or short text,localuses a relative path within the plugin directoryfallback: Optional, the lucide icon name to use if the icon fails to loadbackground: Optional, the icon background color in#RRGGBBformat
Online URLs are not allowed as plugin icons. Local icons only support .png, .jpg, .jpeg, .webp, and .svg. Paths must be within the plugin directory; absolute paths, .., or symbolic links are not permitted.
urls Link Collection
repository· Required — Plugin repository URL, must be an HTTP/HTTPS URLhomepage· Optional — Plugin homepage URLdocumentation· Optional — Plugin documentation URLissues· Optional — Plugin issue reporting URL
host_application / sdk Version Range
Both share the same structure, declaring a closed interval:
{
"min_version": "1.0.0",
"max_version": "1.99.99"
}min_version: Minimum allowed version (inclusive)max_version: Maximum allowed version (inclusive)- Both must be strict three-part semantic version numbers (
X.Y.Z) and cannot be empty min_versioncannot be greater thanmax_version
When and how it is checked: both ranges are validated by the Runner through ManifestValidator when the plugin loads. The Host passes its own version via MAIBOT_HOST_VERSION; the SDK version is whatever maibot-plugin-sdk is actually imported in the Runner's environment. During the handshake (runner.hello) the Host only checks that the Runner's own SDK version falls inside the fixed range [1.0.0, 2.99.99] — that is a separate check from the sdk range declared in the manifest.
The two failure modes are not symmetric:
- Host out of range — if the current Host is above
max_versionbut shares its major and minor version, the plugin still loads with a warning only (patch-level tolerance); every other mismatch is recorded as an error and the plugin is blocked - SDK out of range — always recorded as an error regardless of direction, and the plugin is blocked
Do not pin max_version to a patch release
Writing max_version as a concrete patch release (for example 1.2.3) means your plugin stops loading as soon as MaiBot ships a new version. The official built-in plugins set the upper bound to 999.999.999 and constrain only min_version seriously, letting real testing decide whether a new version still works; write 0.0.0 when you do not want a lower bound.
{
"host_application": {
"min_version": "1.3.0",
"max_version": "999.999.999"
},
"sdk": {
"min_version": "1.0.0",
"max_version": "999.999.999"
}
}i18n Internationalization Configuration
default_locale· Required — Default language code (e.g.,zh-CN)locales_path· Optional — Path to the language resource files directorysupported_locales· Optional — List of supported languages; must not contain empty values or duplicates. If non-empty,default_localemust exist in this list
llm_providers LLM Provider Declaration
Declares the LLM Provider capabilities provided by the plugin, for proxy invocation by other plugins via ctx.llm.
client_type· Required — Unique identifier for the Provider, must exactly match the value declared in the@LLMProviderdecoratorname· Required — Display name for the Providerdescription· Optional — Functional description of the Providerversion· Optional · Default"1.0.0"— Version number of the Provider
Dual Declaration Requirement
The llm_providers field and the @LLMProvider decorator must both be declared, and client_type must match exactly. If declared in only one place, or if one is missing or inconsistent, the plugin will be prevented from loading.
Conflict Loading Policy
If two plugins declare the same client_type, both plugins will be prevented from loading. Please use a unique prefix (e.g., com.example.my-provider) when designing Providers to avoid conflicts.
{
"llm_providers": [
{
"client_type": "my_custom_llm",
"name": "My Custom LLM",
"description": "A custom LLM provider",
"version": "1.0.0"
}
]
}Dependency Declaration
The dependencies array supports two types of dependencies, distinguished by the type field:
Plugin-Level Dependencies
{
"type": "plugin",
"id": "com.example.other-plugin",
"version_spec": ">=1.0.0,<2.0.0"
}id: The ID of the dependent plugin, following the same formatting rules as plugin IDsversion_spec: Version constraint expression, using PEP 440 style (e.g.,>=1.0.0,~=1.0)- Circular dependencies or dependencies on oneself are not allowed
- Declaring the same plugin dependency multiple times is not allowed
Python Package Dependencies
{
"type": "python_package",
"name": "httpx",
"version_spec": ">=0.24.0"
}name: Python package name; only letters, numbers, dots, underscores, and hyphens are allowedversion_spec: Version constraint expression
Dependency Resolution Process
PluginDependencyPipeline performs dependency analysis uniformly on the Host side:
- Scanning: Collect
_manifest.jsonfrom all plugins - Host Conflict Detection: If a plugin's Python package dependency has no intersection with the main program's dependency constraints, loading is blocked
- Inter-Plugin Conflict Detection: If multiple plugins have mutually exclusive version constraints for the same Python package, all are blocked from loading
- Automatic Installation: For missing Python dependencies of loadable plugins, the index order and constrained dependencies in the main program's
pyproject.tomlunder[tool.uv]are read, and installation is attempted one source at a time — any single source succeeding is enough. The main program'sconstraint-dependenciesare passed explicitly to the install command as a temporary constraint file. If every source fails, the error lists the failure summary of each source - Topological Sorting: Determine the Runner startup order based on cross-Supervisor dependency relationships; circular dependencies will be rejected
The index order follows uv's own semantics: indexes that are not default = true come first in declaration order, with default = true last. The uv branch appends --no-config when an index is specified — uv merges indexes from pyproject.toml with command-line indexes, and only this flag restricts the install to that one source. Plugin dependencies declaration rules (type / id / version_spec / name) are unaffected; keep writing them as before.
Validation Rules
The Manifest Validator (ManifestValidator) adopts Pydantic strict mode. The main validation rules include:
- No Extra Fields: Fields not declared in
_manifest.jsonare not allowed. - ID Format: Must match
^[a-z0-9]+(?:[.-][a-z0-9]+)+$(e.g.,com.example.my-plugin). - Version Format: Must be a three-part
X.Y.Zformat. - URL Format: Must start with
http://orhttps://. - No Self-Dependency: The plugin cannot depend on itself in
dependencies. - No Duplicate Dependencies: Each plugin/package name can only be declared once.
Version Range Rules
Both host_application and sdk are judged by the same validation entry point, with results split into error and warning tiers:
- Host above
max_versionwith the same major.minor — warning; the plugin loads normally (patch-level tolerance) - Host out of range in any other case — error; the plugin is blocked from loading
- SDK out of range — error; the plugin is blocked, with no tolerance tier
The handshake-time check of the Runner's own SDK version (fixed range [1.0.0, 2.99.99]) is independent of the two tiers above: it is not declared by the manifest and is not affected by "Force Plugin Compatibility".
Force Plugin Compatibility
With the main-program config [debug] force_plugin_compatibility (the "Force plugin compatibility" switch in the WebUI debug settings) enabled, both the Host and the Runner skip the host_application and sdk range checks: out-of-range plugins are no longer recorded as errors, only a warning that includes the declared ranges and the current Host / SDK versions.
- Requires restarting MaiBot — the Host encodes the switch into the
MAIBOT_FORCE_PLUGIN_COMPATIBILITYenvironment variable for the Runner; editing the config does not hot-update an already running Runner - Skips version ranges only —
manifest_version, the fixed SDK range at handshake, dependency resolution, the capability whitelist, andllm_providersconsistency are all still enforced - Does not affect the Plugin Market — version-index compatibility checks do not read this switch; the "incompatible" labels in the market and the version dropdown are still computed from the declared ranges
Treat it as a temporary tool for answering "is a version number blocking this plugin?" rather than a permanent setting: with the check skipped, runtime failures caused by interface changes come with no upfront signal.
Verify and Troubleshoot
Verification — confirm the JSON parses with the command below, then drop _manifest.json into the plugin directory and reload: the WebUI plugin page shows the plugin as loaded rather than blocked, and any rejection is named in the Runner log by ManifestValidator.
# Run inside the plugin directory: a stray comma or comment fails right here
python -m json.tool _manifest.jsonextra fields not permitted— the validator runs Pydantic in strict mode and rejects any field outside the declared structure: remove custom comment or scratch fields.- Format errors on
id,version, orauthor—idmust match^[a-z0-9]+(?:[.-][a-z0-9]+)+$(lowercase, at least one.or-),versionand both range bounds must be strictX.Y.Z, andauthormust be a{ name, url }object whoseurlstarts withhttp://orhttps://. - The plugin is blocked as version-incompatible — a Host mismatch is an error unless major and minor match (patch-level tolerance), and an SDK mismatch is always an error; set
max_versionto999.999.999and constrainmin_versionseriously, or enable "Force plugin compatibility" for triage — it needs a MaiBot restart and skips version ranges only. - A local
display.icondoes not render or is rejected — withtype="local",valuemust be a relative path inside the plugin directory ending in.png,.jpg,.jpeg,.webp, or.svg; online URLs are never allowed, and absolute paths,.., and symbolic links are refused. A failed load falls back tofallback. - The log reports a dependency or
llm_providersconflict — no self-dependency, duplicate declaration, or circular dependency, and eachclient_typemust match its@LLMProviderdeclaration exactly; when two plugins declare the sameclient_type, both are blocked.