ZenixSolutions

lumics-mcp

Community ZenixSolutions
Updated

lumics-mcp

CInpmLicense: MIT

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:

  1. 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.
  2. 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:

  1. Configure the server with LUMICS_TOKEN only, leaving LUMICS_COMPANY_ID unset, and connect itfrom your client.
  2. 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.
  3. Set LUMICS_COMPANY_ID to 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 -- separates claude mcp add's own flags from the command it should run. Withoutit, npx and its arguments are parsed as flags to claude and the command fails.
  • By default the server is registered for the current project only. Add -s user to 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 localhost and private/internal addresses outright, so tunnellingto a machine on your own network is not a workaround either.
  • The mcp-remote bridge 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 env block. 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=.env requires the file to exist; --env-file-if-exists=.env tolerates 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/revoke revokes 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, or next_offset fields. Inventing themwould be lying: a has_more: false on 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 a limit.

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 spacesCalculated.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 (devices for snmp, http_endpoints for http), so readwhichever key is present rather than assuming devices.

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_metrics returned HTTP 500 on ordinary queries that carried a validproperties value — {"error":"Sorry, an error occurred. Please try again.","code":500}. Itfailed with lastMetric, isMonitored, minIntervals and limit, and with interval=minute andinterval=fiveMin. It succeeded with interval=hour, interval=day, aggregate andalignTimeRange, and a minimal query returned 200 earlier the same day.
  • lumics_summarize_company_metrics never returned at all — over 90 seconds, with and withoutitemType narrowing. 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 withlumics_list_devices, then call lumics_get_device_metrics per device, orlumics_get_device_item_metrics for 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=hour orinterval=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:00Z or 2026-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 Modified and 423 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 component itemType.lumics_list_component_types is the lookup for the componentType key the component tools take,but not for the metric tools' itemType — it returns plural aliases the metrics API rejects.Build an itemType from lumics_get_device_definition_components, or copy the type field off arow a metric call returned. Metric property names are enumerable from one endpoint only, andonly for device-scoped metrics — seeMetric tools require a properties argument.
  • 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 indocs/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=86400one 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:

  1. 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.
  2. The type group. If the note says the group never appeared in any row's stats, the group halfis wrong. Calculated, Rate and TimeTicks are what live tenants have shown; Counter andGauge do not exist.
  3. 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_summary called with no properties, andread data.<class>[].stats.
  4. itemType, if you passed one. Lumics validates it before properties, and a wrong value failswith 400 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

MCP Server · Populars

MCP Server · New