Home
Softono
mesh-api

mesh-api

Open source Python
151
Stars
17
Forks
1
Issues
9
Watchers
3 months
Last Commit

About mesh-api

MESH-API (previously MESH-AI) — Off-Grid AI & API Router with over 30 API extensions for Meshtastic & MeshCore - Seamlessly connect LM Studio, Ollama, AI Providers , 3rd-party APIs, & Home Assistant to your LoRa mesh. Supports custom commands, Twilio SMS, Discord channel routing, & GPS emergency alerts via SMS, email, or Discord + SO MUCH MORE

Platforms

Web Self-hosted

Languages

Python

MESH-API v0.7.3.7 Beta — Meshtastic + MeshCore Mesh Router & API / AI Bridge

🎉 Now with full MeshCore support (since v0.7.0)

You can run MESH-API with EITHER a Meshtastic node, a MeshCore node — or BOTH at the same time, with MESH-API handling cross-network routing between them. MeshCore is a first-class, core-owned radio on equal footing with Meshtastic. Slash commands, the AI assistant, and every extension work across both networks, and the WebUI adapts to whichever radios you have connected.

⚠️ These features are widely untested and I am actively seeking community feedback. If you run a Meshtastic-only, MeshCore-only, or dual-radio setup, please tell me what works, what breaks, and what you'd like to see — open a GitHub Issue. Your real-world reports directly shape the MeshCore implementation going forward.

Also included: a built-in MCP (Model Context Protocol) server that turns MESH-API into an agentic backend for AI tools, and a firmware/software update system. See the dedicated sections below.

🆕 What's new

v0.7.3.7 is a bug-fix release: all extensions now route to MeshCore. Messages and AI replies pushed to the mesh from any extension (Telegram, Discord, Home Assistant, alert feeds, etc.) now reach every connected radio — Meshtastic and MeshCore, not just Meshtastic (GitHub #59). The prior v0.7.3.6 added a token-free heartbeat for named AI endpoints — each endpoint shows a live connection-status dot (🟢/🟡/🔴) in the 🔌 Manage AI Endpoints panel, polled via a cheap /models ping that never spends AI tokens.

📜 See the full version history in CHANGELOG.md.

❤️ Support MESH-API Development — Keep the Lights On

MESH-API is built and maintained by one developer with the help of AI tools. There is no corporate sponsor, no VC funding — just late nights, community feedback, and a passion for off-grid communication.

If MESH-API has been useful to you — whether you're running it on a Raspberry Pi in your go-bag, bridging your local mesh to Discord, or experimenting with AI on LoRa — please consider making a donation. Every contribution, no matter the size, directly fuels continued development, bug fixes, new extensions, and keeping this project free and open-source for everyone.

Donate via PayPal (Preferred)

Donate via PayPal

Click here to donate via PayPal

Crypto Donations

Currency Address
BTC bc1qalnp0xze5t9nner2754k2pj7yjhkrt3uzvzdvt
ETH 0xAd640c506f5d2368cAF420a117380820C0C5F61C
XRP rpciwKrQSaRZ1UjPunH8vLJhoM2s4NaYoL
DOGE DM79aRx58J6RYuWakHjiELWbNJkTTDj1cv

Thank you to everyone who has donated, filed issues, tested pre-releases, and spread the word. You are what makes this project possible. 🙏

MESH-API

MESH-API is an experimental project that bridges Meshtastic (& now MeshCore ) LoRa mesh networks with powerful AI chatbots and 3rd party APIs.

What Sets MESH-API Apart?

Most projects in this space stop at being "AI chatbot integrations" — but MESH-API is much more than that.

  • Full Router / Mesh Operator
    MESH-API isn’t just talking to an LLM. It’s a protocol bridge and mesh backbone, designed to let LoRa networks, online (or offline) services, and APIs talk to each other in real time.

  • Not a One-Trick Pony
    Where other tools simply connect to AI, MESH-API is built to route, translate, and post messages between different systems and services — making it a true hub for both on-grid and off-grid communication.

  • Expandable by Design
    Any software with a working API can be integrated. That means you can merge in external services, dashboards, or automation platforms, extending the mesh far beyond its original scope.

  • AI-Powered Off-Grid Networks
    MESH-API provides the foundation for self-sufficient LoRa mesh networks enhanced with AI, ensuring communication, automation, and decision-making remain possible — even without the internet.

In short, MESH-API bridges the gap between mesh services and online/locally hosted services, making it a powerful backbone for resilient, intelligent LoRa networks.

Disclaimer:
This project is NOT ASSOCIATED with the official Meshtastic Project. It is provided solely as an extension to add AI and advanced features to your Mesh network.

v0.7.3.7 Beta:
The 0.7.x line makes MeshCore a first-class radio and adds the MCP server and firmware-update system; v0.7.3.7 ensures every extension routes outbound messages to MeshCore as well as Meshtastic. These features are still relatively untested in the field — I am actively seeking community feedback. Run it with a Meshtastic node, a MeshCore node, or both, and please report what works and what breaks. Avoid relying on it for mission‑critical tasks or emergencies; always keep backup communication methods available and use responsibly.

I am one robot using other robots to write this code. Some features are still untested in the field. Check the GitHub issues for fixes or feedback!


image The Meshtastic logo trademark is the trademark of Meshtastic LLC.

Features

  • Multi-Radio: Meshtastic, MeshCore, or Both (New in v0.7.0)
    • MeshCore is a first-class, core-owned radio (not an extension) on equal footing with Meshtastic, connecting over serial / TCP / BLE.
    • Run a Meshtastic radio only, a MeshCore radio only (fully standalone — no Meshtastic device required), or one of each with MESH-API as a cross-network man-in-the-middle.
    • Slash commands, the AI assistant, and every extension work across both networks. Send to one network or both at once.
    • WebUI adapts — per-network connection banner, node network filter with collapsible Meshtastic/MeshCore sections, network badges on nodes & messages, distinct map markers, MeshCore group/private channels in the send form, and DMs to MeshCore contacts.
  • MCP (Model Context Protocol) Server (New in v0.7.0)
    • Exposes MESH-API core functions and extensions as MCP tools at POST /mcp, so external AI agents (Claude, Perplexity, Hermes, custom) can drive the mesh as an agentic backend. Disabled by default; bearer-token auth. See MCP Server.
  • Firmware & Software Updates (New in v0.7.0)
    • Detects your connected Meshtastic/MeshCore device and notifies you (🔄 Updates badge) when newer Meshtastic firmware, MeshCore firmware, or MESH-API is available, with stable / beta / alpha release-channel selection. Optional ESP32 over-USB flashing (off by default).
  • Plugin-Based Extensions System (New in v0.6.0)
    • 30 built-in extensions across 7 categories: Communication, Notifications, Emergency/Weather, Ham Radio/Off-Grid, Smart Home, Mesh Bridging, and AI Agents.
    • Drop-in plugin architecture — add or remove extensions by copying a folder. No core code changes required.
    • Extensions can register slash commands, react to emergencies, observe messages, expose HTTP endpoints, and run background services.
    • WebUI Extensions Manager — view, enable/disable, and configure extensions from the dashboard.
    • See the Extensions Reference section below for full details on all built-in extensions, or Developing Custom Extensions to build your own.
  • Multiple AI Providers
    • Support for Local models (LM Studio, Ollama), OpenAI, Claude, Gemini, Grok, OpenRouter, Groq, DeepSeek, Mistral, Hermes (Nous Research), generic OpenAI-compatible endpoints, and Home Assistant integration.
  • Home Assistant Integration
    • Seamlessly forward messages from a designated channel to Home Assistant’s conversation API. Optionally secure the integration using a PIN.
  • NASA Space Weather Monitoring
    • Track geomagnetic storms, solar flares, coronal mass ejections, and more via NASA's DONKI API. Auto-broadcast significant events to the mesh with configurable Kp index and flare class thresholds. Slash commands: /spaceweather, /solarflare, /geomagstorm.
  • n8n Workflow Automation
    • Bidirectional bridge with n8n — forward mesh messages and emergencies to n8n webhook triggers, receive workflow outputs on the mesh, list active workflows, and trigger them via slash commands. Enables powerful no-code automation pipelines for your mesh network.
  • Advanced Slash Commands
    • Built‑in commands: suffixed /about-XY, /help-XY, /motd-XY, /whereami-XY, /nodes-XY, AI commands with your unique suffix (e.g., /ai-XY, /bot-XY, /query-XY, /data-XY), unsuffixed /test, and unsuffixed /emergency (or /911), plus custom commands via commands_config.json.
    • Commands are now case‑insensitive for improved mobile usability.
    • New: a per-install randomized alias (e.g. /ai-9z) is generated on first run to reduce collisions when multiple bots exist on the same mesh or MQTT network. You can change it in config.json (field ai_command). All AI commands require this suffix, and other built‑ins (except emergency/911) also require your suffix.
    • Strongly encouraged: customize your commands in commands_config.json to avoid collisions with other users.
  • Emergency Alerts
    • Trigger alerts that are sent via Twilio SMS, SMTP Email, and, if enabled, Discord.
    • Emergency notifications include GPS coordinates, UTC timestamps, and user messages.
  • Enhanced REST API & WebUI Dashboard
    • A modern three‑column layout showing direct messages, channel messages, and available nodes. Stacks on mobile; 3‑wide on desktop. Controls (⌘ Commands, 🧩 Extensions, ⚙️ Config, 📜 Logs) live in the “Send a Message” header (top‑right).
    • Interactive Node Map — Leaflet.js‑powered map view with markers for all GPS‑enabled nodes. 25 tile providers including OpenStreetMap, Carto (Light, Dark, Voyager, No‑Label variants), OpenTopoMap, Esri (Street, Satellite, Topo, NatGeo, Light/Dark Gray Canvas, Ocean), Stadia (Stamen Terrain/Toner/Toner Lite/Watercolor, Alidade Smooth/Dark, Outdoors, OSM Bright), Humanitarian OSM, CyclOSM, and OPNVKarte. Defaults to Carto Positron (Light). Offline map image support — upload a local map image with lat/lon bounds via settings; Leaflet overlays it as a fully functional map layer with markers, pan, and zoom. Offline detection with fallback notice. Popups include node name, custom name, favorite star, ID, last heard, hop count, DM/PING/PONG buttons, and Google Maps link — all on a single row. Mini DM box over the map includes Send, PING, and PONG on the same row.
    • Collapsible Channel Groups — Each channel is a toggle‑able group with an unread‑count badge. Click the 📻 header to expand/collapse.
    • Draggable Dashboard — All major sections (Send Form, Node Map, Message Panels, Discord) can be reordered via ☰ drag handles. The three message columns (DMs, Channels, Nodes) are also independently sortable. Layout order is saved to localStorage. Sections can be hidden/shown from the UI Settings panel.
    • Notification Sounds — Five built‑in Web Audio API sounds (Two‑Tone Beep, Triple Chirp, Alert Chime, Sonar Ping, Radio Blip) plus a custom sounds library supporting multiple uploaded audio files (stored as base64 in localStorage). Separate sound selection for Default, DMs, Channels, and individual nodes — each with its own dropdown populated from built‑in plus all custom sounds. Test button, volume slider, and per‑node sound management in settings.
    • Node Enhancements — Every node shows DM, PING, and PONG buttons, last‑heard time (📡), beacon time, hop count, distance, Show on Map (fly‑to), and Google Maps link on a single line. Favorite nodes — toggle a ⭐ star to pin nodes to the top of the Available Nodes list (persisted in localStorage). Custom node names — click ✏️ to assign a logical name displayed in cyan alongside the original shortName. Favorites and custom names also appear in map popup titles and tooltip labels.
    • Emoji enhancements: each message has a React button that reveals a compact, hidden emoji picker; choosing an emoji auto‑sends a reaction (DM or channel). The send form includes a Quick Emoji bar that inserts emojis into your draft (does not auto‑send).
    • Additional endpoints include /messages, /nodes, /connection_status, /logs, /logs_stream, /send, /ui_send, /commands_info (JSON commands list), and a new /discord_webhook for inbound Discord messages.
    • UI customization through settings panel including button theme color, section colors, 25 map tile styles (default: Carto Light), offline map image upload with lat/lon bounds, hue rotation, notification sounds (built‑in or custom library with multiple files), per‑node sound assignments, section visibility toggles, and volume control. An About section with links to Meshtastic, MeshCore, and the project's GitHub and website.
    • Config Editor (WebUI): Click the “Config” button in the header to view/edit config.json, commands_config.json, and motd.json in a tabbed editor. JSON is validated before saving; writes are atomic. Some changes may require a restart to take effect.
    • Extensions Manager (WebUI): Click the “Extensions” button to view extension status, enable/disable extensions, edit extension configs, and hot‑reload all extensions — all from the browser.
  • Improved Message Chunking & Routing
    • Automatically splits long AI responses into configurable chunks with delays to reduce radio congestion.
    • Configurable flags control whether the bot replies to broadcast channels and/or direct messages.
    • New: LongFast (channel 0) response toggle — by default the bot will NOT respond on LongFast to avoid congestion. Enable ai_respond_on_longfast only if your local mesh agrees.
    • Etiquette: Using AI bots on public LongFast is discouraged; keep it off unless you’re on an isolated/private mesh with community consent.
  • Robust Error Handling & Logging
    • Uses UTC‑based timestamps with an auto‑truncating script log file (keeping the last 100 lines if the file grows beyond 100 MB).
    • Enhanced error detection (including specific OSError codes) and graceful reconnection using threaded exception hooks.
  • Discord Integration Enhancements
    • Route messages to and from Discord.
    • New configuration options and a dedicated /discord_webhook endpoint allow for inbound Discord message processing.
    • MQTT-aware response gating: set respond_to_mqtt_messages to true in config.json if you want the bot to respond to messages that arrive via MQTT. Off by default to prevent multiple server responses.
    • User‑initiated only: The AI does not auto‑message or greet new nodes; it responds only to explicit user input.
  • Commands modal & startup helper
    • WebUI includes a Commands modal (button in the “Send a Message” header) that lists available commands with descriptions.
    • The current alias suffix and a one‑line commands list are printed at startup for easy reference.
  • Windows & Linux Focused
    • Official support for Windows environments with installation guides; instructions for Linux available now - MacOS coming soon!

image

An example of an awesome Raspberry Pi 5 powered mini terminal - running MESH-API & Ollama with HomeAssistant integration!


Quick Start (Windows)

  1. Prerequisites

    • Python 3.11+ — Download from python.org. During install, check “Add Python to PATH”.
    • Git (optional) — git-scm.com for cloning, or download the ZIP from GitHub.
  2. Download/Clone

    • Clone the repository (or download and extract the ZIP):
      git clone https://github.com/mr-tbot/mesh-api.git
      cd mesh-api
  3. Create & Activate a Virtual Environment:

      python -m venv venv
      .\venv\Scripts\activate
  4. Install Dependencies:

      pip install --upgrade pip
      pip install -r requirements.txt
  5. Configure Files:

    • Edit config.json, commands_config.json, and motd.json as needed. Refer to the Configuration section below.
    • Or use the Setup Wizard on first launch in the WebUI.
  6. Start the Bot:

    • Double-click Run MESH-API - Windows.bat or run:
      python mesh-api.py
  7. Access the WebUI Dashboard:


Quick Start (Ubuntu / Linux)

  1. Prerequisites

    • Python 3.11+ and pip:
      sudo apt update && sudo apt install -y python3 python3-pip python3-venv git
    • Grant serial port access (required for USB-connected Meshtastic devices):
      sudo usermod -aG dialout $USER

      Log out and back in for the group change to take effect.

  2. Download/Clone

      git clone https://github.com/mr-tbot/mesh-api.git
      cd mesh-api
  3. Create & Activate a Virtual Environment:

      python3 -m venv venv
      source venv/bin/activate
  4. Install Dependencies:

      pip install --upgrade pip
      pip install -r requirements.txt
  5. Configure Files:

    • Edit config.json, commands_config.json, and motd.json as needed. Refer to the Configuration section below.
    • Or use the Setup Wizard on first launch in the WebUI.
  6. Start the Bot:

      python mesh-api.py
  7. Access the WebUI Dashboard:

Quick Start (Docker)

Multi-arch Docker images are published for linux/amd64 (x86_64) and linux/arm64 (Raspberry Pi 4/5, Apple Silicon).

  1. Prerequisites

    • Docker Engine (or Docker Desktop) installed on your host.
    • A Meshtastic device connected via USB serial, Wi-Fi, or Bluetooth.
  2. Prepare the Volume Structure

    • The docker-required-volumes/ folder in the repository contains a ready-to-use mesh-api/ directory with default configs, all 30 built-in extensions, and empty log files. Copy it to your working directory:
      git clone https://github.com/mr-tbot/mesh-api.git
      cd mesh-api
      cp -r docker-required-volumes/mesh-api ./mesh-api
    • Edit the config files inside mesh-api/config/ before starting:
    mesh-api/
    ├── config/
    │   ├── config.json           # Core configuration (AI provider, connection, etc.)
    │   ├── commands_config.json   # Custom slash commands
    │   └── motd.json              # Message of the Day
    ├── extensions/                # All 30 built-in extensions (add your own here too)
    │   ├── __init__.py
    │   ├── base_extension.py
    │   ├── loader.py
    │   ├── discord/
    │   ├── telegram/
    │   ├── mqtt/
    │   └── ...  (30 built-in extensions)
    └── logs/
        ├── script.log
        ├── messages.log
        └── messages_archive.json
  3. Pull & Run with Docker Compose

    • Copy the included docker-compose.yml to the same directory as your mesh-api/ folder, then:
      docker compose pull
      docker compose up -d
    • USB Serial devices: Uncomment the devices and /dev volume lines in docker-compose.yml and set your serial device path (e.g. /dev/ttyUSB0 or /dev/ttyACM0).
    • Wi-Fi connection: Set use_wifi: true and wifi_host in mesh-api/config/config.json — no device passthrough needed.
  4. Verify the Container:

      docker compose logs -f mesh-api
  5. Access the WebUI Dashboard:

Tip: To add custom extensions, drop the extension folder into mesh-api/extensions/ on the host — it's volume-mounted into the container so no rebuild is needed. Restart the container with docker compose restart to pick up new extensions.


Supported Mesh Networks

MESH-API supports two mesh radio platforms that can operate independently or be bridged together for cross-network communication.

Meshtastic (Primary)

Meshtastic is MESH-API's primary mesh network. Connection is handled automatically by the core — just plug in your Meshtastic device and configure the connection method in config.json.

Setting Description
use_wifi Set true to connect via TCP/WiFi instead of USB serial
wifi_host IP address of your Meshtastic node (when using WiFi)
wifi_port TCP port (default 4403)
serial_port USB serial port (e.g. /dev/ttyUSB0 or COM3) — leave empty for auto-detect
serial_baud Baud rate (default 460800)
use_mesh_interface Set true for direct MeshInterface mode (no serial/WiFi)

All MESH-API features — AI commands, slash commands, emergency alerts, extensions, WebUI dashboard — work natively over the Meshtastic connection.

MeshCore (First-Class Radio — v0.7.0)

MeshCore is a lightweight, multi-hop LoRa mesh firmware focused on embedded packet routing. As of v0.7.0, MeshCore is a first-class radio owned by the MESH-API core (meshcore_core.py) — not an extension. It connects directly to a MeshCore companion-firmware device over USB serial, TCP, or BLE, and its inbound traffic flows through the same network-agnostic pipeline as Meshtastic, so commands, AI, and every extension work on it natively.

⬆️ Upgrading from an earlier build? The old extensions/meshcore bridge plugin is deprecated and automatically defers to the core when the core MeshCore radio is enabled — you do not need (and should not use) the extension anymore. Move your settings into the meshcore block of config.json (see below).

You can run one Meshtastic radio, one MeshCore radio, or one of each. With both connected, MESH-API acts as the man-in-the-middle, optionally bridging chat between the two networks. See Running With Meshtastic, MeshCore, or Both just below for the full configuration, topologies, and WebUI behavior.

Quick start:

  1. pip install meshcore (already in requirements.txt).
  2. Flash a companion device with MeshCore Companion firmware (USB‑serial, BLE, or TCP variant) from https://flasher.meshcore.co.uk.
  3. Enable and configure the radio in the meshcore block of config.json (set enabled: true, pick connection_type, and the port/host/address).
  4. Restart MESH-API. The 🟣 MeshCore status appears in the connection banner, MeshCore nodes show on the harmonized map, and you can send to MeshCore (or both networks) from the dashboard.

MESH-API-v0 6 0-FINAL

The latest v0.6.0 Web-UI revamp! NEW MAPS FEATURES AND TONS OF NEW GOODIES!


Running With Meshtastic, MeshCore, or Both (v0.7.0)

MESH-API v0.7.0 treats MeshCore as a first-class radio managed by the core (meshcore_core.py), feeding inbound messages through the same network-agnostic pipeline as Meshtastic. You can run any of three topologies:

Topology How
Meshtastic only (classic) Leave meshcore.enabled: false. Nothing changes.
MeshCore only (standalone) Set meshtastic_enabled: false and meshcore.enabled: true. No Meshtastic device required.
Both (cross-network router) meshtastic_enabled: true + meshcore.enabled: true. MESH-API bridges traffic between the two networks.

Configure MeshCore in the meshcore block of config.json:

"meshtastic_enabled": true,            // set false to run MeshCore-only
"default_send_network": "auto",        // auto | meshtastic | meshcore | both
"meshcore": {
  "enabled": true,
  "connection_type": "serial",         // serial | tcp | ble
  "serial_port": "/dev/ttyUSB1",       // or tcp_host/tcp_port, or ble_address
  "serial_baud": 115200,
  "bridge_enabled": true,              // mirror chat between networks when both present
  "bridge_meshcore_channel_to_meshtastic_channel": { "0": 0 },
  "bridge_meshtastic_channel_to_meshcore_channel": { "0": 0 }
}

What works across both networks: slash commands, the AI assistant, every extension/plugin, the harmonized node map, DMs, channel/group messaging, and emergency broadcasts. The WebUI adapts automatically — a per-network connection banner, a node network filter with collapsible Meshtastic/MeshCore sections, network badges (📡 MT / 🟣 MC) on nodes and messages, distinct map markers, and a Network selector in the send form (Auto / Meshtastic / MeshCore / Both) that appears when both radios are present.

⚠️ MeshCore support is brand new and widely untested — please report your experience (any topology) on GitHub.


MCP Server (Model Context Protocol)

v0.7.0 ships a built-in MCP server that exposes MESH-API's core functions and its extensions as callable tools, so external AI agents and services — Claude, Perplexity, Hermes, custom agents, OpenClaw, etc. — can drive your Meshtastic and/or MeshCore networks as an agentic backend. This enables advanced workflows: an agent can read the mesh, decide, and act (send messages, run commands, trigger extensions) over LoRa.

How it works

  • Transport: Streamable HTTP / JSON-RPC 2.0 at a single endpoint, POST /mcp. Implemented directly in Flask (no async/uvicorn dependency — runs fine on a Pi Zero). Compatible with MCP clients that speak HTTP directly, or with stdio-only clients (e.g. Claude Desktop) via the mcp-remote bridge.

  • Disabled by default. Enable it in the mcp block of config.json:

    "mcp": {
      "enabled": true,
      "require_auth": true,        // bearer token (auto-generated + printed on first start)
      "auth_token": "",            // leave blank to auto-generate; also accepted as X-API-Key
      "allowed_origins": ["*"],    // DNS-rebinding allowlist
      "allow_emergency": false,    // gate the emergency tool
      "rate_limit_per_min": 120
    }

    On first start with auth enabled, a token like mesh-mcp-XXXX is generated, saved to config, and printed to the log. Pass it as Authorization: Bearer <token>.

Connecting a client

Point any MCP client at http://<host>:5000/mcp with the bearer token. For Claude Desktop / stdio clients:

{
  "mcpServers": {
    "mesh-api": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://<host>:5000/mcp",
               "--header", "Authorization: Bearer mesh-mcp-YOURTOKEN"]
    }
  }
}

