lcm2m-caddis-mcp
# lcm2m-caddis-mcp
> [!WARNING]
> **This server is deprecated and no longer works.** The legacy
> username/password authentication it relied on has been retired, and this
> package receives no further updates. Use the official hosted MCP server
> instead: connect your assistant to **`https://api.lcm2m.com/mcp`** — there is
> nothing to install and no credentials to configure; sign in through your
> browser on first use. See the [MCP Server page in the Caddis Systems
> documentation](https://docs.caddissystems.com/API/MCP+Server) for setup
> instructions.
An MCP server that exposes the LCM2M Caddis **VM2M API** to LLM tools like Claude
Desktop, Claude Code, and Cursor. Read-only wrappers over equipment, runs, cycles,
telemetry, alarms, and more — served as [TOON](https://toonformat.dev/) so LLM
context stays cheap.
## Requirements
- An LCM2M account (username and password)
- **Node.js 25+** or **Docker**
## Use with an MCP client
The server speaks MCP over stdio and works with any MCP-compatible client —
Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot Chat), Zed,
Continue, Cline, Goose, and others. Configure your client to launch
`npx -y @lcm2m/caddis-mcp` with `CADDIS_USERNAME` and `CADDIS_PASSWORD` in its
env. Concrete examples for the two most common config styles follow.
### Claude Code (CLI)
```sh
claude mcp add caddis \
--env CADDIS_USERNAME=you@example.com \
--env CADDIS_PASSWORD='your-password' \
-- npx -y @lcm2m/caddis-mcp
```
### Claude Desktop, Cursor, and similar (JSON config)
Most clients accept an `mcpServers` block in a JSON config file — for example
`claude_desktop_config.json` (Claude Desktop) or `.cursor/mcp.json` (Cursor):
```json
{
"mcpServers": {
"caddis": {
"command": "npx",
"args": ["-y", "@lcm2m/caddis-mcp"],
"env": {
"CADDIS_USERNAME": "you@example.com",
"CADDIS_PASSWORD": "your-password"
}
}
}
}
```
Other clients (Windsurf, VS Code, Zed, Continue, Cline, etc.) use similar
formats — consult your client's MCP docs for the exact config path and schema.
Restart the client after editing config.
## Configuration
| Variable | Default | Description |
|---|---|---|
| `CADDIS_USERNAME` | *(required)* | LCM2M account username/email |
| `CADDIS_PASSWORD` | *(required)* | LCM2M account password |
| `CADDIS_COMPANY_ID` | *(auto)* | Required if your user belongs to multiple companies |
| `CADDIS_MAX_RETRIES` | `3` | Max 429 retries per request |
| `CADDIS_MAX_RETRY_WAIT_MS` | `30000` | Max total wait budget per request |
## Available tools
All tools are **read-only** and prefixed with `caddis_`. Each maps 1:1 to a VM2M
route; responses are [TOON](https://toonformat.dev/)-encoded (a compact,
JSON-equivalent format mixing YAML-style nesting with CSV-style tables).
- **Company:** `get_company`
- **Devices:** `list_devices`, `get_device`
- **Equipment:** `list_equipment`, `get_equipment`, `get_equipment_utilization`,
`get_equipment_schedule`, `get_equipment_cycles`, `get_equipment_statuslogs`,
`get_equipment_telemetry`, `get_equipment_shift_history`,
`list_equipment_excessive_downtimes`, `get_equipment_excessive_downtime`
- **Org units / tree:** `get_org_unit`, `get_org_unit_schedule`,
`list_org_unit_excessive_downtimes`, `get_tree`
- **Alarms:** `list_alarms`
- **Tags:** `list_tags`, `get_tag`, `list_tag_groups`, `get_tag_group`
- **Runs:** `list_runs`, `get_run`, `get_run_cycles`
- **Status reasons:** `list_status_reasons`
- **Catalog:** `list_manufacturers`, `list_models`, `list_categories`
## Troubleshooting
- **`Missing credentials`** — `CADDIS_USERNAME` / `CADDIS_PASSWORD` aren't reaching
the child process. With `docker run -e VAR`, `VAR` must also be set in the parent
shell.
- **`This user belongs to multiple companies…`** — set `CADDIS_COMPANY_ID` to one
of the numeric IDs listed in the error.
- **`401 Unauthorized`** — bad creds. Test with:
```sh
curl -X POST https://api.lcm2m.com/vm2m/sessions \
-H 'Content-Type: application/json' \
-d '{"username":"you@example.com","password":"...","company_id":1}'
```
- **Persistent `429`** — raise `CADDIS_MAX_RETRY_WAIT_MS` or back off the client's
call rate.
- **Blank Inspector page in Firefox** — use Chrome/Brave/Edge, or
`npx @modelcontextprotocol/inspector --cli ...`.
## Alternative install
The MCP client examples above use `npx`. To run from Docker or a local clone
instead, build using one of the methods below and swap the `command`/`args` in
your client config.
### Docker
```sh
git clone https://github.com/LCM2M/lcm2m-caddis-mcp.git
cd lcm2m-caddis-mcp
docker build --target runtime -t lcm2m-caddis-mcp .
```
Command string:
```sh
docker run -i --rm -e CADDIS_USERNAME -e CADDIS_PASSWORD lcm2m-caddis-mcp
```
### Local Node
```sh
git clone https://github.com/LCM2M/lcm2m-caddis-mcp.git
cd lcm2m-caddis-mcp
npm install
npm run build
```
Command string: `node /absolute/path/to/lcm2m-caddis-mcp/dist/index.js`
## How it works
- **Auth:** first call hits `POST /vm2m/sessions` → JWT, cached and proactively
refreshed 30s before expiry. 401 triggers a single re-login + retry. The
`Authorization` header carries the raw JWT (no `Bearer` prefix).
- **Rate limiting:** backend enforces 20 req/10s per endpoint and 60 req/10s per
user. On 429, the client parses `Retry-After`, applies ±20% jitter, and retries
up to `CADDIS_MAX_RETRIES` (capped by `CADDIS_MAX_RETRY_WAIT_MS`).
- **Response format:** [TOON](https://toonformat.dev/) — a token-efficient JSON
dialect that mixes YAML-style nesting with CSV-style tables (~40% fewer tokens
than JSON). The raw body is passed through, fenced as ` ```toon ... ``` ` so
the model sees an unambiguous format boundary.
- **Errors:** 4xx responses surface as `isError: true` tool results so the LLM can
see the backend error body and recover; 5xx rethrow.
## Development
```sh
# Create .env.local with CADDIS_USERNAME and CADDIS_PASSWORD (gitignored).
npm install
npm run dev # MCP Inspector web UI + tsx
npm test # node --test via tsx
npm run build # tsc -> dist/
npm run typecheck
npm run lint # biome check
```
`npm run dev` opens the MCP Inspector in a **Chromium browser** (Firefox has
rendering issues). Edit source, click **Restart**, re-run the tool.
### Project layout
```
src/
index.ts # MCP server entry (stdio)
config.ts # zod env config
client.ts # CaddisApiClient: login, retry, rate-limit
tools/
schemas.ts # shared zod helpers + runTool error wrapper
index.ts # tool registration
wrappers/ # 1:1 VM2M route wrappers
composite/ # higher-level multi-call tools
```
TDQS
Scored across 31 tools
All tools have clearly distinct purposes: get vs list for single vs multiple entities, and specialized tools like caddis_get_tree for hierarchy, caddis_batch for batching. Descriptions explicitly guide when to use each one, minimizing confusion.
All tools follow the consistent pattern 'caddis_<verb>_<noun>' with snake_case. Verbs are uniformly 'get' for single entities and 'list' for multiple, with only minor outliers like 'caddis_get_tree' and 'caddis_batch' that still fit the pattern.
31 tools is on the high side, exceeding the typical recommended range of 3-15. However, the domain of manufacturing monitoring is rich and warrants many specialized queries. The count is justified but still feels heavy; it's borderline between 2 and 3.
The server covers a wide range of read-only operations for equipment, org units, alarms, tags, runs, etc. However, it lacks any write operations (create, update, delete), which is a notable gap if full lifecycle management is expected. For a read-only monitoring API, it is reasonably complete.