Skip to content

Troubleshooting

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Troubleshooting

Failure modes you can trace to something specific in the code, and what to do about each.

The server starts and prints nothing

Expected. mcp.run() uses the stdio transport, so the process reads MCP frames from stdin and writes to stdout. There is no banner, no log line, and no port. If you want to see it work, register it with a client (Client Setup) or drive it with the MCP Inspector.

A configuration error appears on the first tool call, not at startup

Also expected, and worth knowing when you are debugging. server.py builds the API client lazily inside client(), so Config.from_env() does not run until a tool is invoked. A missing variable therefore shows up as a failed tool call in the agent's transcript rather than a process that refuses to start.

The two messages:

Message Cause
REPORTMATE_API_URL not set (e.g. https://api.reportmate.app) REPORTMATE_API_URL missing or empty.
no credential: set REPORTMATE_API_KEY (preferred) or REPORTMATE_PASSPHRASE Neither credential variable is set.

The usual cause is that the variables are set in your shell but not in the MCP client's server definition. The client launches the process itself and it does not inherit your interactive shell's environment. Put them in the env block.

401 from every tool except api_health

That pattern is diagnostic, not a coincidence: /api/v1/health/ready is unauthenticated upstream, so api_health succeeds regardless of your credential while everything else fails. It means the URL is right and the API is up, and the credential is wrong, revoked, or not accepted by that instance.

If both REPORTMATE_API_KEY and REPORTMATE_PASSPHRASE are set, the key wins unconditionally and the passphrase is never sent, so a stale key will shadow a working passphrase. Unset the key to fall back.

403 from every tool

The API key is valid but lacks the read scope. The API derives the required scope from the HTTP method and this server only issues GET, so read is the only scope it ever needs — and the only one that can be missing. Issue a read-scoped key. Note that a shared passphrase is never scope-limited, so a 403 always means an API key.

404 on get_device or get_device_module

The serial number does not exist in that fleet. Nothing validates it locally, so a typo goes straight upstream. Confirm it with search_devices first. Serial number is ReportMate's primary device identifier — a hostname or asset tag will not work here.

400 Invalid module on get_device_module

The module name is not one of the ten the API serves. MODULES in server.py is documentation for the agent, not a validator, so anything you pass is forwarded and rejected upstream. Use a name from the list in Tools Reference.

Nested paths are the other cause: installs/full and security/certificates are fleet report paths for module_report, and are not valid here — this route takes one module segment.

404 on module_report

Same cause. module_report strips surrounding whitespace and slashes from its argument and interpolates the rest into /api/v1/<module>, so "installs", "/installs", and "/installs/" all work, but a misspelling does not.

422 from recent_events

The limit is outside the range the API accepts, which is 1 to 1000. The value is rejected by request validation before any data is fetched. 0 does not mean "everything" here — that convention applies only to search_devices, whose limit is applied locally.

A tool times out

ReportMateClient sets a 30-second httpx timeout and does not retry. The fleet-wide tools — module_report for any module, application_usage, and search_devices with no limit — pull every device's data in one request, so they are the ones that hit it on a large fleet or a cold database. Retry once; if it is consistent, the API side needs attention rather than the MCP server.

The agent's context fills up, or it truncates the answer

The server does not reshape, paginate, or summarize anything — whatever the API returns is handed to the model verbatim. get_device returns every module for a device, and module_report returns every device for a module. On a fleet of any size that is a lot of tokens.

Steer the question narrower: get_device_module instead of get_device, search_devices with a limit, recent_events with a small limit.

search_devices returns too much, or the wrong things

The filter is a substring match against the whole device record converted to a string:

devices = [d for d in devices if q in str(d).lower()]

So a query matches any field, including ones you did not mean, and there is no field-scoped or structured query syntax. It is also entirely client-side — the full device list is fetched from the API before any filtering happens, so a query does not reduce the API call, only the result handed back.

The Docker container exits immediately

The client must launch it with -i so stdin stays attached; a stdio server with no stdin has nothing to read and ends. Do not pass -t — a TTY corrupts the protocol stream. The working argument list is in Client Setup.

The tool list does not match this wiki

Check the version. __version__ in src/reportmate_mcp/__init__.py is a calendar stamp, YYYY.MM.DD.HHMM in UTC, and the server reports it to the client. Note that an installed package's metadata version is PEP 440-normalized (2026.07.20.0057 becomes 2026.7.20.57), so the literal in the source is the form to compare against.

Clone this wiki locally