Built-in core tools

Tool Purpose
mesh_send_message Send to a network (meshtastic / meshcore / both / auto), broadcast or DM
mesh_list_nodes List nodes across both networks (filterable)
mesh_get_messages Read the recent mesh chat log
mesh_network_status Status of both radios
mesh_list_channels Meshtastic + MeshCore channels
mesh_ai_query Ask the configured AI provider
mesh_list_commands / mesh_run_command List / run any slash command
meshcore_list_contacts MeshCore DM targets
mesh_send_emergency Emergency broadcast (gated by allow_emergency)

Extensions as MCP tools

Every loaded extension's slash command is auto-exposed as an ext_cmd_<command> tool — no work required. Extensions can additionally provide richer, typed tools by implementing two optional, duck-typed methods (no base-class change):

def get_mcp_tools(self) -> list[dict]:
    return [{
        "name": "lookup_city",          # exposed as ext_<slug>_lookup_city
        "description": "Look up weather for a city",
        "inputSchema": {"type": "object",
                        "properties": {"city": {"type": "string"}},
                        "required": ["city"]},
    }]

def call_mcp_tool(self, name: str, arguments: dict) -> str:
    if name == "lookup_city":
        return self._weather_for(arguments.get("city", ""))
    return f"Unknown tool: {name}"

The tool list is rebuilt on every tools/list, so enabling/reloading an extension surfaces its tools without restarting MESH-API. See DEVELOPING_EXTENSIONS.md for details.

Security

Tool calls can send mesh traffic and trigger actions, so the server validates inputs, requires a bearer token by default, validates the Origin header, rate-limits calls, clamps output size, and gates the emergency tool. Treat the token like a password. There should always be a human in the loop for sensitive operations.


Firmware & Software Updates

v0.7.0 adds a comprehensive, safe-by-default update system (firmware_updater.py):

  • Detection — identifies the connected Meshtastic device (hardware model + firmware variant pioEnv + version) and the MeshCore device (model + version).
  • Update notifications — a 🔄 Updates button in the WebUI masthead shows a badge when a newer Meshtastic firmware, MeshCore firmware, or MESH-API release is available (checked against GitHub on a periodic timer + on demand).
  • Release channels — pick stable / beta / alpha independently for Meshtastic and MeshCore firmware. Beta/alpha track GitHub pre-releases; the WebUI shows the newest matching version for the channel you choose. (Your device is not always a Heltec V3 — detection adapts to whatever is connected.)
  • Flashing (optional, off by default) — for ESP32-class Meshtastic devices, the latest firmware can be downloaded and flashed over USB serial via esptool. nRF52/UF2 devices and MeshCore companions fall back to guided web-flasher instructions (unattended flashing there is unsafe). Enable with firmware.allow_flashing: true; optional firmware.auto_update: true flashes ESP32 Meshtastic devices unattended.
"firmware": {
  "auto_check": true,
  "check_interval_sec": 86400,
  "allow_flashing": false,          // master gate for flashing
  "auto_update": false,             // never flashes unattended unless true
  "meshtastic_channel": "stable",   // stable | beta | alpha
  "meshcore_channel": "stable",
  "meshtastic_fw_repo": "meshtastic/firmware",
  "meshcore_fw_repo": "meshcore-dev/MeshCore",
  "mesh_api_repo": "mr-tbot/mesh-api"
}

⚠️ Flashing can brick a radio. Always keep a backup device, and prefer the web flasher unless you understand the risks. The radio goes offline during flashing.


Channel Agents — Assign a Channel to an Agent

v0.7.0 generalizes the old Home Assistant per-channel routing into Channel Agents: assign any mesh channel to a specific agent, and all plain-text (non-command) traffic on that channel is handled by it. Use it to dedicate a channel to OpenClaw, Hermes, Home Assistant, or any AI provider — on either Meshtastic or MeshCore.

Configure it in the top-level channel_agents block of config.json, mapping a channel index (as seen by MESH-API) to an agent spec:

"channel_agents": {
  "6": { "agent": "ai", "provider": "hermes" },          // ch6 → Hermes
  "7": { "agent": "extension", "slug": "openclaw" },      // ch7 → OpenClaw agent
  "5": { "agent": "ai", "provider": "home_assistant" },   // ch5 → Home Assistant
  "8": { "agent": "ai", "provider": "openai", "require_pin": true }
}
  • agent: "ai" routes the channel to a named AI provider (openai, hermes, ollama, claude, home_assistant, etc.) — independent of the global ai_provider, so different channels can use different models.
  • agent: "extension" routes to a loaded extension (e.g. openclaw) via its handle_channel_message() hook, falling back to get_ai_response() or a configured command. See DEVELOPING_EXTENSIONS.md.
  • require_pin: true gates the channel behind a PIN=XXXX prefix (like the Home Assistant secure mode).
  • Channels with an assigned agent always respond, bypassing the global reply_in_channels setting. Slash commands still work normally there.
  • The legacy home_assistant_channel_index setting continues to work and is surfaced via GET /api/channel_agents.

Channel indices differ between physical MeshCore devices — assign the agent to the index as MESH-API receives it (visible in the logs / message panel).


Extensions Reference

Note: The extensions system and all corresponding extensions are new and largely untested. Please report any issues on GitHub so they may be investigated and addressed.

MESH-API ships with 30 built-in extensions across 7 categories — Communication (Discord, Slack, Telegram, Matrix, Signal, WhatsApp, Mattermost, Zello, MQTT, Webhook, IMAP, Mastodon, n8n), Notifications (Apprise, Ntfy, Pushover, PagerDuty, OpsGenie), Emergency & Weather (NWS Alerts, OpenWeatherMap, USGS Earthquakes, GDACS, Amber Alerts, NASA Space Weather), Ham Radio & Off-Grid (Winlink, APRS, BBS), Smart Home (Home Assistant), Mesh Bridging (MeshCore — deprecated, now a core radio), and AI Agents (OpenClaw).

Each extension is a self-contained plugin in the extensions/ directory with its own config.json, extension.py, and __init__.py. Quick start: enable any extension by setting "enabled": true in its config.json and restarting MESH-API.

📦 Full per-extension command lists, config keys, and hooks are documented in EXTENSIONS.md. To build your own, see Developing Custom Extensions and DEVELOPING_EXTENSIONS.md.


Developing Custom Extensions

A step-by-step guide for building custom extensions for the MESH-API plugin system.

Overview

MESH-API uses a plugin-based extension system where each extension is a self-contained Python package that lives in the extensions/ directory. Extensions can:

  • Register slash commands accessible from the mesh network
  • Send and receive messages to/from the mesh
  • React to emergency broadcasts
  • Observe all inbound mesh messages
  • Expose HTTP endpoints via Flask
  • Run background threads for polling external services
  • Act as AI providers

Extensions are automatically discovered and loaded at startup. No changes to core code are required.

Quick Start (Extensions)

  1. Copy the template:

    cp -r extensions/_example extensions/my_extension
  2. Edit the three files:

    • __init__.py — leave empty (marks the folder as a Python package)
    • config.json — define your settings (must include "enabled": true)
    • extension.py — implement your extension class
  3. Restart MESH-API — your extension is auto-discovered and loaded.

  4. Verify — send /extensions on the mesh to see it listed.

Extension Structure

Every extension lives in its own subfolder under extensions/:

extensions/
├── base_extension.py        # Abstract base class (DO NOT MODIFY)
├── loader.py                 # Extension loader (DO NOT MODIFY)
├── __init__.py
└── my_extension/             # Your extension folder
    ├── __init__.py           # Empty file (required)
    ├── config.json           # Extension configuration
    └── extension.py          # Extension implementation

Naming Rules:

  • Folder names must be valid Python identifiers (lowercase, underscores OK)
  • Folders starting with _ are skipped by the loader (used for templates)
  • The class inside extension.py must subclass BaseExtension
  • Class name convention: <Name>Extension (e.g. MyExtension)

Base Class API Reference

Every extension inherits from BaseExtension. Here's the complete API:

Constructor (automatic):

def __init__(self, extension_dir: str, app_context: dict):

You do not override __init__. The base class handles:

  • self.extension_dir — absolute path to your extension's folder
  • self.app_context — shared dict with core helpers (see below)
  • self._config — loaded from your config.json

Required Properties (must override):

Property Returns Description
name str Human-readable name (e.g. "My Extension")
version str Semantic version (e.g. "1.0.0")

Built-in Properties (inherited):

Property Returns Description
enabled bool config["enabled"] — the loader checks this
commands dict Slash commands to register (override to add)
config dict Read-only access to loaded config

Lifecycle Hooks:

Method When Called
on_load() Once after instantiation at startup
on_unload() On shutdown or before hot-reload

Message Hooks:

Method Signature Purpose
send_message() (message: str, metadata: dict \| None) Outbound: mesh → external service
receive_message() () Inbound polling (prefer background threads)
handle_command() (command: str, args: str, node_info: dict) → str \| None Handle a registered slash command
on_emergency() (message: str, gps_coords: dict \| None) Emergency broadcast hook
on_message() (message: str, metadata: dict \| None) Observe all inbound mesh messages

Flask Integration:

Method Signature Purpose
register_routes() (app: Flask) Register HTTP endpoints

Helper Methods:

