lumics-mcp
A Model Context Protocol (MCP) server that lets an AI assistant read and operate the Lumics network monitoring platform.
Not affiliated with Lumics or NetCuras. This is an independent, community-maintainedopen-source project. It is not affiliated with, sponsored by, endorsed by, or supported byLumics or NetCuras. No claim is made to their trademarks or other marks; "Lumics" and"NetCuras" are used here only to identify the platform this software talks to. For supportwith the Lumics platform itself, contact Lumics. For support with this server, open an issuehere.
Contents
- What it does
- Prerequisites
- Install
- Configuration
- Security
- Limitations
- Troubleshooting
- Tool reference
- Development
- Contributing
- License
What it does
The server exposes the documented Lumics REST API v1.0 surface as MCP tools, so an assistant cananswer questions about your monitored estate and — when you allow it — change it. Tools coverthese resource areas:
- Devices — list, read, create, update, delete; update a module's last-discovery time; bulkupdate (gated, off by default).
- Collectors — list, read, create, update, delete.
- Components and component types — list and read components of a given type, update acomponent, enumerate component types and device definitions.
- IPAM subnets — list, read, create, update, delete.
- IPAM addresses — list and read addresses within a subnet, create, update, delete.
- IPAM groups — list, read, create, update, delete, including parent/child grouping.
- Metrics — per-component metrics for a company or a device, single-item metrics, summarized(averaged or summed) metrics, and cross-device metric summaries.
- Identity — read the current user and company; revoke tokens (gated, off by default).
The underlying contract is captured verbatim in docs/reference/lumics-api-v1.md,which ships with the repository so you can audit what the code targets and see when the vendorAPI drifts from it.
Prerequisites
You need two things before installing:
- A Lumics account with access to the data you want the assistant to see. The server acts asyour user; it cannot see anything you cannot see.
- A JWT API token. Lumics issues JWT bearer tokens only — there is no OAuth.
You also want your company id, a 24-character hex value — but you do not need it in handbefore you start, because the server will help you find it. SeeFirst run: finding your company id.
Minting a token
The simplest route is to log into the Lumics web UI and visit /api/v1/me/token. You can alsoPOST your credentials:
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
--data '[email protected]&password=YOUR_PASSWORD' \
'https://app.lumics.io/api/v1/me/token?expiresIn=86400'
Replace [email protected] and YOUR_PASSWORD with your own credentials. The response is{ "token": "...", "expiresIn": 86400 }.
expiresIn is in seconds and defaults to 86400 (one day). The Lumics API accepts larger valuesand documents no maximum, so it is entirely possible to mint a token that effectively neverexpires. Do not. Use the shortest lifetime that is workable and rotate. See Security.
First run: finding your company id
LUMICS_COMPANY_ID is optional. Without it the server still starts, so you can use it todiscover the id:
- Configure the server with
LUMICS_TOKENonly, leavingLUMICS_COMPANY_IDunset, and connect itfrom your client. - Ask the assistant to call
lumics_get_me. It returns the user this token authenticates asand the company that user belongs to. The company id is the 24-character hex string. - Set
LUMICS_COMPANY_IDto that value and restart the server (and your client, if it cachesthe tool list).
Until the variable is set, only the tools that need no company are registered — lumics_get_me andlumics_get_device_definition_components, which is two tools (three, if you have enabledlumics_revoke_tokens). Everything else is company-scoped and is withheld from tools/list ratherthan left to fail on every call, so a partly-configured server looks small rather than broken. Theserver logs a warning on stderr saying exactly this.
lumics_get_device_metrics and lumics_get_device_item_metrics are in that withheld set eventhough their Lumics paths carry no company segment: they enforce the company pin by reading thedevice inside LUMICS_COMPANY_ID first, so without a configured company there is nothing for themto check against. See Cross-company access is off by default.
If you already know the id you can skip all of that and set it up front; the examples below do. Avalue that is supplied is validated at startup and must be 24 hex characters, so a typo failsimmediately instead of turning into a 404 mid-conversation.
You can also read the id out of the Lumics web UI URL after /companies/.
Install
Node.js 20 or newer is required. Every command below runs the published package with npx, sothere is nothing to clone or build.
Claude Code
claude mcp add lumics \
--env LUMICS_TOKEN=your-jwt-token \
--env LUMICS_COMPANY_ID=your-company-id \
--env LUMICS_READ_ONLY=1 \
-- npx -y @zenixsolutions/lumics-mcp
Two details matter here:
- The bare
--separatesclaude mcp add's own flags from the command it should run. Withoutit,npxand its arguments are parsed as flags toclaudeand the command fails. - By default the server is registered for the current project only. Add
-s userto make itavailable in every project:
claude mcp add lumics -s user \
--env LUMICS_TOKEN=your-jwt-token \
--env LUMICS_COMPANY_ID=your-company-id \
--env LUMICS_READ_ONLY=1 \
-- npx -y @zenixsolutions/lumics-mcp
Claude Desktop
Edit claude_desktop_config.json — Settings → Developer → Edit Config will open it, or find it at~/Library/Application Support/Claude/claude_desktop_config.json on macOS and%APPDATA%\Claude\claude_desktop_config.json on Windows. Add:
{
"mcpServers": {
"lumics": {
"command": "npx",
"args": ["-y", "@zenixsolutions/lumics-mcp"],
"env": {
"LUMICS_TOKEN": "your-jwt-token",
"LUMICS_COMPANY_ID": "your-company-id",
"LUMICS_READ_ONLY": "1"
}
}
}
}
Restart Claude Desktop afterwards. Note that this file stores your token in plaintext; treat itas a credential file.
VS Code
Add to .vscode/mcp.json in your workspace, or to your user settings.json under "mcp":
{
"servers": {
"lumics": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@zenixsolutions/lumics-mcp"],
"env": {
"LUMICS_TOKEN": "your-jwt-token",
"LUMICS_COMPANY_ID": "your-company-id",
"LUMICS_READ_ONLY": "1"
}
}
}
}
Cursor
Add to ~/.cursor/mcp.json for all projects, or .cursor/mcp.json inside a project:
{
"mcpServers": {
"lumics": {
"command": "npx",
"args": ["-y", "@zenixsolutions/lumics-mcp"],
"env": {
"LUMICS_TOKEN": "your-jwt-token",
"LUMICS_COMPANY_ID": "your-company-id",
"LUMICS_READ_ONLY": "1"
}
}
}
}
Codex
Add to ~/.codex/config.toml:
[mcp_servers.lumics]
command = "npx"
args = ["-y", "@zenixsolutions/lumics-mcp"]
[mcp_servers.lumics.env]
LUMICS_TOKEN = "your-jwt-token"
LUMICS_COMPANY_ID = "your-company-id"
LUMICS_READ_ONLY = "1"
ChatGPT and Grok — not yet supported in v0.1
Do not expect these to work today. This is a capability gap in those clients, not anoversight here, and no packaging trick works around it:
- ChatGPT developer-mode connectors and Grok custom connectors both connect to MCP servers over apublicly reachable HTTPS URL. Neither client can launch or talk to a local process overstdio, which is the only transport v0.1 ships.
- Grok additionally rejects
localhostand private/internal addresses outright, so tunnellingto a machine on your own network is not a workaround either. - The
mcp-remotebridge does not help. It is itself a local process; it does not make yourserver reachable from ChatGPT's or Grok's servers.
Support for both arrives with the v0.2 self-hosted HTTP deployment, where you run the serverbehind your own TLS-terminated HTTPS endpoint and point the client at your own URL. Thatdeployment is not released yet. Until it is, this section describes work that has not been done.
Client support matrix
| Client | v0.1 (stdio via npx) |
Notes |
|---|---|---|
| Claude Code | Supported | claude mcp add, -s user for all projects |
| Claude Desktop | Supported | claude_desktop_config.json |
| VS Code | Supported | .vscode/mcp.json |
| Cursor | Supported | .cursor/mcp.json |
| Codex | Supported | ~/.codex/config.toml |
| ChatGPT | Not supported | Needs public HTTPS endpoint — planned v0.2 |
| Grok | Not supported | Needs public HTTPS endpoint — planned v0.2 |
| Claude.ai web | Not supported | Needs public HTTPS endpoint — planned v0.2 |
Configuration
All configuration is by environment variable. The canonical, commented list is.env.example; this table mirrors it.
| Variable | Required | Default | Description |
|---|---|---|---|
LUMICS_TOKEN |
Yes | — | Lumics API token (JWT bearer). Sent as Authorization: Bearer <token>. |
LUMICS_COMPANY_ID |
No | unset | Your Lumics company id (24-character hex), used as the companies context id. Unset, the server starts with only the tools that need no company and logs a warning — see First run. Validated when supplied. |
LUMICS_BASE_URL |
No | https://app.lumics.io/api/v1 |
API base URL. Override only for a self-hosted or non-production Lumics. Must use https:, except for a loopback host (127.0.0.1, localhost, [::1]) — the token is a bearer credential sent on every request. Refused at startup otherwise. |
LUMICS_TIMEOUT_MS |
No | 30000 |
Per-request timeout in milliseconds. |
LUMICS_MAX_OUTPUT_CHARS |
No | 25000 |
Maximum characters of tool output — disclosure notes and JSON payload together, not the payload alone. Truncation is always disclosed in the response. |
LUMICS_LOG_LEVEL |
No | info |
Diagnostic verbosity on stderr: debug, info, warn, error, silent. debug adds a per-call record with duration, output size and truncation counts; silent quiets stderr entirely. Never touches stdout, which is the protocol channel. |
LUMICS_READ_ONLY |
No | unset (off) | Set to 1 to register only read tools. No create, update, delete, or admin tool is exposed to the model. Recommended. |
LUMICS_ALLOW_CROSS_COMPANY |
No | unset (off) | Set to 1 to let a tool call name a companyId other than LUMICS_COMPANY_ID. Off, such a call is refused with not_permitted. Read Security first. |
LUMICS_ENABLE_BATCH_UPDATE |
No | unset (off) | Set to 1 to expose bulk device update. One call can rewrite arbitrary fields on many devices. |
LUMICS_ENABLE_TOKEN_REVOCATION |
No | unset (off) | Set to 1 to expose token revocation. Revokes every token on your account. Read Security first. |
LUMICS_TRANSPORT |
No | stdio |
stdio is the only value 0.1.0 accepts. http is refused at startup and arrives in v0.2 — see The HTTP transport is not in 0.1.0. |
LUMICS_HTTP_PORT |
No (v0.2 only) | 3000 |
HTTP listener port. Inert in 0.1.0; documented for forward reference. |
LUMICS_HTTP_HOST |
No (v0.2 only) | 127.0.0.1 |
HTTP bind address. Loopback by default. Change only behind TLS and authentication. Inert in 0.1.0. |
LUMICS_HTTP_AUTH_TOKEN |
Yes when LUMICS_TRANSPORT=http (v0.2 only) |
— | Shared secret clients must present as Authorization: Bearer <value>. Minimum 32 characters, enforced at startup. Generate with openssl rand -hex 32. Inert in 0.1.0. |
LUMICS_HTTP_ALLOWED_HOSTS |
No (v0.2 only) | 127.0.0.1,localhost,[::1] plus the bind host |
Comma-separated Host allowlist for DNS-rebinding protection. Setting it replaces the default list, so repeat every loopback spelling you use — including [::1] — and the bind host. Inert in 0.1.0. |
LUMICS_HTTP_ALLOWED_ORIGINS |
No (v0.2 only) | empty (no origin allowed) | Comma-separated Origin allowlist for CORS. Inert in 0.1.0. |
LUMICS_CONTRACT_TESTS |
No | unset (off) | Maintainers only. Set to 1 to run tests that make real, read-only calls against a live tenant. |
The five LUMICS_HTTP_* variables are listed because they are validated at startup when you ask forthe HTTP transport — but 0.1.0 then refuses the transport itself, so setting any of them changesnothing about how this release runs. They are documented so a v0.2 deployment can be prepared andreviewed before it exists.
The server does not read a .env file
Configuration comes from the environment the process is given, and from nowhere else. Nothing herediscovers a .env. An earlier build did, using a relative path, which for a published server meantwhichever directory the MCP client happened to launch it from — so a file planted there couldredirect LUMICS_BASE_URL and send the token elsewhere, or reopen every safety gate the operator hadleft closed. See Security.
Two supported ways to use a .env, both of which are the operator choosing a file rather than theserver finding one:
Your MCP client's
envblock. Every install above already does this; nothing extra is needed.Node's own flag, for local development:
cp .env.example .env # then edit it node --env-file=.env dist/index.js--env-file=.envrequires the file to exist;--env-file-if-exists=.envtolerates its absence.
.env is gitignored and must never be committed.
Security
Read this section before you give an assistant a write-capable token.
Start read-only
LUMICS_READ_ONLY=1
This is the recommended default posture, and it is a registration-time control rather than aruntime check: with it set, create, update, delete, and admin tools are never advertised to themodel at all. A model cannot misuse a tool it cannot see. Turn it off only when you specificallyintend the assistant to modify your Lumics data, and prefer running two separately configuredserver entries — one read-only for everyday use, one write-capable for deliberate change work —over leaving writes permanently enabled.
Two operations are gated off by default
Both stay unavailable unless you opt in with an environment variable. A model cannot enable them.
LUMICS_ENABLE_BATCH_UPDATE— bulk device update. A single call applies the same fieldchanges to a comma-separated list of device ids. The Lumics API documents no cap on how many.The blast radius of one malformed call is your whole device inventory.LUMICS_ENABLE_TOKEN_REVOCATION— token revocation. The Lumics API has no per-tokenrevoke.POST /me/token/revokerevokes every JWT ever issued to your user, including thetoken this server is currently using, and including tokens used by any other integration,script, or session you own. It is self-inflicted denial of service plus collateral damage, itcannot be undone, and everything affected must be re-issued by hand. The tool also requires anexplicit confirmation argument, but treat that as a speed bump: an agent can supply it. Theenvironment variable is the real gate.
Cross-company access is off by default
Every tool is covered by the company pin. Most take an optional companyId argument, and bydefault a value that differs from LUMICS_COMPANY_ID is refused with a not_permitted error. ALumics token issued to an MSP user reaches every company that user administers, so without this gatea model that had picked up another tenant's id — from a conversation, a document, a previous answer —could read or write there, while the tool description said the call applied to the configuredcompany.
Two tools have no companyId argument to check, because their Lumics paths carry no company segment:lumics_get_device_metrics and lumics_get_device_item_metrics are addressed by device id alone.They enforce the same pin a different way — a device-ownership read first: the device is fetchedinside LUMICS_COMPANY_ID, and the metric read happens only if that confirms the device belongsthere. A device in another tenant is refused with not_permitted and no metric request is made. Thatcosts one extra round trip per call, which is the price of a control with no exception in it. Bothtools are therefore company-scoped, and are withheld entirely when LUMICS_COMPANY_ID is unset.
Set LUMICS_ALLOW_CROSS_COMPANY=1 only if you deliberately operate several tenants through oneserver, and understand that it widens the blast radius of every write from one company to everycompany the token can reach. It is an operator setting; nothing in a conversation can turn it on.See SECURITY.md.
Token handling
- Mint tokens with the shortest workable
expiresIn. The default is one day; the API documents nomaximum, so nothing stops you creating a token that outlives its usefulness by years. - Rotate regularly, and rotate immediately if a token has been pasted anywhere it might persist —chat transcripts, shell history, CI logs, an issue comment.
- Client configuration files (
claude_desktop_config.json,.cursor/mcp.json,~/.codex/config.toml)store tokens in plaintext. Protect them like private keys. - The server redacts credential material at the error boundary, so tokens do not appear in tooloutput or diagnostics. That is verified by test, not assumed.
- Never commit
.env. Secret scanning runs in CI.
Reporting a vulnerability
See SECURITY.md. Do not open a public issue for a security problem.
Limitations
These are real constraints of the current release and the underlying API. They are listed hererather than buried because knowing them changes how you should read the server's answers.
The Lumics API has no pagination
This is the most important limitation. Across all 41 documented endpoints the only result-controlparameter is limit. There is no offset, page, skip, cursor, after, sort, or order,list responses are bare JSON arrays with no total count, and no response carries a next-page link.
Consequences:
- Large result sets can be truncated with no way to fetch the rest. If you have more devicesthan the effective limit, you cannot page through them. Narrowing the query is the only option.
- The server therefore emits no
offset,has_more, ornext_offsetfields. Inventing themwould be lying: ahas_more: falseon a truncated list would make an agent report a partialinventory as complete. - When a result count reaches the requested limit, the server says explicitly that results may betruncated and that the API provides no pagination mechanism. Read that disclosure as "this maynot be everything."
- Some list endpoints (
component,componenttypes,deviceDefinitions/components, andmetrics/summaries) do not document even alimit.
Metric tools require a properties argument, and a wrong one is not an error
properties is required on lumics_get_company_metrics, lumics_summarize_company_metrics,lumics_get_device_metrics and lumics_get_device_item_metrics. The vendor documentation calls itoptional and is wrong: without it the API answers400 "Must supply required component metrics as properties parameter", so those four tools cannotmake a single successful call. It is required rather than defaulted because no metric name iscorrect for every module — a default would answer a question nobody asked. It stays optional onlumics_get_metric_summary, where it means something different (see below).
The syntax is <TypeGroup>.<metric>, comma-separated, no spaces — Calculated.cpu, orCalculated.cpu,Calculated.mem. Type groups seen on live tenants are Calculated, Rate andTimeTicks; there is no Counter or Gauge group despite those being plausible SNMP type names.Property names are tenant-specific, so treat the examples as shapes rather than as a vocabulary.
To find legal names, call lumics_get_metric_summary with no properties and readdata.<class>[].stats: the outer keys are type groups and the inner keys are metric names, sojoining them with a dot gives a value the other four tools accept. Two limits on that, both real:
- it enumerates device-scoped names only, so a component-level name — an interface counter, say —is discoverable from no endpoint at all and has to come from the Lumics UI or from someone whoalready knows it;
- its response key depends on the module (
devicesforsnmp,http_endpointsforhttp), so readwhichever key is present rather than assumingdevices.
On lumics_get_metric_summary itself, properties filters rather than projects: supplying itdrops items that do not carry the property instead of narrowing the items that do, and it has beenobserved emptying a response that was otherwise full. Leave it unset there unless you have a reason.
A name the API does not recognise is not rejected. It returns HTTP 200 with the full row countand empty stats on every row, which reads exactly like "this metric has no data". This serverrejects the worst form of it locally — a value in which no entry carries a Group. prefix — anddetects the rest after the fact, disclosing in the response that the names may be wrong rather thanletting an empty answer stand. SeeA metric call returned rows but no values.
One more thing about component selection: itemType takes the singular component id(snmp_common_cpu), which is not what lumics_list_component_types returns — that endpointreturns plural aliases and most of them are rejected with 400 Unknown component. Build the id fromlumics_get_device_definition_components instead, or copy the type field off a row a metric callalready returned. Lumics validates itemType before properties, so a wrong itemType hides aproperties problem entirely: fix itemType first.
The company-scoped metric endpoint is unreliable; prefer the device-scoped tools
lumics_get_company_metrics and lumics_summarize_company_metrics read/api/v1/metrics/companies/..., and that route did not answer dependably when it was measuredagainst a production tenant on 2026-07-30 (spec §12.5 M12, §14 defect 25). What was observed:
lumics_get_company_metricsreturned HTTP 500 on ordinary queries that carried a validpropertiesvalue —{"error":"Sorry, an error occurred. Please try again.","code":500}. Itfailed withlastMetric,isMonitored,minIntervalsandlimit, and withinterval=minuteandinterval=fiveMin. It succeeded withinterval=hour,interval=day,aggregateandalignTimeRange, and a minimal query returned 200 earlier the same day.lumics_summarize_company_metricsnever returned at all — over 90 seconds, with and withoutitemTypenarrowing. That is the same finding as the slowness below, seen from the other side.- The device-scoped tools worked throughout, returning populated data in one to two seconds.
- Browsing the vendor's own web application, a company dashboard load issued 57 API calls,including its "Top devices by CPU" and "Top devices by memory" widgets, and never once called
/api/v1/metrics/companies/. The vendor's own product does not use this endpoint forcompany-wide metrics.
This is intermittent and query-dependent, not a dead endpoint, and no cause has been established.It is a correlation measured on one tenant on one day. Both tools are still registered and stillwork for some queries — the decision was to document this, not to withhold the capability.
What to do with it:
- Prefer the device-scoped route for anything estate-wide. List the devices you care about with
lumics_list_devices, then calllumics_get_device_metricsper device, orlumics_get_device_item_metricsfor one component. More calls, and it is the path that answered. - If you do use the company-scoped tools, drop the parameters above and prefer
interval=hourorinterval=day. - A 500 or a timeout there is not an absence of data. The tool descriptions say so, and a 500 fromthese two endpoints carries endpoint-specific guidance rather than the generic "this is not aproblem with your arguments" — because on this route the arguments do correlate with the failure.
- The server does not retry a 500 here. It never has (500 is not in its retryable status set), andthe error now says so instead of implying a retry already happened.
lumics_summarize_company_metrics is slow, and a timeout is not an empty result
metrics/.../summarize aggregates every matching component in the company before it answers. It hasbeen measured taking over 90 seconds without returning, where the other metric endpoints answerthe same module and window in one to two seconds. Nothing in the vendor documentation mentions this.
This server gives that one endpoint a three-minute deadline of its own rather than the sharedLUMICS_TIMEOUT_MS, and attempts it exactly once — three attempts at three minutes is nineminutes of silence, which from a client is indistinguishable from a hung server, and a retry cannothelp an endpoint that is slow because of how much work it is doing. On a large tenant it can stilltime out. A timeout there is a timeout, not evidence that there is no data, and the error saysso. Narrow it with itemType, a tighter properties or a shorter window, or uselumics_get_company_metrics, which reads the same module without the cross-component aggregation.
Everything the vendor documents about this endpoint's response — its envelope, the sum semantics,the bucket shape — is unverified against live behaviour, because it never returned during thecontract run.
Metric endpoints require a resolution parameter
All four metric-data endpoints require either dataPoints or width (width wins if both areset). The server supplies a sensible default so calls do not fail on a parameter a model has noway to reason about, and the effective value is disclosed in the output. You can override it. Timewindows are accepted as ISO-8601 timestamps or a relative lookback and converted to the API'sepoch-millisecond fromMs/toMs internally.
The server sends no limit on a metric call unless you supply one. A metric result carries onerow per component per time bucket, and Lumics documents no ordering for those rows, so a limitapplied on your behalf would cut across time as well as across components and leave a series withholes that look like real gaps in the monitoring data. Where a metric response is too large, theoutput budget shortens it instead — from the end of the order Lumics returned, disclosed as such —so what you lose is the tail of the series rather than scattered points.
metrics/summaries — the endpoint whose own vendor description advertises "top X lists ofdevices" — accepts no limit, sort, or top parameter at all. Any ranking you see wasperformed client-side by this server after retrieving the full set, and the output says so.
Timestamps must carry a timezone
from and to on every metric tool, and date on lumics_update_device_last_discovery, accepttwo forms and only two:
- a bare date,
2026-07-29, which means 00:00:00Z — UTC midnight; - a timestamp with an explicit zone,
2026-07-29T14:00:00Zor2026-07-29T14:00:00+02:00.
A timestamp carrying a time but no zone — 2026-07-29T14:00:00 — is rejected, with a messagenaming the fix. It is not guessed at, because Date.parse reads that form in the server's localtimezone while reading a bare date as UTC: the window moves by up to fourteen hours, and theresponse notes report the moved window, so the wrong answer looks internally consistent. Epochmilliseconds are also accepted, and a relative lookback such as 6h or 7d needs no timezone atall.
to may not be in the future either, beyond 24 hours of clock skew. Monitoring data only existsfor the past, so a future end date does not widen the result — it only makes the reported windowwrong.
lumics_list_devices returns a compact projection by default
A full device record is around 1.9 kB, mostly its modules polling map, so the defaultlimit of 100 and the 25,000-character output budget cannot both be satisfied — a default callwould ask for a hundred devices and be able to show about thirteen. So lumics_list_devices appliesa default field projection: id, name, ipAddress, deviceType, collector, enabled,maintenanceMode. Nothing is dropped from the device list by this; only fields are, and theresponse says so.
Pass fields with the names you want to override it, pass fields: [] to get whole records, orcall lumics_get_device for one device in full. No other list tool projects by default.
A write is never retried after a transport failure
If the connection drops, times out, or delivers an incomplete body on a POST, PATCH, or DELETE,the server makes one attempt and no more. The request may have been applied before the connectiondied, and the transport cannot tell that apart from a request Lumics never saw, so a replay wouldduplicate a record, double-apply a change, or hit a record the first attempt had already deleted. Theerror text says the write may already have landed and instructs a verifying read rather than a retry;read that as "go and look", not "it failed".
DELETE is in that set even though HTTP calls it idempotent. Idempotence guarantees the same state,not the same answer: a replayed delete 404s, and not_found — "Lumics has no such resource" — is whatwould surface, so a completed deletion would be reported as never having happened.
Retries on a status code are unchanged and still apply to every verb, 429 included: a statusproves the server answered, which removes the ambiguity.
The HTTP transport is not in 0.1.0
LUMICS_TRANSPORT=http is refused at startup in this release. 0.1.0 ships stdio only(ADR-001 decision 3) and opens no networklistener at all; Streamable HTTP is decision 4 of the same ADR and is scheduled for v0.2. Thelistener code is in the tree so that v0.2 is additive, but it is not reachable from configurationhere, and the five LUMICS_HTTP_* variables are documented for forward reference only.
Unset LUMICS_TRANSPORT, or set it to stdio, and connect over stdio from your MCP client.
Raw Mongo query passthrough is deliberately not exposed
The Lumics metric endpoints accept componentQuery (a raw Mongo query expression) and filters(a filter object convertible to one). Neither is exposed as a tool argument in v0.1. Handing alanguage model a raw database query language is a NoSQL injection and unbounded-query surface. Thetyped itemType, isMonitored, and properties arguments cover the practical use cases. This isa deliberate reduction of capability the vendor API offers, chosen on security grounds. If rawpassthrough is ever added it will go behind an explicit opt-in flag.
v0.1 supports the companies context only
Several Lumics endpoints take a :context path segment whose legal values vary per endpoint —companies, admingroups, and in two read cases system. v0.1 supports companies only.admingroups and system are not offered. Note also that the vendor documentation neverspecifies what context id to supply for system, so it is documented-but-unspecified and not safeto rely on.
Rate limits are undocumented
The API documents a 429 Too Many Requests response but publishes no limits, no windows, and noheaders. The server is conservative about concurrency and honours Retry-After when present, butwe cannot tell you where the ceiling is because Lumics does not say.
Other API-level caveats
- Documented status codes include
304 Not Modifiedand423 Locked, but no endpoint documentsits own caching or locking semantics. - Identifier field names are inconsistent upstream: some responses return
id, others_id, andcomponent objects leak Mongoose internals (__t,__v). - No enumerations are published for
deviceType,role,moduleType, or componentitemType.lumics_list_component_typesis the lookup for thecomponentTypekey the component tools take,but not for the metric tools'itemType— it returns plural aliases the metrics API rejects.Build anitemTypefromlumics_get_device_definition_components, or copy thetypefield off arow a metric call returned. Metric property names are enumerable from one endpoint only, andonly for device-scoped metrics — seeMetric tools require apropertiesargument. - IPAM address routes use singular
/ipsubnet/for reads and plural/ipsubnets/for writes.This asymmetry is real in the vendor's route definitions, not a documentation typo, and theserver sends each spelling as documented. - The vendor documentation does drift from live behaviour, and has been measured doing so. Thefirst contract run against a live tenant, on 2026-07-30, contradicted it in seven places, four ofthem in the metric layer. Those measurements are recorded, dated and marked in
docs/reference/lumics-api-v1.md— §0 indexes them, §14defects 17–23 carry the detail — with the vendor's original text left in place beside them, and thedecisions taken about them are inADR-003. Contract tests run againsta live tenant before each release; see docs/RELEASE.md.
Release maturity
This is a 0.x release. The tool surface may change before 1.0, including breaking renamesand argument changes, which pre-1.0 semver permits with a minor bump and a changelog entry. Pin anexact version if you depend on stability. The HTTP transport, Docker deployment, and .mcpbbundle are not in v0.1.
Troubleshooting
It worked yesterday and now every tool fails
Your token expired. Lumics tokens default to expiresIn=86400 — one day — so this is the mostcommon failure by a wide margin, and it presents as an authentication error (unauthorized, HTTP 401) on every call rather than as anything that looks like expiry.
Mint a fresh token, update it wherever your client stores it, and restart the server. The tokenis read once at startup; editing a config file is not enough on its own. If you find yourself doingthis daily, mint with a longer expiresIn deliberately rather than by accident, and readSecurity first — there is no documented maximum, and a token that never expires is acredential you will forget you issued.
The server started but almost no tools are there
LUMICS_COMPANY_ID is not set. Company-scoped tools are withheld from tools/list when there isno company to scope them to, which leaves two tools in a default deployment — lumics_get_me andlumics_get_device_definition_components. The server logs a warning on stderr saying so. FollowFirst run: finding your company id.
Tools work but every list comes back empty
Check that LUMICS_COMPANY_ID is the company your token belongs to. A syntactically valid idfor a company your user cannot see returns empty results rather than an error, which reads as "youhave no devices". lumics_get_me reports the user the token authenticates as and the company itbelongs to; compare the two.
A call was refused with not_permitted and a companyId in the message
The call named a companyId other than LUMICS_COMPANY_ID, and LUMICS_ALLOW_CROSS_COMPANY isoff. Omit companyId to use the configured company. If you genuinely need another tenant, seeCross-company access is off by default — it is anoperator setting and cannot be changed from a conversation.
A device list is truncated, or says it may be incomplete
Do not raise the limit. The Lumics API has no pagination, and a larger limit fetches recordsthe output budget then drops — you get fewer, not more. Pass a fields projection instead so eachrecord is small enough that more of them fit, narrow the query, or ask for a higherLUMICS_MAX_OUTPUT_CHARS. Seelumics_list_devices returns a compact projection by default.
The same applies to a metric response: shrink it with properties, itemType, lastMetric or ashorter window rather than with limit.
A metric call returned rows but no values
The property names are probably wrong. This is the most likely explanation, and it is far morelikely than the metric genuinely having no data.
Lumics does not reject an unrecognised properties value. It answers HTTP 200 with the full rowcount and an empty stats object on every row — a successful, complete-looking, empty answer thatis indistinguishable from a real absence of data except by inspection. This server inspects it: whenthe paths you asked for resolve on none of the returned rows, the response says the names may bemalformed and does not present the result as a negative finding about your estate. Read thatnote as "nothing was measured", not as "nothing is being collected".
What to check, in order:
- Syntax. Each entry must be
<TypeGroup>.<metric>with no spaces —Calculated.cpu, notcpu. A value with no dotted entry at all is refused locally before any request is made. - The type group. If the note says the group never appeared in any row's
stats, the group halfis wrong.Calculated,RateandTimeTicksare what live tenants have shown;CounterandGaugedo not exist. - The metric name. If the note says the group came back but held no such metric, the name half iswrong. Enumerate the real names with
lumics_get_metric_summarycalled with noproperties, andreaddata.<class>[].stats. itemType, if you passed one. Lumics validates it beforeproperties, and a wrong value failswith400 Unknown component— which hides the properties problem rather than showing it.
If the names are right and the rows are still empty, the metric may genuinely not be collected onthose components. Confirm it in the Lumics UI before reporting it: the API gives no signal thatdistinguishes the two. SeeMetric tools require a properties argument.
A company metric call failed with a 500
Expected, unfortunately. /api/v1/metrics/companies/... returned 500 on ordinary queries when it wasmeasured, and the failures tracked specific parameters: drop lastMetric, isMonitored,minIntervals and limit, and use interval=hour or interval=day rather than minute orfiveMin. If it still fails, go device-scoped — lumics_list_devices, thenlumics_get_device_metrics per device — which is the route that answered in every run. The failure isnot evidence that the company has no data for that module. SeeThe company-scoped metric endpoint is unreliable.
A timestamp was rejected
Add an explicit zone: 2026-07-29T14:00:00Z, or an offset such as 2026-07-29T14:00:00+02:00. Abare 2026-07-29 is fine and means UTC midnight. A relative lookback like 6h avoids thequestion entirely. See Timestamps must carry a timezone.
If the rejection says the value is in the future, that is to — it may not be later than now, giveor take 24 hours of clock skew.
A metric call was refused and named a device in another company
lumics_get_device_metrics and lumics_get_device_item_metrics take no companyId, so they checkthe pin by reading the device inside LUMICS_COMPANY_ID first. A not_permitted refusal there meansthe device is in a different Lumics company, or does not exist at all; no metric request was made.Pick a device with lumics_list_devices, which lists only the configured company. SeeCross-company access is off by default.
The server refuses to start and says LUMICS_BASE_URL must use https:
Your LUMICS_BASE_URL is a plain http: URL for a host that is not loopback. The Lumics token is abearer credential sent on every request, so over plaintext to a remote host it crosses the network inthe clear and is readable by anything on the path. Switch the URL to https:.
Plain http: is accepted only for 127.0.0.1, localhost, or [::1], which covers a localdevelopment proxy. The comparison is exact, so a host like localhost.example.invalid gets noexemption. No environment flag widens this — if a deployment genuinely needs plaintext to a remotehost, terminate TLS in front of Lumics or tunnel to loopback.
The server refuses to start with LUMICS_TRANSPORT=http
That is deliberate in 0.1.0, not a bug. SeeThe HTTP transport is not in 0.1.0.
The server exits immediately with a configuration error
Configuration is validated at startup and the message names the variable and what to do about it.Read it on stderr — most clients hide server stderr behind a "server disconnected" message, socheck your client's MCP log rather than concluding the server is broken. Nothing is requested and nocredential is read when validation fails.
I need to see what the server is actually doing
Turn the verbosity up with LUMICS_LOG_LEVEL=debug and restart. Diagnostics go to stderr —stdout is the MCP protocol channel and carries nothing else — so look in your client's MCP log.
debug adds a record per tool call: which tool, how long it took, how many characters it returned,whether a list reached the limit it asked for, and how many items the output budget dropped. That iswhat to reach for when a tool returns less than you expected. The default is info; warn anderror are quieter, and silent turns the stream off entirely for a supervisor that treats anystderr output as a fault.
Tool reference
Tools are named lumics_<action>_<resource> and grouped by resource below. Every tool carries anoperation classification — Read, Create, Update, Admin, or Destructive — asrequired by the Engineering OS security standard, mapped to MCP tool annotations so clients canpresent the right confirmation prompts.
39 tools exist; 37 are registered in a default deployment. The two missing arelumics_batch_update_devices and lumics_revoke_tokens, each behind its own environment flag. WithLUMICS_READ_ONLY=1 only the Read rows are registered, which is 20 tools. WithoutLUMICS_COMPANY_ID only the two tools that need no company are registered — seeFirst run.
Devices
| Tool | Class | Gate |
|---|---|---|
lumics_list_devices |
Read | — |
lumics_get_device |
Read | — |
lumics_create_device |
Create | — |
lumics_update_device |
Update | — |
lumics_update_device_last_discovery |
Update | — |
lumics_batch_update_devices |
Admin | LUMICS_ENABLE_BATCH_UPDATE |
lumics_delete_device |
Destructive | — |
Bulk device update is classified Admin, not Update: one call rewrites arbitrary fields acrossmany devices and the Lumics API documents no cap on how many, so it carries destructiveHint andrequires a confirm argument alongside its environment flag. SeeADR-002.
Collectors
| Tool | Class | Gate |
|---|---|---|
lumics_list_collectors |
Read | — |
lumics_get_collector |
Read | — |
lumics_create_collector |
Create | — |
lumics_update_collector |
Update | — |
lumics_delete_collector |
Destructive | — |
Components
| Tool | Class | Gate |
|---|---|---|
lumics_list_components |
Read | — |
lumics_get_component |
Read | — |
lumics_list_component_types |
Read | — |
lumics_get_device_definition_components |
Read | — |
lumics_update_component |
Update | — |
IPAM — subnets
| Tool | Class | Gate |
|---|---|---|
lumics_list_ipsubnets |
Read | — |
lumics_get_ipsubnet |
Read | — |
lumics_create_ipsubnet |
Create | — |
lumics_update_ipsubnet |
Update | — |
lumics_delete_ipsubnet |
Destructive | — |
IPAM — addresses
| Tool | Class | Gate |
|---|---|---|
lumics_list_ipaddresses |
Read | — |
lumics_get_ipaddress |
Read | — |
lumics_create_ipaddress |
Create | — |
lumics_update_ipaddress |
Update | — |
lumics_delete_ipaddress |
Destructive | — |
IPAM — groups
| Tool | Class | Gate |
|---|---|---|
lumics_list_ipgroups |
Read | — |
lumics_get_ipgroup |
Read | — |
lumics_create_ipgroup |
Create | — |
lumics_update_ipgroup |
Update | — |
lumics_delete_ipgroup |
Destructive | — |
Metrics
| Tool | Class | Gate |
|---|---|---|
lumics_get_company_metrics |
Read | — |
lumics_summarize_company_metrics |
Read | — |
lumics_get_device_metrics |
Read | — |
lumics_get_device_item_metrics |
Read | — |
lumics_get_metric_summary |
Read | — |
Identity
| Tool | Class | Gate |
|---|---|---|
lumics_get_me |
Read | — |
lumics_revoke_tokens |
Admin | LUMICS_ENABLE_TOKEN_REVOCATION |
Per-tool argument detail lives in docs/TOOLS.md, which ships alongsidethe implementation — arguments, types, defaults, enums, output shape, and the endpoint each toolmaps to. The table above is structural only;treat docs/TOOLS.md and the server's own tools/list response as authoritative for signatures,and remember that names may change before 1.0.
Development
Requires Node.js 20 or newer.
git clone https://github.com/ZenixSolutions/lumics-mcp.git
cd lumics-mcp
npm ci
cp .env.example .env # then set LUMICS_TOKEN, and LUMICS_COMPANY_ID once you have it
npm run build
node --env-file=.env dist/index.js
The --env-file flag is how the .env gets read: the server does not look for one itself. SeeThe server does not read a .env file.
| Script | What it does |
|---|---|
npm run build |
Compile TypeScript to dist/ and mark the bin executable |
npm run dev |
Compile in watch mode |
npm start |
Run the built server (node dist/index.js) |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint |
npm run lint:fix |
ESLint with --fix |
npm run format |
Prettier write |
npm run format:check |
Prettier check — what CI runs |
npm test |
Vitest, single run |
npm run test:watch |
Vitest in watch mode |
npm run test:coverage |
Vitest with V8 coverage |
npm run test:contract |
Contract tests against a live tenant (needs a real token) |
npm run validate |
typecheck + lint + format:check + test — run before opening a PR |
npm run validate is the gate CI enforces. Run it locally first.
Contract tests are opt-in because they make real API calls. They are read-only, but they consumequota against a live tenant. See docs/RELEASE.md for when they are required.
Contributing
Contributions are welcome. This repository is governed by the Engineering OS framework, whichmeans meaningful changes go through issue, discovery, approval, independent review, and validationbefore merge — and that no author may be the sole reviewer of their own work. ReadCONTRIBUTING.md before you start writing code; it will save you a rejectedpull request.
- CONTRIBUTING.md — contribution flow, branch and commit conventions
- CODE_OF_CONDUCT.md — Contributor Covenant 2.1
- SECURITY.md — vulnerability reporting and hardening guidance
- CHANGELOG.md — release history
- docs/DEPENDENCY_POLICY.md — dependency rules
- docs/RELEASE.md — release and tagging policy
- docs/DECISION_LOG.md — approved decisions and recorded exceptions
- docs/rfc/ and docs/adr/ — approved designs and architecturaldecisions
- docs/reference/lumics-api-v1.md — the captured Lumics APIcontract this server targets
License
MIT © 2026 Zenix Solutions