Plugins¶
Plugins are externally-loaded components that extend Missy's capabilities. Unlike tools (which are compiled into the codebase), plugins are loaded at runtime and gated behind a two-layer security check.
Security model¶
Plugins are disabled by default. Loading a plugin requires both:
plugins.enabled: truein config.- The plugin's name in
plugins.allowed_plugins.
Every load and execute attempt emits an audit event. Denied operations raise a PolicyViolationError.
Plugin permissions manifest¶
Every plugin must declare a PluginPermissions manifest listing all resources it needs:
from missy.plugins.base import BasePlugin, PluginPermissions
class WeatherPlugin(BasePlugin):
name = "weather"
description = "Fetches weather data from an external API"
version = "1.0.0"
permissions = PluginPermissions(
network=True,
allowed_hosts=["api.openweathermap.org"],
)
def initialize(self) -> bool:
# Validate API key, set up HTTP client, etc.
# Return True on success, False on failure.
return True
def execute(self, *, location: str = "") -> dict:
# Plugin logic here
...
Permission fields¶
| Field | Type | Default | Description |
|---|---|---|---|
network | bool | false | Outbound HTTP requests |
filesystem_read | bool | false | Read from filesystem |
filesystem_write | bool | false | Write to filesystem |
shell | bool | false | Execute shell commands |
allowed_hosts | list | [] | Specific hosts the plugin may contact |
allowed_paths | list | [] | Specific filesystem paths the plugin may access |
Creating a plugin¶
Step 1: Define the class¶
from missy.plugins.base import BasePlugin, PluginPermissions
class HomeAutomationPlugin(BasePlugin):
name = "home-automation"
description = "Controls smart home devices via Home Assistant"
version = "0.1.0"
permissions = PluginPermissions(
network=True,
allowed_hosts=["homeassistant.local:8123"],
)
def initialize(self) -> bool:
"""Called once when the plugin is loaded.
Use this to validate configuration, set up clients,
and verify connectivity. Return False to abort loading.
"""
self.ha_url = "http://homeassistant.local:8123"
self.ha_token = os.environ.get("HA_TOKEN")
return self.ha_token is not None
def execute(self, **kwargs) -> dict:
"""Called by the agent when using this plugin."""
action = kwargs.get("action", "status")
...
Step 2: Register¶
from missy.plugins.loader import get_plugin_loader
loader = get_plugin_loader()
loader.load_plugin(HomeAutomationPlugin())
Step 3: Enable in config¶
plugins:
enabled: true
allowed_plugins:
- "home-automation"
network:
tool_allowed_hosts:
- "homeassistant.local:8123"
Plugin lifecycle¶
- Policy check --
PluginLoaderverifiesplugins.enabledandallowed_plugins. - Initialize --
plugin.initialize()is called. ReturnsTrueon success. - Register -- Plugin is added to the internal registry with
enabled=True. - Execute -- Agent can invoke the plugin. Each call emits an audit event.
If initialize() returns False or raises an exception, the plugin is not registered.
Listing plugins¶
Shows all loaded plugins, their status, and version numbers.
Plugins vs. tools vs. MCP¶
| Aspect | Tools | Plugins | MCP Servers |
|---|---|---|---|
| Where they run | In-process | In-process | Separate process |
| Security gate | Permission declarations | Global enable + allowlist | Config file + digest pin |
| When to use | Core capabilities | Runtime extensions | External services |
| Audit granularity | Per-execution | Per-load + per-execution | Per-tool-call |