Architecture
The Litmus MCP Server is two cooperating processes packaged in one container.
Processes
Process | File | Port | Role |
|---|---|---|---|
MCP server | src/server.py | 8000 | Implements the MCP protocol over SSE. Exposes 57 tools and a set of litmus://docs/* resources. |
Web client | src/web_client.py | 9000 | FastAPI app: chat UI, config pages, LLM streaming, multi-instance management. Launches server.py as a subprocess. |
Both ports come from a single Docker image. The Web UI is optional; clients can hit the SSE endpoint directly without ever touching port 9000.
Request flow (Web UI)
Browser
|
| POST /chat
v
web_client.py ---> client_utils.process_streaming_query
|
| anthropic.messages.stream() / OpenAI / Gemini
v
LLM provider
|
| tool_use events
v
session.call_tool(name, args)
|
v
MCP server (port 8000)
|
v
server.py handle_call_tool
|
v
tools/<category>_tools.py
|
v
Litmus Edge REST / NATS / InfluxDBRequest flow (external MCP client)
Claude Desktop / Cursor / VS Code / Windsurf / Claude Code
|
| SSE (with headers)
v
MCP server (port 8000)
|
v
tools/<category>_tools.py
|
v
Litmus Edge REST / NATS / InfluxDBAuthentication
The server is stateless. Every tool call receives a request object and pulls credentials from headers (SSE mode) or environment variables (STDIO mode) at call time, via helpers in src/utils/auth.py:
- get_litmus_connection(request) - Litmus Edge REST/SDK
- get_nats_connection_params(request) - NATS broker
- get_influx_connection_params(request) - InfluxDB
This means a single MCP server can be shared across many users, each presenting their own headers. There is no server-side session, no shared credentials.
Multi-instance edge support
The Web UI lets you register multiple Litmus Edge devices and switch between them. Internally, instances live in .env as:
EDGE_INSTANCE_1_URL=...
EDGE_INSTANCE_1_CLIENT_ID=...
EDGE_INSTANCE_1_CLIENT_SECRET=...
EDGE_INSTANCE_1_NAME=...
EDGE_INSTANCE_2_URL=...
ACTIVE_EDGE_INSTANCE=1The active instance's credentials are mirrored into the canonical EDGE_URL, EDGE_API_CLIENT_ID, EDGE_API_CLIENT_SECRET keys so the rest of the system needs no awareness of multi-tenancy.
See Multi-Instance Edge.
Transport modes
Mode | When to use | Auth source |
|---|---|---|
SSE (default) | Remote or shared MCP server, any HTTP-capable client | HTTP headers per connection |
STDIO | Single-user local install with Claude Desktop | Environment variables |
STDIO is opt-in via ENABLE_STDIO=true in src/config.py or environment.
Source layout
src/
├── server.py MCP server entrypoint, SSE setup
├── web_client.py FastAPI app, all web routes
├── client_utils.py LLM streaming, tool-call loop
├── env_config.py .env read/write, path constants
├── config.py Ports, SSL, NATS/InfluxDB defaults
├── conversation.py In-memory chat history
├── utils/
│ ├── auth.py Header -> connection params
│ └── formatting.py Tool response shape helpers
└── tools/
├── devicehub_tools.py
├── dm_tools.py
├── marketplace_tools.py
├── data_tools.py
├── digitaltwins_tools.py
├── system_tools.py
├── lem_tools.py
└── resource_tools.py MCP Resources (live docs.litmus.io)Each *_tools.py exports a TOOLS = [...] list of dicts. server.py concatenates them and dispatches by name.