idm-mcp
An MCP server for read-only inspection of an ISOTEL IDM gateway, its devices, parameters, history, protocol stacks, streams, and connections.
Describe the gateway and list active/inactive devices, optionally filtering by brand.
Inspect a device’s connection state, port, endpoints, last update, and status counters.
Read protocol/stream stacks for a device and list TCP streams and physical connections with link counters.
Fetch the gateway’s own HTML device description, including sections, units, live values, and diagnostics.
Read the gateway activity log.
Read raw bytes from a named stream without permanently attaching.
Discover parameter paths; read flattened parameter values, whole substructures, or one parameter with options.
Query recorded parameter history with gateway-side aggregation modes: regular, scale, average, calc, and interpolation.
This schema is read-only: no write, stream-open, serial-open, or parameter-setting tools are exposed.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@idm-mcp list the devices connected to my ISOTEL IDM gateway"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
idm-mcp
MCP server for an ISOTEL IDM smart gateway. It exposes the gateway's devices, parameters, records, protocol stacks, streams and connections to an MCP client (Claude Desktop, Chatbox, an agent, anything speaking MCP).
Read-only by default - 14 tools that only GET. Writing is opt-in:
--allow-write registers the write tools, IDM_WRITABLE names the parameters
that may be changed, and every value is validated against the gateway's own
options (read-only flag, allowed values, numeric range) before anything is sent.
Built on isotel-idm and fastmcp;
target gateway API 1.3.845.
Workflow
idm_list_devices— what is attached.idm_get_device_htmlfor any device you are about to read or change. It is the description layer: sections (System, Acquire, Trigger, Channels, ...), units, live values and free-text diagnostics, with every value tagged by its parameter name. It honours the access level, so it grows from 140 to 195 parameters with--enable-devel --enable-hidden.Then the value tools.
Skip step 2 only when the user has already named a specific parameter and value
— the intent is unambiguous then. Otherwise describe first and let the sections
inform which parameters matter; don't infer meaning from a parameter name alone.
The write tools return device_described and a hint when it is false.
Related MCP server: Remote Link Local MCP
Tools
Tool | Returns |
| host, API version, mode, group, active and known device counts |
|
|
|
|
| protocol stack layers, plus streams with |
| every TCP stream with its name, port, clients, rx/tx |
| every PHY connection with state and link counters, including one with no device |
| the gateway's own HTML page for a device — the description step; honours the access level |
| recent gateway activity log — the receipt for a write |
| parameter paths only — for discovery |
| flattened |
| a whole substructure in one request, e.g. |
| one parameter: value, unit, accuracy, timestamp; with |
| recorded samples, aggregated by the gateway |
device accepts the root (device0), the full name (spaces or underscores) or
an unambiguous substring. Ambiguity is refused with the candidate list rather
than silently picking the first device. Parameter paths use slashes (ec/E);
dots (ec.E) are accepted too.
idm_list_parameters reports variables of a device that wraps them in a data
container as data/ch/PGA_in/PGA_in0, but the gateway addresses that variable as
ch/PGA_in/PGA_in0. The tools accept either and strip the container, so paths
from idm_list_parameters can be used as-is. prefix filters match through the
container too: prefix="sys" selects data/sys/uptime.
Every value carries timestamp, the gateway's UTC time of the sample — compare
it across polls before believing that anything changed.
History modes
The gateway does the aggregation, so a month or a day costs one call:
Arguments | Mode | Result |
| regular | N samples ( |
| scale | N points spanning the span, each with |
| average | N points averaged over 3600 s intervals, with |
| calc | one aggregate over the whole record set ( |
average is an interval duration in seconds — the Python library's docstring
calls it a record count, which is wrong. max_points and scale are mutually
exclusive; average and interpolate require scale.
Streams
idm_get_protocols flattens the protocol stack of the port a device sits on:
PHYSICAL -> VIRTUALPHY -> MESSAGE -> CUSTOM, with each layer's rx/tx
counters. A layer that carries a tcp_port is a stream served by the
gateway — connect to <gateway_host>:<tcp_port> for the raw byte stream:
"streams": [{"name": "UART 115200-8E1 to Amplifier", "layer": "CUSTOM",
"tcp_port": 33100, "gateway_host": "gateway", "clients": []}]Only active devices have a connection port, so this needs one. full=True
returns the tree exactly as the gateway sends it.
Write mode
Read-only is the default and the write tools are then not registered at all.
Enable them with --allow-write (alias --allow-post) or IDM_ALLOW_WRITE=1:
python -m idm_mcp.server --transport http --port 8003 --allow-writeTool | Effect |
| sends a terminal command ( |
| gateway dials the stream: |
| closes it: |
| opens a serial port or preset ( |
| closes a serial connection by id ( |
| writes one parameter — only if allow-listed (see below) |
| writes several parameters in one POST — the struct |
Opening a stream makes the gateway connect to it. idm_open_stream sends
openTCPConnection(host, port); the gateway dials its own endpoint and creates a
PHY connection named <host>:<port>. If a device stack sits behind that stream,
it loads as a new device — the stacking case: opening TestPort's
UART 115200-8E1 to Amplifier brings up a second IOLite-4CFB- (same serial)
over that UART, and idm_close_stream (closeConnection("host:port")) unloads
it again. The host defaults to localhost because the gateway dials itself;
override with IDM_STREAM_HOST. idm_read_stream is the different operation: it
attaches from this host to read raw bytes, and detaches afterwards.
Every write returns its activity entries. The gateway's response body is
just {"result": "OK"|"Error"|"null"}, so a command's real outcome comes from
GET /me/activity: the client snapshots the newest log timestamp before the
POST and returns entries logged after it. A Command not found or
Exception while executing entry makes the stream/connection tools raise
instead of reporting a silent no-op.
Command names carry no $ prefix on the wire — help, not $help.
Device parameter writes are deliberately not implemented: they need an explicit allowlist of parameters and are not part of this surface yet.
Access level
The gateway hides whole sections behind three access flags, and the client passes your setting both when it opens the device and on every read, so the level can never differ between one call and the next:
python -m idm_mcp.server --enable-advanced --enable-hidden --enable-develFlag | Env | Reveals |
|
| advanced sections |
|
| hidden sections — |
|
| development sections — a substructure GET like |
All three are off by default, which is the gateway's own narrow view. On
device4 the default shows 142 parameters and cfg comes back empty; with all
three it shows 195 and cfg returns {"rsv": "0x28", "release": "2"}.
Parameter writes
Two write paths, same allowlist and same validation: idm_set_value for one
variable, idm_set_structure for a whole section in a single POST.
Substructures keep their wrapper key. get("") on ch/PGA_in answers
{"status", "PGA_in": {...}, "time"}. Send that back minus status and time,
wrapper included. An unwrapped payload is accepted and wrapped automatically,
because the gateway otherwise applies the first value to every sibling — all
four channels took the same gain when I tried it. After the POST the tool
re-reads the section and returns verified plus mismatch, so a write that did
not take is visible rather than assumed.
idm_set_value refuses everything unless the parameter is on an allowlist you
define, and the allowlist lives outside the code:
IDM_WRITABLE="device4:ch/PGA_in/PGA_in0,device4:ch/ADC/ADC0,*:ch/range/*" \
IDM_ALLOW_WRITE=1 pm2 restart idm-mcp --update-envEach entry is device:parameter where device is a root (device4), a full
name, or *; parameter is a path, optionally ending in * for a prefix. An
empty allowlist means nothing is writable.
Validation before the POST uses the gateway's own options for that parameter
(read them with idm_get_value(..., options=True)):
readOnly: true→ refused;enum → the value must be in
allowedValues, matched case-insensitively and sent in canonical spelling (vin→Vin);numeric → must parse, and respect min/max when the gateway reports them.
The payload is the gateway's own shape, {"<variable>": {"value": "..."}},
POSTed to the parameter's URL.
Freshness
idm_get_value, idm_get_parameters, idm_get_structure and
idm_list_parameters return the gateway's last stored sample unless
fresh=True, which appends after=now to the GET and makes the gateway query
the device (one round-trip over USB/Bluetooth, so slower). Use it on every poll
whose point is to observe change — the gateway will otherwise happily serve the
same cached sample indefinitely.
Install
pip install -e . --no-deps--no-deps because isotel-idm and fastmcp are already in that venv.
Run
stdio — the client spawns the process:
~/.venv/bin/idm-mcp # or: python -m idm_mcp.serverHTTP — long-running, e.g. under pm2:
python -m idm_mcp.server --transport http --host 127.0.0.1 --port 8003 --path /mcpEnds up at http://127.0.0.1:8003/mcp.
Environment
Variable | Default | Meaning |
|
| gateway base URL |
|
| group ( |
|
| request timeout, seconds |
| unset | basic auth, if the gateway needs it |
| unset (off) | register the write tools ( |
| unset (nothing) | allowlist of |
| unset (off) | access level; same as |
|
| host the gateway dials for |
|
| command names used by |
|
| transport defaults |
|
| interpreter used by |
| unset |
|
Command line
Every setting can also be given as a flag; a flag wins over its environment variable.
Flag | Equivalent |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IDM_WRITABLE deliberately has no flag: an allowlist belongs in one place,
not on a command line where it can be mistyped or silently lost from the pm2
environment.
CLI flags override the environment.
Client configuration (stdio)
{
"mcpServers": {
"isotel-idm": {
"command": "/path/to/venv/bin/idm-mcp",
"env": { "IDM_GATEWAY": "http://localhost:33000", "IDM_GROUP": "me" }
}
}
}Protocol era (mode)
FastMCP 4.x negotiates one of two protocol eras per connection: legacy
(initialize, 2025-11-25) or the modern server/discover era (2026-07-28).
mode is a client-side setting — Client(..., mode="auto"|"legacy"|version);
the server has no equivalent switch and answers whichever era the client asks
for.
Both work against this server over stdio, verified with fastmcp 4.1.2:
mode="auto" settles on 2026-07-28, mode="legacy" on 2025-11-25, both list
14 tools and execute calls. So legacy is not required — if a client's auto
fails here, that is its probe, not this server: auto sends server/discover
first and falls back to initialize only for an RPC error, so a client that
times the probe out or that predates the fallback reports a connection failure
while legacy still succeeds.
pm2
pm2 start ecosystem.config.cjs
pm2 logs idm-mcp
pm2 restart idm-mcp
pm2 delete idm-mcpIDM_ALLOW_WRITE=1 IDM_GATEWAY=http://gateway:33000 pm2 restart idm-mcp --update-envecosystem.config.cjs runs the HTTP transport on 127.0.0.1:8003/mcp with
autorestart. It uses __dirname for its working directory and takes the
interpreter from IDM_PYTHON (default python3 on PATH), so start it with the
environment that has isotel-idm and fastmcp installed:
IDM_PYTHON=/path/to/venv/bin/python pm2 start ecosystem.config.cjsTest
~/.venv/bin/python -m pytest # 111 unit tests, no gateway needed
IDM_LIVE=1 ~/.venv/bin/python -m pytest # + 6 tests against the real gateway
~/.venv/bin/python scripts/smoke_http.py # exercises a running HTTP servertests/test_server.py fails if any write operation (set, post, delete,
user/group management, scripts) appears in the package source, and if the tool
set changes — read-only is a tested property, not a promise.
Behaviour worth knowing
Only active devices serve live telemetry.
idm_get_historyalso accepts inactive devices, because the gateway keeps their records. A gateway lists inactive entries for devices it knows but is not running (they have noroot), and those entries mirror active devices, so active always wins in resolution.Deviceobjects are cached per name, and oneGroupkeeps a keep-alive connection; the client is thread-safe. Device construction redirects stdout, becauseisotel.idmprints an inactive-device warning that would corrupt the JSON-RPC stream on the stdio transport.isotel-idmpins the exact API revision (1.3.845). A gateway firmware update that bumps the revision makes the library refuse to connect; upgradingisotel-idmis then required.Not included: service/user/group management, script execution, virtual devices, firmware upgrade and
show/aggrgroup-link vertical aggregation.
Requirements
Python 3.10+, fastmcp, and isotel-idm 1.3 for a gateway running API 1.3.
The library enforces an API major-version match when it opens the group, so
isotel-idm 1.2 installs fine but refuses to connect to a 1.3 gateway
(Major API versions not compatible, gateway: 1.3 library: 1.2).
PyPI currently publishes up to 1.2, so the dependency is declared >=1.2:
that keeps pip install -e . valid while 1.3 is distributed privately — an
existing 1.3 install already satisfies the requirement and pip will not
downgrade it. The pin moves to >=1.3 once 1.3 is on PyPI.
License
LGPL-3.0-only, matching isotel-idm. See LICENSE.
Available Tools
14 toolsidm_device_statusIdm Device StatusC
Connection state of one device: state, port, endpoints, last update time.
Inputs: device (required). Returns: object
idm_gateway_infoIdm Gateway InfoA
Describe the gateway: host, API version, mode, group and device count.
Inputs: no named parameters. Returns: object
idm_get_activityIdm Get ActivityA
Read the gateway's recent activity log.
This is the real receipt for a write: the gateway reports rejected commands here as "Command not found: ...".
Inputs: limit. Returns: object
idm_get_device_htmlIdm Get Device HtmlA
Start here: fetch the gateway's own HTML page for a device.
This is the description layer. It groups parameters into sections (System, Acquire, Trigger, Channels, ...), shows units, live values and free-text diagnostics (status, message, error number), and every value carries its parameter name - so it is how you learn what a device is and what its parameters mean before reading or writing them.
It honours the server's access level: with development/hidden enabled the page grows (140 -> 195 parameters on this gateway). Fetching it marks the device as described, which the write tools report back.
Inputs: device (required), max_bytes. Returns: object
idm_get_historyIdm Get HistoryA
Read recorded samples of one parameter, aggregated by the gateway.
Four modes, chosen by which arguments are set:
regular (default) - max_points samples from the span.
scale - exactly scale points spanning the span, each with m records.
average - scale plus average: interval duration in SECONDS; adds n, sd.
calc - one aggregate over the whole record set, e.g. "avg".
Also works for inactive devices: the gateway keeps their records.
Inputs: device (required), parameter (required), calc, scale, +5 more. Returns: object
idm_get_parametersIdm Get ParametersB
Read current values of a device's parameters, flattened to path -> {value, unit}.
Inputs: device (required), fresh, names, prefix. Returns: object
idm_get_protocolsIdm Get ProtocolsA
Show the protocol and stream stack of the port a device is attached to.
The ISN stack is a compound object tree: MESSAGE / TRANSPORT / FRAME /
USER / CUSTOM layers can nest to any depth, and new protocol types appear
without notice. Layers are returned flattened, each with its path.
A layer carrying a tcp_port is a stream: connect to
<gateway_host>:<tcp_port> for the raw byte stream.
Inputs: device (required), full. Returns: object
idm_get_streamsIdm Get StreamsB
List the TCP streams the gateway exposes, with their names.
De-duplicated across devices that share one connection port.
Inputs: device. Returns: object
idm_get_structureIdm Get StructureA
Fetch a whole sub-structure in one request (get("") on that path).
Use this instead of idm_get_parameters when you want the gateway's own
nested JSON for one section, e.g. "cfg" or "ch/PGA_in", including
hidden/development sections.
Inputs: device (required), path, options. Returns: object
idm_get_valueIdm Get ValueC
Read a single parameter, e.g. device="device0", parameter="ec/E".
Inputs: device (required), parameter (required), fresh, options. Returns: object
idm_list_connectionsIdm List ConnectionsA
List the gateway's physical connections with link counters.
Shows every connection the gateway holds - USB, serial or a TCP client it dialled for a stream - including one with no device behind it, which the device and stream views cannot represent.
Inputs: no named parameters. Returns: object
idm_list_devicesIdm List DevicesC
List the devices of this group.
Inputs: brand_filter, include_inactive. Returns: object
idm_list_parametersIdm List ParametersA
List a device's parameter paths without their values.
Inputs: device (required), prefix. Returns: object
idm_read_streamIdm Read StreamA
Read raw bytes from a stream.
Attaches to the stream for the read and detaches again unless it was already opened with idm_open_stream. Returns the bytes as hex and text.
Inputs: device (required), stream (required), seconds, max_bytes. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
Most tools target distinct resources (devices, connections, streams, activity, history), and the parameter-reading family (list_parameters, get_parameters, get_value, get_structure) is differentiated by descriptions. However, the four parameter tools plus the topology trio (get_protocols, get_streams, list_connections) require careful reading to avoid misselection.
All tools share the idm_ prefix and follow a mostly consistent verb_noun pattern (list_devices, get_parameters, read_stream, get_history). Minor deviations: idm_gateway_info and idm_device_status are noun-only, and get vs read both appear but are used sensibly.
14 tools is well-scoped for a gateway/device management server, covering topology, parameters, streams, and history without obvious redundancy. Each tool earns its place.
The read surface is thorough (info, devices, connections, streams, parameters, history), but the descriptions reference write tools and idm_open_stream that are not present in the tool list, implying missing write capability. The surface is read-only despite describing a read/write parameter workflow.
Maintenance
Related MCP Connectors
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Toolstile is a managed MCP gateway that connects remote MCP servers behind one endpoint, with per-agent permissions, shared accounts and activity logs. Connect at https://mcp.toolstile.com/mcp over Streamable HTTP with OAuth; sign in at https://toolstile.com, select a workspace and connect your upstream servers. Available tools, resources and prompts depend on that workspace and the agent's granted access.
Single-connection MCP gateway discovering and executing 490+ verified MCP & A2A tools.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables MCP clients to manage a self-hosted RPort fleet by listing and inspecting connected devices, opening and closing SSH tunnels, and executing commands through RPort agents.10MIT
- AlicenseAqualityBmaintenanceEnables MCP clients to access a local computer through a controlled, permission-based tool surface for read-only or developer-mode file, process, and terminal operations.6MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to securely reach and invoke tools on self-hosted machines through a remote gateway, using outbound-only device connections, OAuth or bearer authentication, per-device policies, and audit logging.8 npmMIT
- AlicenseAqualityCmaintenanceEnables MCP clients to read facilities, devices, live values, history, and alarms from a Cloud Studio Gear IoT account, filtered to the user's permissions and strictly read-only.6MIT