Packaging and installing¶
A Python plugin is a folder with a plugin.json and a Python module.
You can install one two ways: package it as a .mteplugin archive
and use the editor's installer (recommended), or drop the folder into a
plugins directory by hand.
Start from the template
A ready-to-copy starter lives at
Python/templates/plugin-template-python/ in the repo — three
files (plugin.json, main.py, README.md) plus a
Claude-facing CLAUDE.md. Rename the folder to your plugin's
short name, edit four fields in plugin.json, and start writing
register(ctx). The installed SDK ships the same template under
share/MTEPluginSDK/plugin-template-python/.
Plugin folder layout¶
Everything the plugin needs must sit inside this folder. Sub-imports
work but must be flat — dotted module names in plugin.json are a
future extension.
You may add extra files (README, LICENSE) — the host ignores what it doesn't recognise.
.mteplugin packages (recommended)¶
Zip the plugin's files and give the archive the .mteplugin extension
(a plain .zip also works). Both shapes are accepted:
hello.mteplugin hello.mteplugin
├── plugin.json └── hello-1.0.0/ # any folder name
└── main.py ├── plugin.json
└── main.py
The single wrapper folder — the natural result of zipping a folder — is
normalised away: the plugin always installs to <Plugins>/<id>/, keyed
by the manifest id, never by the archive's folder name. That is also
why installing version 2 of a plugin replaces version 1 instead of
sitting next to it.
Install it via Preferences → Plugins → Install… and restart the editor.
Only install packages you trust
The installer checks that a package is structurally sound (see below) —
it does not make an untrusted plugin safe to run. Once loaded, a plugin
runs with the same access to your files, settings and network as the
editor, and is not sandboxed. The static register(...) probe runs no
code at install time, but the plugin's code does run on the next launch.
Treat a .mteplugin (and any config or JSON files it ships) like an unknown
program: install it only from a source you trust. See
Plugins for the full warning.
What the installer validates¶
The package is fully validated against a throwaway copy before anything on disk is touched — a bad package can never disturb an existing install:
plugin.jsonparses, has anid,apiVersionmatches the editor,entryis"python", andmoduleis present.- The archive listing is safe: no absolute or
..entry paths, at most 10 000 entries, at most 256 MiB unpacked, and at least one.pyfile. - After extraction to a scratch directory, the declared
<module>.pyexists and declares a top-leveldef register(...)— the same static probe discovery uses. No Python code is executed at install time.
Any failure produces a human-readable error dialog and leaves the editor's plugins untouched.
Updating and uninstalling¶
- Installing a package whose
idis already installed replaces it. If that plugin is currently loaded, the update is staged and applied on the next launch. - Uninstall from the same Preferences page; removal also happens at the next launch, while nothing is loaded.
- Bundled (built-in) plugins cannot be overridden by an installed
package with the same
id.
Installing by hand (folder drop)¶
The editor discovers plugins in two directories on every startup:
- Bundled — next to the executable, at
<exe-dir>/Plugins/. Built-in plugins (e.g.HelloPython) live here. - User — per-user, writable. On Windows this is
%LOCALAPPDATA%/MTE/Plugins/, on macOS~/Library/Application Support/MTE/Plugins/, on Linux~/.local/share/MTE/Plugins/.
Both directories are scanned. If the same id appears in both, the
first occurrence wins (bundled takes precedence). Restart the editor
to pick up new folders — hot reload is not supported.
Two acceptable layouts¶
The scan looks for plugin.json either directly in the plugins
directory (loose) or inside one level of immediate subdirectory
(per-plugin folder):
Plugins/
├── plugin.json # loose -- one plugin at the top level
├── main.py
└── hello/ # per-plugin folder -- one plugin
├── plugin.json
└── main.py
The per-plugin folder is the recommended layout; it's the shape the installer produces.
Dependencies (pyRequires)¶
plugin.json may declare pip requirements as an inline array:
On the plugin's first launch the editor provisions a virtual
environment at <AppLocalData>/PluginVenv/<id>/ (python -m venv +
pip install, which needs network access that one time) and runs the
plugin's worker under that venv's interpreter. Subsequent launches
reuse it; changing pyRequires re-provisions automatically (the venv
carries a stamp of the requirements it was built for).
- Provisioning failures (no network, a typo'd requirement) leave the plugin listed but not loaded, with the pip error in the log; the next launch retries.
- Uninstalling a plugin removes its venv along with the plugin folder
(a venv is derived data — it is rebuilt from
pyRequiresif you ever reinstall).
Offline / locked-down machines¶
Ship a pre-built venv inside the package as a venv/ folder at the
plugin root. When present it wins over everything — no pip ever runs.
Build it on a machine with the same OS/architecture, since venvs are
not portable across platforms.
Vendoring (small pure-Python deps)¶
For a single pure-Python module, vendoring inside the plugin folder still works and avoids the venv machinery entirely:
# main.py
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "vendor"))
import your_dep
Declared permissions¶
plugin.json may carry a permissions array (e.g. ["network"]).
The Plugins Preferences page shows it in a read-only Permissions
column so users can see what a plugin declares about itself. It is a
declaration, not an enforcement — sandboxing is future work.
Where writable data goes¶
The plugins directory holds code. Runtime data (caches, per-user
configuration, indexes) go to a separate per-plugin data directory the
host manages — ctx.data_dir() returns its absolute path. Do NOT
write into the plugins directory from your .py at runtime; uninstall
removes the whole <Plugins>/<id>/ folder, and writes made elsewhere
in the plugins tree are never cleaned up.
Deploying to other machines¶
Ship the .mteplugin file — it is self-contained and validated on the
receiving end. A zip/tarball/git-clone of the folder into the per-user
plugins directory works too.
Enable / disable¶
The Plugins Preferences page lists every discovered plugin (native + Python, together) with the same origin/status columns. Disabling a plugin keeps it listed but prevents its factory from being invoked at startup — no worker is spawned. Re-enable and restart to bring it back.