Method Signature Purpose
send_to_mesh() (text, channel_index=None, destination_id=None) Send a message to the mesh network
log() (message: str) Write to the MESH-API script log
_save_config() () Persist config changes to disk

app_context Dict:

The app_context dict provides access to core functionality:

Key Type Description
interface MeshInterface The Meshtastic serial/TCP/BLE interface
send_broadcast_chunks function(iface, text, channel_idx) Send broadcast message
send_direct_chunks function(iface, text, destination_id) Send direct message
add_script_log function(message) Core logging function
flask_app Flask The Flask application instance
config dict Main config.json contents

Step-by-Step Tutorial

1. Create the folder structure:

extensions/my_sensor/
├── __init__.py          # Empty
├── config.json
└── extension.py

2. Define config.json:

{
  "enabled": true,
  "sensor_url": "http://localhost:9000/api/reading",
  "poll_interval_seconds": 300,
  "broadcast_channel_index": 0,
  "unit": "°F"
}

The only required key is "enabled". Everything else is up to you.

3. Implement extension.py:

"""My Sensor extension — reads temperature from a local sensor API."""

import threading
import time

try:
    import requests
except ImportError:
    requests = None

from extensions.base_extension import BaseExtension


class MySensorExtension(BaseExtension):

    @property
    def name(self) -> str:
        return "My Sensor"

    @property
    def version(self) -> str:
        return "1.0.0"

    @property
    def commands(self) -> dict:
        return {
            "/temp": "Read the current temperature",
        }

    def on_load(self) -> None:
        self._stop = threading.Event()
        self.log(f"My Sensor loaded. URL: {self.config.get('sensor_url')}")

    def on_unload(self) -> None:
        self._stop.set()
        self.log("My Sensor unloaded.")

    def handle_command(self, command: str, args: str,
                       node_info: dict) -> str | None:
        if command == "/temp":
            return self._read_sensor()
        return None

    def _read_sensor(self) -> str:
        url = self.config.get("sensor_url", "")
        unit = self.config.get("unit", "°F")
        if not url:
            return "Sensor URL not configured."
        try:
            resp = requests.get(url, timeout=5)
            data = resp.json()
            temp = data.get("temperature", "?")
            return f"🌡️ Current temperature: {temp}{unit}"
        except Exception as exc:
            return f"⚠️ Sensor error: {exc}"

4. Test it:

  1. Restart MESH-API
  2. Send /extensions on mesh — should show "My Sensor v1.0.0 [enabled]"
  3. Send /temp — should return the temperature reading

Hook Reference

handle_command(command, args, node_info) → str | None

The most commonly used hook. Called when a mesh user sends one of your registered commands.

def handle_command(self, command: str, args: str, node_info: dict) -> str | None:
    if command == "/mycmd":
        sender = node_info.get("shortname", "?")
        return f"Hello {sender}! You said: {args}"
    return None  # Not our command

node_info dict:

{
    "node_id": "!abcd1234",      # Hex node ID
    "shortname": "ABC",          # 4-char node short name
    "longname": "Alpha Bravo",   # Full node name
    "channel_index": 0,          # Channel the message arrived on
    "is_direct": False,          # True if DM, False if broadcast
}

Return value:

  • str — text sent back to the mesh (broadcast or DM depending on context)
  • None — command not handled, loader passes to next extension

on_message(message, metadata)

Read-only observer hook. Called for every inbound mesh message. Use for logging, analytics, keyword scanning, or triggering side-effects.

def on_message(self, message: str, metadata: dict | None = None) -> None:
    if "help" in message.lower():
        self.log(f"Help request detected: {message}")

Do NOT return a response from on_message. Use handle_command for responses, or send_to_mesh() for async replies.

on_emergency(message, gps_coords)

Called when /emergency or /911 is triggered on the mesh.

def on_emergency(self, message: str, gps_coords: dict | None = None) -> None:
    lat = gps_coords.get("lat", "?") if gps_coords else "?"
    lon = gps_coords.get("lon", "?") if gps_coords else "?"
    self.log(f"EMERGENCY at {lat},{lon}: {message}")
    # Forward to your external service here

send_message(message, metadata)

Outbound hook. Called by the loader when the core wants to push a message to external services.

def send_message(self, message: str, metadata: dict | None = None) -> None:
    requests.post("https://example.com/api", json={"text": message})

register_routes(app)

Register Flask HTTP endpoints for inbound webhooks or APIs.

def register_routes(self, app) -> None:
    @app.route("/my_extension/webhook", methods=["POST"])
    def my_webhook():
        from flask import request, jsonify
        data = request.get_json()
        message = data.get("message", "")
        self.send_to_mesh(message, channel_index=0)
        return jsonify({"status": "ok"})

Extension Configuration

Reading Config:

api_key = self.config.get("api_key", "")
interval = int(self.config.get("poll_interval", 60))

Updating Config at Runtime:

self._config["last_check"] = "2025-01-01T00:00:00Z"
self._save_config()  # Writes to config.json on disk

Config Best Practices:

  • Always provide defaults with .get(key, default)
  • Include "enabled": false as the first key
  • Use descriptive key names: poll_interval_seconds, broadcast_channel_index
  • Document every key in your extension's comments or README

Flask Routes (Extensions)

Extensions can expose HTTP endpoints. The Flask app is passed to register_routes():

def register_routes(self, app) -> None:
    @app.route("/my_ext/data", methods=["GET"])
    def my_data():
        from flask import jsonify
        return jsonify({"status": "ok", "extension": self.name})

    @app.route("/my_ext/inbound", methods=["POST"])
    def my_inbound():
        from flask import request
        data = request.get_json(force=True)
        text = data.get("message", "")
        if text:
            self.send_to_mesh(text)
        return "OK", 200

Rules:

  • Use unique route paths prefixed with your extension name
  • Import Flask utilities inside the route functions (avoid circular imports)
  • Keep route handlers lightweight

Background Threads

Many extensions need to poll external services. Use daemon threads:

import threading
import time

class MyExtension(BaseExtension):
    def on_load(self) -> None:
        self._stop = threading.Event()
        self._thread = threading.Thread(
            target=self._poll_loop,
            daemon=True,
            name="my-ext-poll",
        )
        self._thread.start()

    def on_unload(self) -> None:
        self._stop.set()
        if self._thread.is_alive():
            self._thread.join(timeout=10)

    def _poll_loop(self) -> None:
        time.sleep(10)  # Initial delay to let system stabilize

        while not self._stop.is_set():
            try:
                data = self._fetch_data()
                if data:
                    self.send_to_mesh(f"New data: {data}")
            except Exception as exc:
                self.log(f"Poll error: {exc}")

            # Interruptible sleep (checks stop event every second)
            interval = int(self.config.get("poll_interval_seconds", 60))
            for _ in range(interval):
                if self._stop.is_set():
                    break
                time.sleep(1)

Thread Safety Tips:

  • Use threading.Lock() if shared state is accessed from multiple threads
  • Use threading.Event() for clean shutdown signaling
  • Use interruptible sleep pattern (loop with 1-second sleeps)
  • Always set daemon=True so threads don't prevent exit
  • Give threads descriptive names

Extension Best Practices

  1. Guard imports — wrap optional dependencies in try/except:

    try:
        import requests
    except ImportError:
        requests = None
  2. Handle errors gracefully — never let exceptions crash the main process:

    try:
        result = self._call_api()
    except Exception as exc:
        return f"⚠️ Error: {exc}"
  3. Respect mesh bandwidth — keep messages short (< 230 chars if possible). The mesh has limited capacity.

  4. De-duplicate — track seen message IDs to avoid broadcasting the same alert twice:

    if msg_id in self._seen_ids:
        return
    self._seen_ids.add(msg_id)
  5. Clean up in on_unload() — stop threads, close sockets, flush buffers.

Naming Conventions:

  • Folder: snake_case (e.g. my_extension)
  • Class: PascalCaseExtension (e.g. MyExtension)
  • Commands: /<lowercase> — avoid collisions with built-in commands
  • Config keys: snake_case with descriptive names

Message Formatting — Use emoji prefixes for visual scanning on small screens:

  • 📡 — radio/connectivity
  • 🚨 — alerts/emergencies
  • ✅ — success/confirmation
  • ⚠️ — warnings/errors
  • 📋 — lists/info
  • 📧 — email/messages
  • 🌡️ — weather/sensors

