MESH-API is a Meshtastic mesh router + AI chatbot bridge. The core (mesh-api.py) runs a Flask web dashboard and a Meshtastic interface (serial/WiFi/BLE/TCP), routes slash commands and AI responses to the mesh, and loads plugins from extensions/.
For details, link to existing docs rather than duplicating them:
- README.md — project overview, features, config reference.
- DEVELOPING_EXTENSIONS.md — step-by-step extension guide + API reference.
- EXTENSIONS.md — reference for all built-in extensions and their config.
- Install:
pip install -r requirements.txt - Run locally:
python mesh-api.py→ dashboard athttp://localhost:5000/dashboard - Docker:
docker compose up -d - No automated test suite, linter, or formatter is configured. Verify changes by running the app.
- Core mesh-api.py: Flask server + Meshtastic interface, built-in slash commands, AI provider integration, message chunking, emergency broadcasts.
- Multi-radio (v0.7.0+): MeshCore is a first-class, core-owned radio, not a plugin.
meshcore_core.py (
MeshCoreManager) owns the MeshCore connection (serial/TCP/BLE) on its own asyncio thread and feeds inbound messages into the same network-agnostic pipeline as Meshtastic (notify_extensions_inbound→route_and_respond→dispatch_response). This lets commands, AI, and every plugin work on either network, and lets the user run Meshtastic-only, MeshCore-only (standalone viameshtastic_enabled:false), or both. The oldextensions/meshcorebridge plugin is deprecated and defers to the core. - Extension system extensions/: auto-discovered plugins. The loader
(extensions/loader.py) scans
extensions/at startup, skips folders starting with_, and instantiates every BaseExtension subclass. - MCP server (v0.7.0+) mcp_server.py (
MCPServer): exposes core functions and extensions as MCP tools over a Streamable-HTTP / JSON-RPC 2.0 Flask endpoint atPOST /mcp(no async deps — fits the sync Flask core). Core tools (mesh_send_message,mesh_list_nodes,mesh_run_command, etc.) are built fromapp_context/providers; every extension slash command is auto-exposed as anext_cmd_*tool, and extensions may add richer typed tools via optionalget_mcp_tools()+call_mcp_tool()methods (duck-typed, no framework edit needed). Disabled by default; bearer-token auth + gated emergency tool. Config:mcpblock. - Messaging: event-driven receive via Meshtastic pubsub callbacks; extensions use background
threads (
threading) for polling. Noasync/awaitanywhere. Outbound messages >chunk_size(default 200 chars) are split at word boundaries with a delay between chunks.
- Copy extensions/_example/ to
extensions/<name>/(lowercase, underscores, valid Python identifier — no hyphens). Keep the layout:__init__.py,config.json,extension.py. - In
extension.py, subclassBaseExtensionand define requirednameandversionproperties. Override only the hooks you need:on_load,on_unload,handle_command,send_message,receive_message,on_message,on_emergency,register_routes. - In
config.json, set"enabled": trueplus any custom keys. - Restart MESH-API (the loader auto-discovers it). Check EXTENSIONS.md first to avoid duplicating an existing extension.
- Do not modify base_extension.py or extensions/loader.py when adding features — extend, don't edit the framework.
- Never override
__init__in an extension; the loader constructs it with(extension_dir, app_context). - Use
self.log(...)(notprint) for log capture; read config viaself.config.get(key, default). - Persist config changes with
self._save_config()— mutatingself._configalone does not save. - Use mesh helpers instead of hardcoding hardware:
self.send_to_mesh(...), orself.app_context["interface"]/send_broadcast_chunks/send_direct_chunks. handle_command()returns a string response, orNoneif it does not handle the command.- Background threads must stop cleanly in
on_unload(); exceptions inon_load()are caught and logged by the loader (the extension just won't activate) — check the log. - Config files are not reloaded mid-run; changes require a restart (or WebUI restart).
commands_config.jsonmust keep its top-level{"commands": [...]}shape.- The AI command alias (
ai_commandin config.json) is a randomized suffix (e.g./ai-9z); it regenerates on startup if it doesn't match the expected pattern.