Repository navigation
Architecture
How the server is put together and why it is shaped the way it is. The whole implementation is about 200 lines across five files.
| Module | Responsibility |
|---|---|
config.py |
Config, a frozen dataclass. from_env() validates and resolves REPORTMATE_API_URL plus one credential; auth_header() picks the header. |
client.py |
ReportMateClient, a wrapper around an httpx.AsyncClient with the base URL, auth header, Accept, and User-Agent baked in. One method: get(path, params), which raises on non-2xx and returns parsed JSON. |
server.py |
The FastMCP("reportmate") instance, the MODULES tuple, and the eight @mcp.tool functions. |
__init__.py |
__version__, the calendar version literal. |
__main__.py |
main() — calls mcp.run(). Registered as the reportmate-mcp console script. |
A tool call travels one hop and comes back:
agent -> MCP client -> stdio -> FastMCP dispatch -> tool function
-> ReportMateClient.get() -> HTTPS GET /api/v1/... -> ReportMate API
The tool functions do almost nothing beyond building a path. The single
exception is search_devices, which filters and truncates in Python after
fetching the whole device list — see Tools Reference for what that means in
practice.
server.py holds a module-level _client that starts as None. The client()
helper builds it on first use:
def client() -> ReportMateClient:
global _client
if _client is None:
_client = ReportMateClient(Config.from_env())
return _clientThat is deliberate — the module imports cleanly with no environment set, which
is what lets the test suite import server and enumerate tools without
credentials. The cost is that a bad configuration surfaces as a failed tool
call rather than a failed startup.
The client is created once and reused for the process lifetime, so httpx keeps
the connection pool warm across tool calls. aclose() exists on the client but
nothing in the server calls it; the process exiting is the cleanup.
server.py defines the ten collection modules that match the dashboard tabs:
inventory installs applications system management
identity hardware peripherals security network
It is documentation for the agent, not a validator — no tool checks its argument
against it. module_report and get_device_module pass anything through, which
is how nested variants such as installs/full reach the API through
module_report. get_device_module takes only a single module segment
upstream. An unrecognized module produces an upstream HTTP error, not a local
one.
Read-only. Only GET is reachable; ReportMateClient has no post, put, or
delete method. Archive and delete endpoints exist on the API and are
deliberately not registered. Adding them would mean adding both a client method
and an explicit opt-in.
Intent-shaped, not endpoint-shaped. Eight tools cover a much larger API surface. An agent reasons better about a small, well-named toolset than about a faithful mirror of every route, and each extra tool competes for attention in the model's context.
Thin. No caching, no retries, no response reshaping, no pagination handling. Whatever the API returns is what the agent sees. This keeps the server honest about being a transport for the API rather than a second implementation of it — but it does mean fleet-wide tools can return large payloads.
Single-tenant per process. Credentials come from the process environment, so one running server is one fleet with one identity. Multiple fleets means multiple server entries in your client config.
Hatchling builds a wheel from src/reportmate_mcp, with the version read from
the __version__ literal in __init__.py. The Dockerfile is a
python:3.12-slim base that copies pyproject.toml, README.md, and src,
runs pip install ., and sets reportmate-mcp as the entry point. Environment
variables are supplied at run time; nothing is baked into the image.
AGPL-3.0-or-later — a commercial license is also available. reportmate.app