Testing Extensions

  1. Set "enabled": true in your extension's config.json
  2. Restart MESH-API
  3. Check the logs for [ext:YourName] entries
  4. Send /extensions to verify it's loaded
  5. Test each command from a mesh node

Your self.log() calls appear in the MESH-API script log with the prefix [ext:YourName]. Check the WebUI Logs panel or the log file.

Extension Troubleshooting

Extension not loading:

  • Check that extension.py exists in the folder
  • Ensure __init__.py exists (even if empty)
  • Verify the class inherits from BaseExtension
  • Check that name and version properties are defined
  • Folder names starting with _ are ignored intentionally
  • Check logs for import errors

Commands not responding:

  • Verify commands property returns a dict with your command
  • Check handle_command() matches the exact command string
  • Make sure no other extension registers the same command
  • Confirm "enabled": true in your config.json

send_to_mesh not working:

  • Ensure app_context contains a valid interface
  • Check that the mesh interface is connected
  • Verify channel index is valid for your mesh configuration

Config not loading:

  • Validate JSON syntax in config.json (use a JSON linter)
  • Check file permissions
  • Look for log entries about config load failures

Extension Examples Reference

The extensions/ directory includes 25+ working extensions you can reference:

Extension Complexity Good Example Of
_example Minimal Basic structure, all hooks documented
ntfy Simple HTTP API + push notifications
pushover Simple Outbound-only notifications
nws_alerts Medium Polling + auto-broadcast + filtering
telegram Medium Bidirectional bridge + long-polling
mqtt Medium Event-driven with paho-mqtt
bbs Complex SQLite database + thread safety + subcommands
aprs Complex Raw TCP sockets + protocol parsing
discord Complex Webhook + bot + Flask route

Roadmap

  • API Integration Workflow (Planned)

    • A guided workflow for adding new API integrations, including configuration templates, validation checks, and safe test routes before production use.
    • Goal: make it easier to connect external services without editing core code.
  • MeshCore Routing Support (Initial Implementation — v0.6.0)

    • MeshCore extension added with bidirectional bridge, command support, and AI integration.
    • Supports USB serial and TCP/WiFi connections to MeshCore companion devices.
    • (Superseded in v0.7.0 — MeshCore is now a first-class core radio; see Running With Meshtastic, MeshCore, or Both.)

Changelog

📜 The full version history has moved to CHANGELOG.md to keep this README short. It covers every release from v0.1 through the current v0.7.3.7 Beta, with both per-release summaries and detailed notes.


Basic Usage

  • Interacting with the AI:
    • Use your randomized alias shown at startup (e.g., /ai-9z) — AI commands require the suffix: /ai-9z, /bot-9z, /query-9z, /data-9z followed by your message.
    • To avoid collisions (multiple bots responding), prefer your unique alias and/or customize commands in commands_config.json.
    • For direct messages, simply DM the AI node if configured to reply.
  • Location Query:
    • Send /whereami-XY (replace XY with your suffix) to retrieve the node’s GPS coordinates (if available).
  • Emergency Alerts:
    • Trigger an emergency using /emergency <message> or /911 <message>.
      • These commands send alerts via Twilio, SMTP, and Discord (if enabled), including GPS data and timestamps.
  • Sending and receiving SMS:
    • Send SMS using your suffixed command, e.g., /sms-9z <+15555555555> <message>
    • Config options to either route incoming Twilio SMS messages to a specific node, or a channel index.
  • Home Assistant Integration:
    • When enabled, messages sent on the designated Home Assistant channel (as defined by "home_assistant_channel_index") are forwarded to Home Assistant’s conversation API.
    • In secure mode, include the PIN in your message (format: PIN=XXXX your message).
  • WebUI Messaging:
    • Use the dashboard’s send‑message form to send broadcast or direct messages. The mode toggle and node selection simplify quick replies.

WebUI Config Editor (new)

  • Open the dashboard and click the “Config” button in the header (next to Commands/Logs).
  • A tabbed editor appears with four views:
    • ⚙️ config.json — a form-based editor for all core app settings (providers, timeouts, routing, integrations, etc.)
    • 📝 Raw JSON — direct JSON editing for advanced users
    • commands_config.json — a form-based command builder. Add, edit, or remove slash commands. Each command has a trigger (/mycommand), a type (Static Response or AI Prompt), a response/prompt value, and a description. No manual JSON editing needed.
    • motd.json — a simple text field for the Message of the Day string
  • Make edits and click Save. The editor validates data before saving and writes changes atomically.
  • 🧙 Run Setup Wizard — re-run the first-start wizard at any time from the Config Editor header. The wizard pre-populates fields from the existing configuration so you can review and update settings without starting from scratch.
  • Changes to some settings may require restarting the app/container to take effect (e.g., provider, connectivity, or Discord/Twilio credentials).
  • Security note: If you expose the WebUI beyond localhost, protect access to the dashboard since configuration files may contain secrets (API keys, tokens).

Manual GPS Location

  • If your node does not have a GPS module, you can set your latitude and longitude manually in the UI Settings panel under "📍 My Location (Manual GPS)".
  • When set, distance calculations to other nodes and the “You” marker on the map will use your manual coordinates instead of the connected node’s GPS.
  • Leave the fields blank to revert to the connected node’s GPS.

Channel Names from Node

  • Channel names are now automatically pulled from the connected Meshtastic node via the /api/channels endpoint.
  • You can rename channels in the UI Settings panel under "📡 Channel Names". Overrides are stored locally and take priority over node-reported names.

WebUI Config Options (Quick Guide)

The Config Editor includes a grouped help panel. These are the main groups and what they cover:

  • Core: AI provider selection, system prompt, alias suffix, AI node name, local location string.
  • Diagnostics: debug logging and message log limits.
  • Providers: LM Studio, OpenAI, Ollama, and Home Assistant settings.
  • Connectivity: WiFi, serial, or mesh interface options and advanced node overrides.
  • Policy: Reply behavior and MQTT response gating.
  • Performance: Chunk sizing and delays for radio-friendly responses.
  • Channels: Friendly channel names and /nodes-XY online window.
  • Integrations: Home Assistant, Discord, Twilio, and SMTP credentials and routing. (future home of API integration settings)

How AI messages are identified and ignored by other bots

  • AI responses include a very short prefix marker m@i at the start of the message body. This is not configurable on purpose and is capped at 3 characters to conserve airtime.
  • Other MESH-API instances will ignore messages that start with this marker, preventing bots from talking to each other.
  • Each instance also tracks node IDs that have sent AI-tagged messages and ignores further messages from those nodes.

What is bot‑looping and why is it a problem?

  • Bot‑looping happens when two or more automated agents see each other’s messages as prompts and keep replying back and forth without a human in the loop.
  • On a constrained LoRa mesh, this can quickly saturate airtime (especially on LongFast), drain batteries, and crowd out legitimate traffic.
  • Loops can be surprisingly hard to break because:
    • Messages may be re‑broadcast via MQTT and multiple gateways, multiplying replies.
    • Nodes can buffer/retry after brief outages, re‑triggering the loop even if you silence one side.
    • Different bots might parse/quote each other in ways that keep producing “valid” prompts.
  • MESH‑API mitigations:
    • A fixed 3‑char marker (m@i) at the start of AI replies so other instances will ignore them.
    • A memory of node IDs that have emitted AI‑tagged messages to avoid engaging those nodes.
    • Conservative defaults: no LongFast replies by default and MQTT response gating disabled by default.
    • Policy: the AI never initiates conversations or responds to arrival/presence events—only explicit human messages.

Using the API

The MESH-API server (running on Flask) exposes the following endpoints:

  • GET /messages
    Retrieve the last 100 messages in JSON format.
  • GET /nodes
    Retrieve a live list of connected nodes as JSON.
  • GET /connection_status
    Get current connection status and error details.
  • GET /logs
    View a styled log page showing uptime, restarts, and recent log entries.
  • GET /logs_stream
    Stream recent logs in JSON for lightweight polling.
  • GET /dashboard
    Access the full WebUI dashboard.
  • GET /commands_info
    Retrieve a JSON list of available commands and descriptions (used by the in‑app Commands modal).
  • GET /config_editor/load
    Load the current contents of config.json, commands_config.json, and motd.json for the WebUI Config Editor.
  • POST /config_editor/save
    Save updates to the above files. Payload is validated (JSON where applicable) and written atomically. Some settings require an app restart to apply.
  • POST /send and POST /ui_send
    Send messages programmatically.
  • POST /discord_webhook
    Receive messages from Discord (if configured).

Configuration

Your config.json file controls core MESH-API settings — connection, AI, messaging, and emergency alerts. Integration-specific settings (Discord, Home Assistant, Slack, Telegram, etc.) are now configured per-extension in extensions/<name>/config.json. See the Extensions System section and the WebUI Extensions Manager for details.

Below is the default config.json with inline explanations:

{
  "debug": false,                          // Enable verbose debug logging
  "use_mesh_interface": false,             // Set true to use the Meshtastic mesh interface
  "use_wifi": false,                       // Set true to connect to your node via WiFi instead of serial
  "wifi_host": "MESHTASTIC NODE IP HERE",  // IP address of your Meshtastic device (WiFi mode)
  "wifi_port": 4403,                       // TCP port for WiFi connection (default 4403)

  "use_bluetooth": false,                   // Set true to connect to your node via Bluetooth Low Energy (BLE)
  "ble_address": "",                        // BLE MAC address or UUID of your node (leave empty for auto-scan)

  "extensions_path": "./extensions",       // Path to the extensions directory

  "ai_respond_on_longfast": false,         // Do NOT auto-respond on LongFast (channel 0) — enable only with mesh/community consent
  "respond_to_mqtt_messages": false,       // If true, the bot responds to messages that arrived via MQTT (off by default to prevent multi-replies)

  "nodes_online_window_sec": 7200,         // Time window (seconds) for /nodes-XY online count

  "serial_port": "/dev/ttyUSB0",           // Serial port if using USB (e.g., /dev/ttyUSB0 on Linux, COMx on Windows)
  "serial_baud": 460800,                   // Baud rate for serial connections (lower for long USB runs)

  "ai_command": "",                        // Randomized per-install AI command suffix (e.g., "/ai-9z") — generated on first run to prevent collisions
  "ai_provider": "lmstudio, openai, ollama, claude, gemini, grok, openrouter, groq, deepseek, mistral, or openai_compatible",
  "system_prompt": "You are a helpful assistant responding to mesh network chats. Respond in as few words as possible while still answering fully.",

  // --- LM Studio ---
  "lmstudio_url": "http://localhost:1234/v1/chat/completions",
  "lmstudio_chat_model": "MODEL IDENTIFIER HERE",
  "lmstudio_embedding_model": "TEXT EMBEDDING MODEL IDENTIFIER HERE",
  "lmstudio_timeout": 60,

  // --- OpenAI ---
  "openai_api_key": "",
  "openai_model": "gpt-4.1-mini",
  "openai_timeout": 60,

  // --- Ollama ---
  "ollama_url": "http://localhost:11434/api/generate",
  "ollama_model": "llama3",
  "ollama_timeout": 60,
  "ollama_max_parallel": 1,               // Max concurrent Ollama requests (useful on low-power hardware)
  "ollama_options": {},                    // Optional generation overrides (e.g., {"num_ctx": 2048, "temperature": 0.2})
  "ollama_keep_alive": "10m",             // Keep model loaded for this duration; "0" to unload immediately

  // --- Claude ---
  "claude_api_key": "",
  "claude_model": "claude-sonnet-4-20250514",
  "claude_timeout": 60,

  // --- Gemini ---
  "gemini_api_key": "",
  "gemini_model": "gemini-2.0-flash",
  "gemini_timeout": 60,

  // --- Grok ---
  "grok_api_key": "",
  "grok_model": "grok-3",
  "grok_timeout": 60,

  // --- OpenRouter ---
  "openrouter_api_key": "",
  "openrouter_model": "openai/gpt-4.1-mini",
  "openrouter_timeout": 60,

  // --- Groq ---
  "groq_api_key": "",
  "groq_model": "llama-3.3-70b-versatile",
  "groq_timeout": 60,

  // --- DeepSeek ---
  "deepseek_api_key": "",
  "deepseek_model": "deepseek-chat",
  "deepseek_timeout": 60,

  // --- Mistral ---
  "mistral_api_key": "",
  "mistral_model": "mistral-small-latest",
  "mistral_timeout": 60,

  // --- OpenAI-Compatible (any provider with an OpenAI-compatible API) ---
  "openai_compatible_api_key": "",
  "openai_compatible_url": "",
  "openai_compatible_model": "",
  "openai_compatible_timeout": 60,

  // --- Channel names ---
  "channel_names": {
    "0": "LongFast",
    "1": "Channel 1",
    "2": "Channel 2",
    "3": "Channel 3",
    "4": "Channel 4",
    "5": "Channel 5",
    "6": "Channel 6",
    "7": "Channel 7",
    "8": "Channel 8",
    "9": "Channel 9"
  },

  "reply_in_channels": true,              // Allow AI to reply in broadcast channels
  "reply_in_directs": true,               // Allow AI to reply in direct messages

  "chunk_size": 200,                      // Maximum size for message chunks (bytes)
  "max_ai_chunks": 5,                     // Maximum number of chunks per AI response
  "chunk_delay": 10,                      // Delay (seconds) between chunks to reduce congestion

  "local_location_string": "@ YOUR LOCATION HERE",  // Location label for your node
  "ai_node_name": "Mesh-API-Alpha",       // Display name for your AI node

  "force_node_num": null,                 // Override the node number (null = auto-detect)
  "max_message_log": 0,                   // Max messages to log (0 = unlimited)

  // --- Emergency Alerts: Twilio SMS ---
  "enable_twilio": false,
  "enable_smtp": false,
  "alert_phone_number": "+15555555555",
  "twilio_sid": "TWILIO_SID",
  "twilio_auth_token": "TWILIO_AUTH_TOKEN",
  "twilio_from_number": "+14444444444",
  "twilio_inbound_target": "channel",      // "channel" or "node" for inbound SMS routing
  "twilio_inbound_channel_index": 1,
  "twilio_inbound_node": "!FFFFFFFF",

  // --- Emergency Alerts: SMTP Email ---
  "smtp_host": "SMTP HOST HERE",
  "smtp_port": 465,                       // 465 for SSL, 587 for TLS
  "smtp_user": "SMTP USER HERE",
  "smtp_pass": "SMTP PASS HERE",
  "alert_email_to": "ALERT EMAIL HERE"
}

Note: Discord, Home Assistant, Slack, Telegram, and all other integration-specific settings have been moved to the Extensions System. Each extension has its own config.json under extensions/<name>/. You can manage them via the WebUI Extensions Manager or by editing the files directly.


Home Assistant & LLM API Integration

Home Assistant Integration

Home Assistant is now an extension. Configure it in extensions/home_assistant/config.json or via the WebUI Extensions Manager. See Extensions System for details.

  • Enable: Set "enabled": true in extensions/home_assistant/config.json.
  • Configure: Set the url, token, channel_index, and timeout fields in the extension config.
  • Security (Optional): Enable "enable_pin": true and set "secure_pin" in the extension config.
  • Routing: Messages on the designated channel are forwarded to Home Assistant. When PIN mode is enabled, include your PIN in the format PIN=XXXX your message.

LLM API Integration

Set "ai_provider" in config.json to one of the 12 supported providers, then fill in the corresponding API key / URL / model fields:

  • LM Studio: "ai_provider": "lmstudio" — configure lmstudio_url, and optionally set lmstudio_chat_model / lmstudio_embedding_model if using multiple models.
  • OpenAI: "ai_provider": "openai" — provide openai_api_key and choose a model (default gpt-4.1-mini).
  • Ollama: "ai_provider": "ollama" — configure URL, model, and optional generation overrides via ollama_options.
  • Claude: "ai_provider": "claude" — provide claude_api_key (default model claude-sonnet-4-20250514).
  • Gemini: "ai_provider": "gemini" — provide gemini_api_key (default model gemini-2.0-flash).
  • Grok: "ai_provider": "grok" — provide grok_api_key (default model grok-3).
  • OpenRouter: "ai_provider": "openrouter" — provide openrouter_api_key (default model openai/gpt-4.1-mini).
  • Groq: "ai_provider": "groq" — provide groq_api_key (default model llama-3.3-70b-versatile).
  • DeepSeek: "ai_provider": "deepseek" — provide deepseek_api_key (default model deepseek-chat).
  • Mistral: "ai_provider": "mistral" — provide mistral_api_key (default model mistral-small-latest).
  • OpenAI-Compatible: "ai_provider": "openai_compatible" — provide openai_compatible_url, openai_compatible_api_key, and openai_compatible_model for any provider with an OpenAI-compatible API.

