Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time.
Manage them from **Settings → Plugins**. Disable every packaged plugin at startup with `--disable-plugins` or `MESHCHAT_DISABLE_PLUGINS=true`.
## What plugins can do
- Add a row on the **Tools** page
- Add an item in the main **Navigation** sidebar
- React to mesh events (announces, RNS link traffic)
- Keep a private key-value store (`storage: isolated`)
- Optionally fetch clearnet HTTP (`network: fetch`), still subject to **Privacy mode**
Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below).
Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not.
After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered.
## Bundled example: Bug Reports
`com.meshchatx.mcx-bugs` ships with MeshChatX. It adds a **Bug Reports** tool for sending redacted debug logs to an `mcx-bugs-v1` collector, or running a collector yourself.
Layout:
```
mcx-bugs/
plugin.json
frontend/main.js
backend/main.py
locales/en.json
```
Use it as the reference package when building your own.
## Manifest (`plugin.json`)
Every packaged plugin needs a root `plugin.json`.
```json
{
"id": "com.example.my-plugin",
"version": "1.0.0",
"apiVersion": 1,
"name": "My Plugin",
"description": "Adds a custom tool.",
"frontend": {
"entry": "frontend/main.js",
"type": "js"
},
"backend": {
"entry": "backend/main.py",
"type": "python"
},
"i18n": {
"directory": "locales",
"defaultLocale": "en"
},
"contributes": {
"navItems": [
{
"id": "my-plugin",
"route": { "name": "plugin-my-plugin" },
"icon": "puzzle",
"labelKey": "nav"
}
],
"toolsPageEntries": [
{
"name": "my-plugin",
"route": { "name": "plugin-my-plugin" },
"icon": "puzzle",
"titleKey": "title",
"descriptionKey": "description"
}
]
},
"permissions": {
"hooks": ["announce.received"],
"managers": ["destinationPath.read"],
"storage": "isolated",
"network": "none"
}
}
```
Notes:
-`id` is reverse-DNS style and must stay stable across versions
-`apiVersion` is currently `1`
- Plugin strings live in the plugin bundle (`locales/{locale}.json`), not core `en.json`
-`contributes` wires UI slots through the frontend registries
## Permissions
Nothing is available unless it is declared in the manifest and granted in the install dialog.
| `destinationPath.read` | Read the Reticulum path table |
| `debugLog.read` | Read redacted debug logs |
| `bugReport.*` | Bug report / collector APIs |
| `rnsLink.open` | Open or reuse an RNS link |
| `rnsLink.identify` | Identify on a cached link |
| `rnsLink.request` | Request/response on a link |
| `rnsLink.send` | Send a raw link packet |
| `rnsLink.close` | Tear down a cached link |
Call managers from a plugin with `POST /api/v1/plugins/{id}/invoke` and `method: "callManager"`. Details for the link transport are in [RNS Link API](rns-link-api.md).
Legacy Sideband-style flat `*.py` files are separate from packaged ZIP/WASM plugins.
```
Settings → Plugins → Sideband
|
--> Confirm danger prompt
|
--> Set directory of *.py files
|
--> Optional filename.py.rsg next to each script
|
--> Reload
```
These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory.
## Operator tips
- Prefer signed packages from publishers you added yourself