Skip to main content
Glama

meshcore-mcp

A Model Context Protocol server that exposes a MeshCore node — and the mesh reachable through it — as a clean, high-signal interface for any AI agent or tool.

License: MIT Types: included Node Module: ESM

Quick start — add it to Claude Code pointed at a node, then ask "survey the mesh":

claude mcp add meshcore --env MESHCORE_HOST=<node-ip> -- npx -y @dpup/meshcore-mcp
claude -p 'survey the mesh'

meshcore-mcp wraps a @dpup/meshcore-ts MeshCoreClient behind a small, deliberately shaped surface of MCP tools, resources, and prompts. Point an MCP client at a node and operate the mesh in natural language — survey nodes, read recent traffic, send messages, and run curated admin commands — with structured, digested results instead of raw frames.

It is the device layer, and ungated by design: no conversation policy, no admin-channel gate, no coalescing, no autonomous behavior. A human at Claude Code is the policy, and approves each step; an autonomous agent brings its own. Same server, correct in both cases — its job is to be a faithful, well-shaped device interface, and to mark read-versus-action on every tool so a consuming policy layer, or a reviewing human, can reason about safety mechanically.

WARNING

The commands are ungated. send_message, set_channel, delete_channel, trace_path, and admin transmit on the mesh and change device state with no built-in approval or policy layer of their own. With Claude Code you approve every tool call — that human-in-the-loop is the gate, and what makes interactive use safe. For autonomous or headless use, put your own policy layer in front (that is meshcore-elmer's job) — don't point an unattended agent at a live mesh without one. Every tool is annotated read-only vs. action (readOnlyHint / destructiveHint) so a policy layer, or a reviewing human, can reason about safety mechanically.

Use it with Claude Code

meshcore-mcp is a local-process server (stdio). The simplest consumer is a human operator with Claude Code: add one entry to your MCP configuration, pointing it at a node, and operate the mesh in plain language — Claude Code prompts before every tool call, so no policy layer is needed (see the warning above).

Pick the config that matches how your node is flashed. A MeshCore node runs either a WiFi companion firmware (companion_radio_wifi, reached over TCP/IP) or a USB/serial companion firmware — one transport per build. Set the matching variable; setting both is rejected at startup with a legible error.

WiFi / IP companion — point it at the node's address (default port 5000):

{
  "mcpServers": {
    "meshcore": {
      "command": "npx",
      "args": ["-y", "@dpup/meshcore-mcp"],
      "env": {
        "MESHCORE_HOST": "192.168.1.50",
        "MESHCORE_PORT": "5000"
      }
    }
  }
}

USB / serial companion — point it at the device path (/dev/ttyACM0 on Linux, /dev/tty.usbmodemXXXX on macOS, COM3 on Windows):

{
  "mcpServers": {
    "meshcore": {
      "command": "npx",
      "args": ["-y", "@dpup/meshcore-mcp"],
      "env": {
        "MESHCORE_SERIAL_PATH": "/dev/ttyACM0"
      }
    }
  }
}

Serial needs Node's native serialport bindings — the published npx / binary path runs on Node, so this just works (the bun dev entrypoint can't load the serial native module).

The server reads its home node and credentials from the environment its launcher hands it; a malformed or missing config exits non-zero with a legible message (never a stack trace).

Variable

Meaning

MESHCORE_HOST

Home node TCP host — the companion_radio_wifi path. Set this or MESHCORE_SERIAL_PATH, not both.

MESHCORE_PORT

TCP port for MESHCORE_HOST (default 5000).

MESHCORE_SERIAL_PATH

USB serial device path (e.g. /dev/ttyACM0) — the serial alternative to MESHCORE_HOST.

MESHCORE_LOGIN_PASSWORD

Default login/admin password for remote nodes (default "" — the guest password).

MESHCORE_LOGIN_PASSWORD_FILE

Read the default password from a file instead (its trailing newline is stripped). Set this or MESHCORE_LOGIN_PASSWORD, not both.

MESHCORE_NODE_PASSWORDS

JSON object of per-node overrides: { "rocky-ridge": "secret" } (keyed by node id or name).

MESHCORE_NODE_PASSWORDS_FILE

Read that same JSON map from a file instead — keeps node secrets out of the environment and your MCP config (chmod 600 it). Set this or MESHCORE_NODE_PASSWORDS, not both.