All providers have a configurable _timeout (default 60 seconds).


Communication Integrations

Email Integration

  • Enable Email Alerts:
    • Set "enable_smtp": true in config.json.
  • Configure SMTP:
    • Provide the following settings in config.json:
      • "smtp_host" (e.g., smtp.gmail.com)
      • "smtp_port" (use 465 for SSL or another port for TLS)
      • "smtp_user" (your email address)
      • "smtp_pass" (your email password or app-specific password)
      • "alert_email_to" (recipient email address or list of addresses)
  • Behavior:
    • Emergency emails include a clickable Google Maps link (generated from available GPS data) so recipients can quickly view the sender’s location.
  • Note:
    • Ensure your SMTP settings are allowed by your email provider (for example, Gmail may require an app password and proper security settings).

Discord Integration: Detailed Setup & Permissions

Discord is now an extension. Configure it in extensions/discord/config.json or via the WebUI Extensions Manager. The setup steps below for creating a Discord bot and permissions still apply.

483177250_1671387500130340_6790017825443843758_n

1. Create a Discord Bot

  • Access the Developer Portal:
    Go to the Discord Developer Portal and sign in with your Discord account.
  • Create a New Application:
    Click on "New Application," give it a name (e.g., MESH-API Bot), and confirm.
  • Add a Bot to Your Application:
    • Select your application, then navigate to the Bot tab on the left sidebar.
    • Click on "Add Bot" and confirm by clicking "Yes, do it!"
    • Customize your bot’s username and icon if desired.

2. Set Up Bot Permissions

  • Required Permissions:
    Your bot needs a few basic permissions to function correctly:
    • View Channels: So it can see messages in the designated channels.
    • Send Messages: To post responses and emergency alerts.
    • Read Message History: For polling messages from a channel (if polling is enabled).
    • Manage Messages (Optional): If you want the bot to delete or manage messages.
  • Permission Calculator:
    Use a tool like Discord Permissions Calculator to generate the correct permission integer.
    For minimal functionality, a permission integer of 3072 (which covers "Send Messages," "View Channels," and "Read Message History") is often sufficient.

3. Invite the Bot to Your Server

  • Generate an Invite Link:
    Replace YOUR_CLIENT_ID with your bot’s client ID (found in the General Information tab) in the following URL:
    https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=3072&scope=bot
  • Invite the Bot:
    Open the link in your browser, select the server where you want to add the bot, and authorize it. Make sure you have the “Manage Server” permission in that server.

4. Configure Bot Credentials in Extension Config

Update extensions/discord/config.json with the following keys (or use the WebUI Extensions Manager):

{
  "enabled": true,
  "webhook_url": "YOUR_DISCORD_WEBHOOK_URL",
  "receive_enabled": true,
  "bot_token": "YOUR_BOT_TOKEN",
  "channel_id": "YOUR_CHANNEL_ID",
  "inbound_channel_index": 1,
  "send_ai": true,
  "send_emergency": true
}
  • webhook_url:
    Create a webhook in your desired Discord channel (Channel Settings → Integrations → Webhooks) and copy its URL.
  • bot_token & channel_id:
    Copy your bot’s token from the Developer Portal and enable message polling by specifying the channel ID where the bot should read messages.
    To get a channel ID, enable Developer Mode in Discord (User Settings → Advanced → Developer Mode) then right-click the channel and select "Copy ID."

5. Polling Integration (Optional)

  • Enable Message Polling:
    Set "receive_enabled": true in the extension config to allow the bot to poll for new messages.
  • Routing:
    The "inbound_channel_index" key determines the mesh channel used by MESH-API for routing incoming Discord messages.

6. Testing Your Discord Setup

  • Restart MESH-API (or hot-reload via the WebUI Extensions Manager).
  • Check Bot Activity:
    Verify that the bot is present in your server, that it can see messages in the designated channel, and that it can send responses.
  • Emergency Alerts & AI Responses:
    Confirm that emergency alerts and AI responses are being posted in Discord as per your extension configuration.

7. Troubleshooting Tips

  • Permissions Issues:
    If the bot isn’t responding or reading messages, double-check that its role on your server has the required permissions.
  • Channel IDs & Webhook URLs:
    Verify that you’ve copied the correct channel IDs and webhook URLs (ensure no extra spaces or formatting issues).
  • Bot Token Security:
    Keep your bot token secure. If it gets compromised, regenerate it immediately from the Developer Portal.

Twilio Integration

  • Enable Twilio:
    • Set "enable_twilio": true in config.json.
  • Configure Twilio Credentials:
    • Provide your Twilio settings in config.json:
      • "twilio_sid": "YOUR_TWILIO_SID"
      • "twilio_auth_token": "YOUR_TWILIO_AUTH_TOKEN"
      • "twilio_from_number": "YOUR_TWILIO_PHONE_NUMBER"
      • "alert_phone_number": "DESTINATION_PHONE_NUMBER" (the number to receive emergency SMS)
  • Usage:
    • When an emergency is triggered, the bot sends an SMS containing the alert message (with a Google Maps link if GPS data is available).
  • Tip:
    • Follow Twilio's setup guide to obtain your SID and Auth Token, and ensure that your phone numbers are verified.

Other Important Settings

  • Logging & Archives:

    • Script logs are stored in script.log and message logs in messages.log.
    • An archive is maintained in messages_archive.json to keep recent messages.
  • Device Connection:

    • Configure the connection method for your Meshtastic device by setting either the "serial_port" or enabling "use_wifi" along with "wifi_host" and "wifi_port".
    • For Bluetooth Low Energy (BLE) connections, set "use_bluetooth": true and optionally provide "ble_address" with the device MAC address or UUID. Leave "ble_address" empty for auto-scan. Requires the bleak Python package.
    • Alternatively, enable "use_mesh_interface" if applicable.
    • Connection priority: WiFi TCP > Bluetooth BLE > MeshInterface > USB Serial.
    • Baud Rate is optionally set if you need - this is for longer USB runs (roof nodes connected via USB) and bad USB connections.
  • Message Routing & Commands:

    • Custom commands can be added in commands_config.json.
    • The WebUI Dashboard (accessible at http://localhost:5000/dashboard) displays messages and node status.
  • AI Provider Settings:

    • Adjust "ai_provider" and related API settings (timeouts, models, etc.) for any of the 12 supported providers: LM Studio, OpenAI, Ollama, Claude, Gemini, Grok, OpenRouter, Groq, DeepSeek, Mistral, or any OpenAI-compatible endpoint.
  • Extensions:

    • Integration-specific settings (Discord, Home Assistant, Slack, Telegram, etc.) are configured per-extension in extensions/<name>/config.json. Use the WebUI Extensions Manager to enable, disable, and configure extensions without editing files directly.
  • Security:

    • If using the Home Assistant extension with PIN protection, follow the specified format (PIN=XXXX your message) to ensure messages are accepted.
  • Testing:

    • You can test SMS sending with your suffixed /sms-XY command or trigger an emergency alert to confirm that Twilio and email integrations are functioning.

Contributing & Disclaimer

  • v0.7.3.7 Beta:
    The 0.7.x line makes MeshCore a first-class radio and adds the MCP server, firmware-update system, token-free AI-endpoint heartbeat monitoring (v0.7.3.6), and — in v0.7.3.7 — a fix so all extensions route to MeshCore, not just Meshtastic. These features are still relatively untested in the field — please report any issues on GitHub so they may be investigated and addressed.
  • Feedback & Contributions:
    Report issues or submit pull requests on GitHub. Your input is invaluable.
  • Use Responsibly:
    Modifying this code for nefarious purposes is strictly prohibited. Use at your own risk.


Conclusion

MESH-API v0.7.3.7 Beta is here! The 0.7.x line treats MeshCore as a first-class radio alongside Meshtastic, adds a built-in MCP server for external AI agents, a firmware/software update manager, token-free AI-endpoint heartbeat monitoring (v0.7.3.6), and — new in v0.7.3.7 — a fix so every extension routes outbound messages to MeshCore as well as Meshtastic. All on top of the powerful 30-extension plugin system, 12 AI providers, and safer defaults. Whether you’re chatting directly with your node, integrating with Home Assistant, or leveraging multi‑channel alerting (Twilio, Email, Discord), this release offers the most comprehensive and extensible off‑grid AI assistant experience yet. Please report any issues on GitHub.

Enjoy tinkering, stay safe, and have fun!
Please share your feedback or report issues on GitHub.