cmms-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cmms-mcp-server Show me the plant overview and any critical unacknowledged alarms"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
cmms-mcp-server
An MCP server that lets an AI assistant work with a maintenance management system (CMMS): look up assets, read and acknowledge alarms, and create and move work orders through a real life cycle, with role checks and an audit trail.
Everything runs on fictional data (a made-up plant with Quanta compressors, Ironwood presses, Helix lathes, Nimbus chillers and Vortex pumps; 16 assets, 12 alarms, 9 work orders, 5 users). There is no connection to any real system.
AI client --MCP/stdio--> tools.py (schemas, annotations, error mapping)
|
service.py (validation, roles, state machine, transactions)
|
SQLite (db.py)The domain logic in service.py knows nothing about MCP, so it is tested directly, and the MCP layer is
tested separately, in-process and over a real stdio subprocess.
Run it
uv sync
uv run cmms-mcp-server # stdio, in-memory, reseeded each start
uv run cmms-mcp-server --db plant.sqlite3 # persistent (created and seeded when missing)
uv run cmms-mcp-server --read-only # write tools are not registered at all
uv run cmms-mcp-server --fixed-now 2026-10-09T06:00:00Z # freeze the clock (the seed is set on that day)
uv run python examples/demo_client.py # scripted client session, see belowAdd it to an MCP client with .mcp.json.example (command uv run --directory ... cmms-mcp-server).
SKILL.md is a companion skill that teaches an assistant the order of calls and the safety rules.
Related MCP server: PRAETOR-MCP
Tools
tool | kind | what it does |
| read | counts of assets/alarms/work orders, overdue work, class-A assets with unacknowledged alarms |
| read | filter by status, type, location, criticality, free text; paged |
| read | one asset + its uncleared alarms, active work orders, last completed work order |
| read | filter by state, severity, asset, time; most severe first |
| read | filter by status, priority, asset, assignee, overdue; highest priority first |
| read | one work order with its full event history |
| write, idempotent | take ownership of an alarm; repeating it changes nothing |
| write | optionally linked to an alarm; one active work order per alarm |
| write | supervisors only |
| write | the life cycle below, with role and note rules |
Also: resource cmms://glossary and prompt shift_handover.
Work-order life cycle:
open -> assigned -> in_progress <-> on_hold -> completed -> closed
\ \ \ \-> in_progress (reopen, supervisor)
`--------`--------------------------`-> cancelledrole | may |
viewer | read |
technician | acknowledge, create, start / hold / complete work assigned to them |
supervisor | everything: assign, unassign, close, cancel, reopen, act for anyone |
A note (5+ characters) is required to hold, complete, cancel, unassign or reopen.
What a session looks like
Real output of examples/demo_client.py (long values shortened by the script):
> acknowledge_alarm as a viewer (must fail)
ERROR Error executing tool acknowledge_alarm: forbidden: view.d is a viewer and cannot acknowledge alarms
> acknowledge_alarm as tech.a
{"alarm": {"id": "ALM-0001", "asset_id": "AST-0001", "severity": "critical", "code": "OIL_TEMP_HIGH", ...
> create_work_order
{"work_order": {"id": "WO-2026-0010", "asset_id": "AST-0001", "title": "Oil temperature high: inspect cooler", ...
> create_work_order again for the same alarm (must fail)
ERROR Error executing tool create_work_order: conflict: alarm ALM-0001 already has an active work order: WO-2026-0010
> transition straight to completed (must fail)
ERROR Error executing tool transition_work_order: invalid: cannot move a work order from 'open' to 'completed'; allowed: assigned, cancelledDesign decisions
Errors are written for a model. A domain error becomes a tool error
code: reasonthat says what was wrong and what is allowed ("allowed: assigned, cancelled"). Unexpected exceptions stay generic: the SDK hides their text, and a test checks that a crash message containing a path does not reach the client.Typed outputs. Tools return pydantic models, so each has an output schema and clients get
structuredContent. A test fails if any tool or parameter lacks a description.Annotations. Reads are marked
readOnlyHint;acknowledge_alarmandassign_work_orderare idempotent; nothing is destructive (records are never deleted).--read-onlyremoves the write tools instead of rejecting calls, so the model never sees them.Duplicates are prevented where retries happen. A model may call
create_work_ordertwice; the second call for the same alarm is a conflict that names the existing work order.Writes are all-or-nothing (
BEGIN IMMEDIATE). A test makes the event insert fail and checks that the status update is rolled back too.Injection and wildcards. All SQL is parameterised;
%and_in search text are escaped.Bounded output. Every list has
limit(1-100) andnext_offset.Injectable clock (
--fixed-now) so "overdue" and ids are deterministic in tests and demos.
Tests
74 test cases (uv run pytest): state machine; every read filter and ordering; every write rule and
permission; rollback; tool inventory, annotations, schemas, documentation of every parameter; error
mapping; read-only mode; resource and prompt; and a full triage flow plus persistence across two server
processes over real stdio.
Mutation check: 20 deliberate bugs (viewer allowed to acknowledge, duplicate guard removed, ownership check dropped, no rollback, unescaped LIKE, crash text leaking, write tools registered in read-only mode, terminal state with an exit, ...) were each caught by at least one test. One mutant first survived because it was not actually leaking; it was rewritten and then caught.
Limitations
No authentication.
actoris a claimed identity checked against a user table. That is fine for a demo and for stdio on your own machine; a real deployment must take the identity from the transport (for example OAuth on streamable HTTP) and never from a tool argument. Thestreamable-httpoption exists but is unauthenticated.One process, one SQLite connection with a lock. No multi-writer support, no migrations.
Offset paging: rows added between pages can shift the window.
Not tested against a real assistant (Claude Desktop, Claude Code, ...); it is tested with the MCP SDK's own client.
SKILL.mdis guidance I wrote and have not measured.Written for
mcp2.x (MCPServer,from mcp.client.client import Client); version 1.x usedFastMCPand different client entry points, hence themcp>=2.2,<3pin.
Development
uv sync
uv run pytest
uv run ruff checkLicense
MIT, see LICENSE.
Available Tools
10 toolsacknowledge_alarmAIdempotent
Take ownership of an active alarm. Safe to repeat: an already acknowledged alarm is returned unchanged with already_acknowledged=true.
Inputs: actor (required), alarm_id (required). Returns: alarm, already_acknowledged
assign_work_orderAIdempotent
Assign (or reassign) an open or assigned work order. Supervisors only.
Inputs: actor (required), assignee (required), work_order_id (required). Returns: events, work_order
create_work_orderA
Create a new work order (status open). Check list_work_orders for the asset first to avoid duplicates.
Inputs: actor (required), title (required), asset_id (required), wo_type, +4 more. Returns: events, work_order
get_assetARead-onlyIdempotent
One asset with its uncleared alarms, its active work orders and its last completed work order.
Inputs: asset_id (required). Returns: asset, active_alarms, open_work_orders, last_completed_work_order
get_plant_overviewARead-onlyIdempotent
Counts of assets, active alarms and work orders, overdue work, and critical (A) assets with unacknowledged alarms. Call this first.
Inputs: no named parameters. Returns: as_of, assets_by_status, overdue_work_orders, work_orders_by_status, +2 more
get_work_orderBRead-onlyIdempotent
One work order with its full history of events (who did what, when, and why).
Inputs: work_order_id (required). Returns: events, work_order
list_alarmsBRead-onlyIdempotent
List alarms, most severe and most recent first.
Inputs: limit, since, state, offset, +2 more. Returns: items, total, next_offset
list_assetsARead-onlyIdempotent
List assets, most critical first. All filters are optional and combine with AND.
Inputs: type, limit, query, offset, +3 more. Returns: items, total, next_offset
list_work_ordersBRead-onlyIdempotent
List work orders, highest priority first, then earliest due date.
Inputs: limit, offset, status, asset_id, +3 more. Returns: items, total, next_offset
transition_work_orderA
Move a work order along its life cycle. The assignee or a supervisor can start, hold and complete; only a supervisor can close, cancel, unassign or reopen. Use assign_work_order for open -> assigned.
Inputs: actor (required), to_status (required), work_order_id (required), note. Returns: events, work_order
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 10 tools
Most tools target a clearly distinct resource+action (overview vs list vs get vs mutate). The only real overlap is assign_work_order vs transition_work_order, but descriptions explicitly carve out the boundary ('Use assign_work_order for open -> assigned'), which mostly resolves it.
Every tool follows a clean verb_noun snake_case pattern (get_plant_overview, list_assets, get_asset, acknowledge_alarm, create_work_order, assign_work_order, transition_work_order). No casing or verb-style deviations.
10 tools is well within the ideal 3-15 range and each one maps to a distinct CMMS capability (dashboard, asset/alarm/WO reads, and key WO/alarm mutations). Nothing feels padded or missing on count grounds.
Work order lifecycle is well covered (create, assign, transition, history), but there are notable gaps: get_asset references 'uncleared' alarms yet no tool clears/resolves an alarm, and there is no way to update a work order or append notes/log entries. Asset creation/editing is also absent, leaving some dead ends.
Maintenance
Related MCP Connectors
Build and run grounded business agents over MCP: agents, knowledge bases, skills, Storylines.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
Public MCP digital twin with synthetic systems and an agent firewall. No customer data.
Hosted MCP memory and agent control plane for durable conversations, jobs, and operations.
Related MCP Servers
- AlicenseCqualityBmaintenanceReference implementation of the Operational Ontology pattern: MCP tools are generated from a typed business domain model (objects, links, actions) — one tool per query shape and per action, deliberately no raw SQL tool. Writes pass business-rule preconditions, are audited, and write back to the systems of record; refusals are machine-readable. Ships with a demo orders ontology spanning two legacy34203MIT
- AlicenseNot gradedqualityDmaintenanceProvides a local synthetic MCP stdio service for querying maintenance evidence and preparing human-review-gated advisory packets, with deterministic governance, evidence boundary tracing, and Agent K integrity scoring to separate retrieved evidence from model inference while preserving human review authority.11 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to generate audit reports and query financial ledger data via MCP. This demo version contains intentional security vulnerabilities for security testing and should not be used in production.-
- AlicenseNot gradedqualityDmaintenanceA modular, security-focused MCP server that exposes enterprise-ready tools for filesystem, database, REST API, system utilities, and AI generation, with authentication, authorization, and audit logging.MIT