MESHCORE_REQUEST_TIMEOUT_MS

Device request timeout, ms (default 10000).

MESHCORE_TRAFFIC_CAPACITY

Recent-traffic ring-buffer size (default the buffer's own default).

MESHCORE_ADMIN_REPLY_TIMEOUT_MS

How long the remote-admin path waits for a CLI reply, ms (default 15000).

The --host, --port, and --serial flags override the corresponding env vars.

Try it with no radio (simulator)

No MeshCore hardware? examples/sim-server.ts serves the exact same MCP surface over stdio, but backed by @dpup/meshcore-sim over a small simulated mesh — with a real-time clock so live traffic actually flows while you poke at it. Point Claude Code at it:

git clone https://github.com/dpup/meshcore-mcp && cd meshcore-mcp && bun install
claude mcp add meshcore-sim -- bun "$(pwd)/examples/sim-server.ts"

Then ask Claude to "survey the mesh", "check the health of Rocky Ridge", "show recent traffic", or "preview an admin reboot of Rocky Ridge". The production binary (src/cli.ts) talks only to real devices; the simulator is a dev dependency and never ships — this entrypoint is the hardware-free way to try the server. Remote admin execution round-trips too: the sim-server configures reactive responders (meshcore-sim ≥ 0.2.0), so login → CliData → reply returns a plausible CLI reply instead of timing out.

Related MCP server: @lex-tools/codebase-context-dumper

The surface

A short, action-oriented surface: a handful of well-shaped tools beats dozens of fine-grained ones. Reads are exposed as read-only tools (not only as resources) because tools are what every MCP client reliably surfaces to the model for active querying. The login / logout handshake is never exposed — it is mechanical, and lives inside the tools that need it.

Tools

Tool

Intent

Annotations

get_node_health(node?)

Consolidated health snapshot — identity, radio, battery, uptime/queue, packet/radio stats. Omit node for the home node; pass a contact name or hex key prefix for a remote. Hides the home-vs-remote distinction and any remote login.

read-only · idempotent

survey_mesh()

One roster of the home node and every known contact, with advertised role and last-heard time — for spotting quiet or missing nodes.

read-only · idempotent

get_recent_traffic(since?)

Recent live mesh traffic from the rolling buffer, oldest→newest, each tagged with structural provenance. since (ISO-8601 or epoch-ms) windows it.

read-only · idempotent

send_message(target, text)

Transmit a message to a contact (name / hex prefix) or channel (#name, #index, or a bare index).

action · not idempotent — a resend is a second transmission

admin(node, command, params?, dryRun?)

Run one enumerated admin command against a node, home or remote; collapses the remote login → CliData → reply handshake. dryRun: true previews the intent without contacting the device.

destructive (statically conservative); per-command risk tier in the result

admin's command is drawn from an enumerated, curated set of 16 commands — see the guide — extensible, but never free-form text. Each command declares a risk tier (benign · config · sensitive · destructive) that maps deterministically to per-command annotations, surfaced in the structured result.

Resources

Resource

URI

Notes

Live mesh traffic

meshcore://traffic/live

Subscribable. A rolling feed of inbound events; the server pushes notifications/resources/updated to subscribers, who then re-read to fetch. Every event carries provenance — sender, channel identity, and decrypt-verification.

Mesh roster

meshcore://nodes

Pull-style: the home device plus every known contact, each with role and last-heard time.

Contacts

meshcore://contacts

Pull-style: the home node's stored contact list — name, public key, role, location, last-heard.

Decrypt-verification is structural, not a wire flag: meshcore-ts distinguishes a verified channelMessage from an unverified channelData datagram (and a direct contactMessage from either), and the stream preserves that distinction. That is exactly what lets a downstream gate's negative cases be representable — an unverified admin-channel datagram never surfaces as a verified channel message.

Prompts

Prompt

Args

Frames

morning-mesh-check

—

A daily health sweep: survey the roster, flag quiet nodes, spot-check the suspicious ones.

diagnose-quiet-node

node

Work out why a node has gone quiet — health, recent traffic, last-heard, context.

draft-outage-notice

node, window?

Draft a concise outage notice over a time window, and send it on approval.

A prompt frames; it does not freeze: each poses a well-formed task and points the agent at the real tools by name, and the agent still reasons freely. No policy, no secrets.

Install

meshcore-mcp is an MCP server, not a library you import — "installing" it means registering its launch command with an MCP client.

  • Claude Code — claude mcp add (see Use it with Claude Code above). The client launches the server on demand via npx; nothing to install globally.

  • Any other MCP client (Cursor, Windsurf, …) — put the same command in that client's MCP config: npx -y @dpup/meshcore-mcp, with the MESHCORE_* env vars above.

  • Prefer a pinned, on-PATH binary? Install it globally and point the client's command at meshcore-mcp:

    npm i -g @dpup/meshcore-mcp      # or: bun add -g / pnpm add -g @dpup/meshcore-mcp

ESM-only, Node.js ≥ 18.

Embedding the server in your own host (the createServer API)? Add it as a dependency instead — npm install @dpup/meshcore-mcp — and see Quickstart — embed a sim-backed server.

Documentation

  • Guide — concepts and recipes: configuring the home node, the tool surface and annotations, the live stream and provenance, the admin set and dry-run, the three consumers, and testing your own agent against a sim-backed server.

  • API reference — the complete, generated reference: every exported symbol, with full signatures and types.

Quickstart — embed a sim-backed server

You can drive the whole server in-process with no hardware, against @dpup/meshcore-sim: inject a SimConnection where production would build MeshCoreClient.tcp(host, port), drive a virtual SimClock, and call tools through a real in-memory MCP Client. Nothing below MeshService can tell a sim from a radio — this is the seam the whole test strategy hangs on.

Add it as a dependency first — npm install @dpup/meshcore-mcp — then:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { MeshCoreClient } from "@dpup/meshcore-ts";
import { SimClock, SimConnection, defineWorld, node, contact } from "@dpup/meshcore-sim";
import { createServer, MeshService } from "@dpup/meshcore-mcp";

// 1. A simulated mesh — no radio. The home node plus one contact.
const world = defineWorld({
  homeNodeId: "home",
  nodes: [node("home", { name: "Base" }), node("alice", { name: "Alice" })],
  contacts: [contact("Alice", "alice")],
});

// 2. The injected clock the whole server takes its time from.
const clock = new SimClock();

// 3. A real MeshCoreClient over the sim Connection — the production seam.
const meshClient = new MeshCoreClient(new SimConnection({ world, clock }).asConnection(), {
  autoSync: true,
});
const service = new MeshService(meshClient, clock);
await service.start();

// 4. Wire createServer to an in-memory MCP Client (no sockets, no stdio).
const server = createServer({ service });
const client = new Client({ name: "demo", version: "0.0.0" });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);

// 5. Call a tool exactly as Claude Code would.
const health = await client.callTool({ name: "get_node_health", arguments: {} });
console.log(health.structuredContent);

await client.close();
await server.close();
await service.stop();

In production, cli.ts does the same wiring over MeshCoreClient.tcp(host, port) and a SystemClock, served on a StdioServerTransport — that's what the meshcore-mcp binary (and the Claude Code config above) launches.

See it in action

examples/demo.ts is a guided tour — it builds a world, drives the real server through an in-memory MCP Client, and walks the whole feature set: tools (home + an offline repeater failing cleanly), the live stream with verified vs. unverified provenance side by side, and an admin dry-run preview then a scripted exec. No hardware, fully deterministic:

bun examples/demo.ts          # or --seed <n> / a positional seed

In a real terminal the tour is ANSI-colored; re-run it with any --seed and the output is identical every time.

Develop

bun install
bun run typecheck   # tsc --noEmit (strict)
bun run test        # vitest — incl. full-stack tests over a sim-backed server
bun run build       # emit dist/ (ESM + .d.ts + the meshcore-mcp binary)
bun run dev         # run the server over stdio
bun run docs        # regenerate docs/api.md from the source

See AGENTS.md for architecture and contribution notes.

Design notes

  • Two contracts, one server. meshcore-mcp is a thin server wedged between the MCP protocol above (@modelcontextprotocol/sdk's McpServer) and the @dpup/meshcore-ts device client below. It owns neither. Almost every design decision falls out of taking both seriously.

  • Injected client + clock. MeshService takes its MeshCoreClient and Clock by injection — production builds MeshCoreClient.tcp(host, port) with a SystemClock; tests build new MeshCoreClient(sim.asConnection()) with a SimClock. Nothing below MeshService knows which it got, and time always comes from the clock — never raw Date.now() / setTimeout.

  • Provenance is a hard requirement. The live stream carries each event's sender, channel identity, and structural decrypt-verification, because a downstream policy layer can only function if it knows which channel a message genuinely arrived on.

  • Ungated by design. No policy, no admin-channel gate, no scheduling, no autonomous behavior. That belongs to a consumer; keeping the server free of it is exactly what lets the same artifact serve a human at a terminal and an autonomous agent without compromise.

License

MIT © Dan Pupius

Available Tools

30 tools
adminRun an admin commandA
Destructive

Run one enumerated admin command against a node. node is the home node (its name or key prefix) or a remote repeater (name / hex prefix). Pass dryRun: true to preview the intent without contacting the device. Each command carries a risk tier (surfaced in the result). Commands: Read: • ver [remote] — no params • board [remote] — no params • clock [remote] — no params • neighbors [remote] — no params • get-config [remote] — key: config key to read; prv.key is intentionally excluded (firmware blocks remote read for security)

Benign: • advert [home+remote] — mode: flood (mesh-wide) or zerohop (neighbours only) • sync-time [home+remote] — no params • log-start [remote] — no params • log-stop [remote] — no params • set-time [remote] — epochSecs: absolute time as epoch seconds (firmware rejects backwards clocks) • discover-neighbors [remote] — no params

Config: • set-tx-power [home+remote] — dbm: transmit power in dBm, 1–22 • set-radio [home+remote] — freqMhz: centre frequency in MHz (e.g. 910.525); kHz/Hz also accepted, bwKhz: bandwidth in kHz (sub-GHz: 62.5, 125, 250, 500; 2.4GHz/SX128x goes higher, up to 1000); Hz (62500) also accepted, sf: spreading factor, 5–12, cr: coding rate 5–8 (the 'n' in 4/n); '4/5' also accepted • set-name [home+remote] — name: advertised name, ≤32 bytes • set-location [home+remote] — lat: latitude in degrees, lon: longitude in degrees • set-repeat [remote] — enabled: true to enable packet repeating • set-dutycycle [remote] — percent: duty-cycle limit, 1–100 (percent) • set-path-hash-mode [remote] — mode: path-hash mode 0|1|2 (1-byte/2-byte/3-byte advertised prefixes; 64/32/21 flood cap respectively) • set-loop-detect [remote] — level: loop-detection aggressiveness • set-flood-max [remote] — hops: max flood hop count, 0–64 • set-radio-rxgain [remote] — enabled: true to enable boosted RX gain • tempradio [remote] — freqMhz: centre frequency in MHz (e.g. 910.525); kHz/Hz also accepted, bwKhz: bandwidth in kHz (sub-GHz: 62.5, 125, 250, 500; 2.4GHz/SX128x goes higher, up to 1000); Hz (62500) also accepted, sf: spreading factor, 5–12, cr: coding rate 5–8 (the 'n' in 4/n); '4/5' also accepted, timeoutMins: auto-revert timeout in minutes (≥1) • set-tx-delay [remote] — factor: TX delay factor 0–2 • set-direct-tx-delay [remote] — factor: direct TX delay factor 0–2 • set-rx-delay [remote] — secs: RX processing delay 0–20s • set-airtime-factor [remote] — factor: airtime factor 0–9 • set-interference-threshold [remote] — value: interference threshold (firmware-defined units) • set-agc-reset-interval [remote] — secs: AGC reset interval in seconds; firmware rounds to a multiple of 4 (0 disables) • set-multi-acks [remote] — enabled: true to enable multi-ack support • set-flood-advert-interval [remote] — hours: flood-advert interval in hours, 3–168 • set-advert-interval [remote] — minutes: zero-hop advert interval in minutes, 60–240; firmware stores it as /2 (use even values) • set-owner-info [remote] — text: owner info shown in adverts; '|' in the string becomes a newline • set-adc-multiplier [remote] — value: ADC multiplier 0.0–10.0 (0 = board default) • powersaving [remote] — enabled: true to enable sleep-between-TX power saving • region [remote] — sub: status | save | allowf (region: region name (prefix-matched)) | denyf (region: region name (prefix-matched)) | get (region: region name (prefix-matched)) | home-get | home-set (region: region name to set as home) | default-get | default-set (region: region name to set as default; use '' to clear) | put (name: new region name, parent: optional parent region; defaults to wildcard) | remove (region: region name (exact match)) | list-allowed | list-denied • gps [remote] — sub: status | on | off | sync | setloc | advert-get | advert-set (policy: none | share | prefs) • sensor [remote] — sub: get (key: sensor setting key to read) | set (key: sensor setting key to write, value: new value (string-typed; firmware parses per setting)) | list (startIndex: optional pagination start index (134-char chunks))

Sensitive (writes secrets / grants access): • set-admin-password [remote] — password: new admin password, ≤15 chars • set-permission [remote] — pubKey: hex public key, level: guest|read|readwrite|admin, or null to revoke (downgrade to guest — the firmware has no explicit removal, only a level-0 demotion) • set-allow-read-only [remote] — enabled: true to enable read-only mode • set-guest-password [remote] — password: new guest password, ≤15 chars

Destructive (data/identity loss or reboot): • reboot [home+remote] — no params • log-erase [remote] — no params • clear-stats [remote] — no params • remove-neighbor [remote] — pubKeyPrefix: hex public-key prefix • set-private-key [remote] — hex: new private key as 64 hex chars (32 bytes) • start-ota [remote] — no params • clkreboot [remote] — no params

Inputs: node (required), command (required), params, dryRun. Returns: command, tier, annotations, dryRun, +3 more

broadcast_advertBroadcast an advertA

Send an advert from a node — home or remote. mode selects between flood (mesh-wide) and zerohop (immediate neighbours only). Omit node to target home. Equivalent to admin <node> advert { mode }. Costs airtime; flood propagates mesh-wide.

Inputs: mode, node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

delete_channelDelete a channelA
DestructiveIdempotent

Remove a channel slot on the connected node, addressed by index or by name. List channels via the meshcore://channels resource.

Inputs: index, name. Returns: index, name

discover_neighborsTrigger a node's neighbour discoveryA

Broadcast a node-discovery request from a remote repeater. Replies populate its neighbour list — read it back with get_node_neighbors(node). Equivalent to admin <node> discover-neighbors. Required node.

Inputs: node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

export_contactExport a contactA
Read-onlyIdempotent

Return the advert-packet bytes (hex) for a contact, suitable for another node's import_contact. Omit target to export the home node's own advert (useful for sharing identity off-mesh).

Inputs: target. Returns: name, publicKey, advertHex

forget_credentialForget a node's stored admin passwordA
DestructiveIdempotent

Remove the server's stored login password for node. Subsequent admin / remote get_node_health calls fall back to the env default credential (the guest password, unless MESHCORE_LOGIN_PASSWORD / MESHCORE_NODE_PASSWORDS is set). removed is false when no entry existed — the end state is the same either way. Marked destructive: the stored password is gone after this call.

Inputs: node (required). Returns: node, removed

get_node_boardRead a remote node's hardware boardA
Read-onlyIdempotent

Read a remote repeater's hardware board / model identifier. Equivalent to admin <node> board. Required node. For the home node, use get_node_health() (the firmware.manufacturerModel field) — that's the structured equivalent via the companion protocol, no admin call needed.

Inputs: node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

get_node_clockRead a remote node's current clockA
Read-onlyIdempotent

Read a remote repeater's current device clock (HH:MM - D/M/Y UTC). Equivalent to admin <node> clock. Required node. For the home node, use get_node_health() (the deviceTimeMs field) — that's the structured equivalent via the companion protocol, no admin call needed.

Inputs: node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

get_node_configRead a remote node's config valueA
Read-onlyIdempotent

Read one of a remote repeater's configuration values by key (e.g. tx, radio, name, freq, flood.max, path.hash.mode). Equivalent to admin <node> get-config { key }. Required node. The reply is a single line of text the agent parses. For the home node's radio / identity config, use get_node_health() (structured fields cover the common ones — radio, firmware, location, autoAddContacts).

Inputs: key (required), node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

get_node_healthGet node healthA
Read-onlyIdempotent

Consolidated health snapshot for a node: identity, radio, battery, uptime/queue, and packet/radio stats. Omit node for the connected home node; pass a contact name or hex public-key prefix for a remote repeater (logs in and reads its status).

Inputs: node. Returns: kind, node, publicKey, role, +13 more

get_node_neighborsRead a remote node's recent neighboursA
Read-onlyIdempotent

List a remote repeater's recent neighbours (up to 8), each as {pk-prefix}:{ts}:{snr*4} in the reply text. The H15 topology data source. Equivalent to admin <node> neighbors. Required node. Pairs with discover_neighbors(node) to trigger an active probe first.

Inputs: node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

get_node_versionRead a remote node's firmware versionA
Read-onlyIdempotent

Read a remote repeater's firmware version + build date string. Equivalent to admin <node> ver. Required node — only repeater firmware implements the ver CLI verb. For the home node, use get_node_health() (the firmware field) — that's the structured equivalent via the companion protocol, no admin call needed.

Inputs: node (required), dryRun. Returns: command, tier, annotations, dryRun, +3 more

get_recent_trafficGet recent trafficA
Read-onlyIdempotent

Recent live mesh traffic from the rolling buffer, oldest→newest, each tagged with structural provenance (kind + decrypt-verified). Optional since windows it — prefer a relative duration ("10m", "1h", "30s"), which is the portable form; an ISO-8601 datetime or epoch-ms also work but are wall-clock instants. Omit for all buffered traffic.

Inputs: since. Returns: events, count

import_contactImport a contactA
Idempotent

Add a contact to the local node's roster from its advert-packet bytes (hex). Typically obtained from another node's export_contact, a QR scan, or another out-of-band channel. The contact appears in survey_mesh and the meshcore://contacts resource after this.

Inputs: advertHex (required). Returns: imported, lengthBytes

reboot_nodeReboot a nodeA
Destructive

Reboot a node — the home companion or a remote repeater. Omit node to target home. Equivalent to admin <node> reboot; this top-level form carries the destructive-tier annotations directly. ⚠ The node is unreachable for ~30–60s while it restarts; any session ends.

Inputs: node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

remove_contactRemove a contactA
DestructiveIdempotent

Drop a contact from the local node's roster. Local-only — does not tell any other node. The contact may reappear if its advert is heard again under auto-add mode; combine with set_auto_add_contacts { autoAdd: false } for a sticky removal.

Inputs: target (required). Returns: name, publicKey

reset_pathReset cached path to a contactA
Idempotent

Clear the local cached forwarding path to a contact. The next direct send to that contact re-discovers the route. Useful when a known path has gone stale (a repeater rebooted, topology changed) and direct sends are failing or taking longer than expected.

Inputs: target (required). Returns: name, publicKey

send_messageSend a messageA

Transmit a text message. target is a contact (name or hex public-key prefix) or a channel (#name, #index, or a bare channel index). A resend is a second transmission — not idempotent. Set confirm: true to wait for the delivery ack and report whether it arrived + the round-trip time. Confirmation applies to direct (contact) messages only: a channel/broadcast has no single recipient to ack, so a confirm: true channel send returns confirmationNotApplicable: true (and no delivered) rather than silently ignoring the request.

Inputs: target (required), text (required), confirm. Returns: kind, contact, publicKey, channelIdx, +6 more

set_auto_add_contactsToggle auto-add for new contactsA
Idempotent

Set whether new contacts heard via adverts are added to the local roster automatically (autoAdd: true, the companion default) or only when explicitly imported (autoAdd: false). Manual mode pairs well with remove_contact for a curated contact list.

Inputs: autoAdd (required). Returns: autoAdd

set_channelAdd or update a channelA
Idempotent

Configure a channel slot on the connected node. Omit secret to generate a random private channel; omit index to use the next free slot (so a plain add won't overwrite an existing channel). Returns the channel's key so you can share it. List channels via the meshcore://channels resource.

Inputs: name (required), secret, index. Returns: index, name, secret

set_contact_pathPin an explicit path to a contactA
Idempotent

Set an explicit forwarding path (a sequence of repeater path-hash bytes, up to 64) to a contact. Advanced: only when you want to override automatic path discovery. Pass pathHex: "" to mark the contact as direct (no repeaters). Pair with trace_path to discover a working path first.

Inputs: target (required), pathHex (required). Returns: name, publicKey, pathHex

set_credentialRemember a node's admin passwordA
Idempotent

Persist the password the server will use to log into node for subsequent admin and remote get_node_health calls. Use this when you've learned a repeater's password out of band (e.g. via DM) and need the server to remember it across restarts. This is the server's local credential, not the node's password — it does not send anything over the mesh. The node must already be known (in the contact list) — a typo'd name is rejected so credentials don't silently land under a key that's never looked up. Overwrites any existing entry. The password (max 256 chars) is stored in the credentials file under MESHCORE_STATE_DIR with 0600 permissions, and is never echoed in the tool result. The password is opaque to the server: the device unlocks guest (read-only) or admin (full) depending on which it matches — store the admin password when you have it, since it covers reads too. If a later admin command comes back with ERR: in reply, the stored credential is likely guest-tier; overwrite via set_credential if you have the admin password. Separate from the set-admin-password admin command (which changes the node's own password).

Inputs: node (required), password (required). Returns: node, stored

set_node_locationSet a node's advertised locationA
Idempotent

Set a node's advertised lat/lon (decimal degrees). Omit node to target home. Equivalent to admin <node> set-location { lat, lon }.

Inputs: lat (required), lon (required), node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

set_node_nameSet a node's advertised nameA
Idempotent

Rename a node's advertised mesh name. Omit node to target home. Equivalent to admin <node> set-name { name }. Max 32 bytes (24 if a location is set).

Inputs: name (required), node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

set_radioSet a node's radio parametersA
Idempotent

Set frequency (MHz), bandwidth (kHz), spreading factor, and coding rate on a node. Omit node to target home. Equivalent to admin <node> set-radio { freqMhz, bwKhz, sf, cr }. ⚠ Applies after a reboot; if the new params no longer match the rest of the mesh, the node drops off the network.

Inputs: freqMhz (required), bwKhz (required), sf (required), cr (required), +2 more. Returns: command, tier, annotations, dryRun, +3 more

set_tx_powerSet a node's transmit powerA
Idempotent

Set the radio transmit power in dBm on a node. Omit node to target home. Equivalent to admin <node> set-tx-power { dbm }. ⚠ Confirm the value is legal for your band/region; some boards add a PA stage on top of the configured dBm.

Inputs: dbm (required), node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

share_contactShare a contact mesh-wideA

Broadcast a contact's advert across the mesh. Other nodes that receive the advert can route to this contact without having heard its own transmissions. Costs airtime.

Inputs: target (required). Returns: name, publicKey

survey_meshSurvey the meshA
Read-onlyIdempotent

List the home node and every known contact, with each contact's advertised role and last-heard time — a roster for spotting quiet or missing nodes.

Inputs: no named parameters. Returns: home, contacts

sync_clockSync a node's clockA

Set a node's clock to the controller's current time. Omit node to target home. Equivalent to admin <node> sync-time. No-op if already in sync. For an explicit epoch on a remote repeater, use admin <node> set-time { epochSecs } instead.

Inputs: node, dryRun. Returns: command, tier, annotations, dryRun, +3 more

trace_pathTrace a mesh pathA

Trace a route through the mesh and report each repeater hop's SNR. Give an explicit path of repeater hops (comma-separated hex bytes, e.g. "23,5f,3a") or a node (a contact name / hex prefix) to trace along its known out-path. A path that doesn't respond returns a timeout.

Inputs: path, node. Returns: completed, hopCount, hops, lastSnr

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 30 tools

    Disambiguation2/5

    Many dedicated tools overlap with subcommands of the monolithic `admin` tool, causing ambiguity. For example, `get_node_board`, `get_node_clock`, `broadcast_advert`, `discover_neighbors`, and several `set_*` tools have direct equivalents in admin subcommands, making it unclear when to use which.

    Naming Consistency4/5

    Most tools follow a consistent verb_noun pattern (e.g., get_node_*, set_*, send_message). The only outlier is `admin`, which is a generic verb and does not fit the pattern. Overall, naming is predictable and clear.

    Tool Count3/5

    With 30 tools, the count is on the higher side but still reasonable for the complexity of mesh network management. Some granularity could be reduced, but the tools are logically grouped. Agents may find the list lengthy but manageable.

    Completeness4/5

    The tool set covers a wide range of operations: health monitoring, configuration, messaging, contact management, and path tracing. Missing operations (e.g., OTA, permission management) are accessible via the `admin` tool, so no critical gaps exist.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers