acm_mcp
by DPBeahr-rok
README.md
# acm_mcp
MCP server for building PLC automation project plans using the Rockwell Automation **ACM (Application Code Manager)** library catalog.
An LLM client connects to this server to browse the ACM catalog, resolve linked library dependencies, and assemble a project hierarchy — controllers, tasks, programs, routines, and AOI definitions — with automatic placement rules and deduplication.
## Quick Start
```bash
cd acm_mcp
uv sync # install dependencies (creates .venv)
uv run python server.py # start server on port 8012
```
The server loads the library catalog from `data/data.db` on startup and exposes tools over **streamable-http** at `http://127.0.0.1:8012/mcp`.
## Configuration
All settings are in `config.yaml`:
```yaml
server:
host: "127.0.0.1"
port: 8012
log_level: "INFO"
transport: "streamable-http"
ssl_certfile: "" # path to TLS cert for HTTPS (optional)
ssl_keyfile: "" # path to TLS key for HTTPS (optional)
acm_browser:
working_dir: "data" # relative to this directory
pull_libraries: false # set true to re-export from ACM
pull_templates: false
pull_database: false
resolver:
max_depth: 10 # linked library recursion limit
```
## Populating the Database
The server relies on a SQLite database (`data/data.db`) built from ACM library exports. On startup, it reads the three `pull_*` flags in `config.yaml` to decide whether to re-run each step of the pipeline:
| Flag | What it does | ACM Browser call |
|------|-------------|-----------------|
| `pull_libraries` | Runs ACMConsole to export registered libraries (HSL4 files) into the working directory | `ExportRegisteredLibraries()` |
| `pull_templates` | Converts exported HSL4 files into XML templates using `ACMTemplateGen.CLI.exe` | `ParseHSL4IntoTemplate()` |
| `pull_database` | Clears and rebuilds `data.db` by parsing templates, extracting parameters, decoding PDF docs, and linking everything | `fillDatabase()` |
### First-time setup (no existing data.db)
Set all three flags to `true` in `config.yaml` to run the full pipeline:
```yaml
acm_browser:
working_dir: "data"
pull_libraries: true
pull_templates: true
pull_database: true
```
Then start the server:
```bash
uv run python server.py
```
This will export libraries from ACM, convert them to templates, and populate `data.db`. The process requires ACM to be installed on the machine.
### Subsequent runs (data.db already exists)
Set all flags to `false` to skip re-export and reuse the existing database:
```yaml
acm_browser:
working_dir: "data"
pull_libraries: false
pull_templates: false
pull_database: false
```
This is the default configuration and makes startup much faster.
### Refreshing the catalog
To pick up newly registered ACM libraries, set all three flags back to `true` and restart the server. You can also selectively re-run steps — for example, set only `pull_database: true` if you've manually updated template files and just need to rebuild the database.
## Tools
| # | Tool | Description |
|---|------|-------------|
| 1 | `resolve_library` | Preview a library's dependencies before adding it. Returns tiered results: tier_1_mandatory (REQUIRED, AOI_AUTO_ADDED, DEDUP, SELECT), tier_2_optional (user decides), tier_3_conditional (conditionals, SELECTs). |
| 2 | `search_libraries` | Search the catalog by text query, LibraryType, Category, or ContentType. |
| 3 | `fuzzy_search_libraries` | Fuzzy name search across the catalog. Use when you have an approximate or misspelled name. Returns results ranked by similarity score. |
| 4 | `list_controllers` | List all available controller types (plain and MachineBuilder variants). |
| 5 | `add_controller` | Add a controller as the root node. Accepts optional `parameter_overrides` (JSON string). MachineBuilder controllers auto-create pre-built tasks. |
| 6 | `add_task` | Create an empty task under a controller (PERIODIC, EVENT, or CONTINUOUS). |
| 7 | `add_program` | Create an empty program under a task. |
| 8 | `add_item` | Add a library item with automatic placement. AOI dependencies are auto-registered; all other dependencies appear in `todo` for explicit linking via `resolve_link`. |
| 9 | `resolve_link` | Bind an unresolved dependency on a node to a specific instance in the project. Replaces `resolve_select` — works with all link types (REQUIRED, SELECT, CONDITIONAL-MET, OPTIONAL). |
| 10 | `get_unresolved_links` | List all unresolved dependency links across the entire project, with existing instances that match each link's candidates. |
| 11 | `remove_item` | Remove a node and all children (cascade). Automatically unresolves any links on remaining nodes that pointed to a removed node, restoring them to their original tag. Reports broken links in the response. |
| 12 | `get_project_views` | Render the project as two text trees: Class View (by type) and Preview (hierarchy). |
| 13 | `get_project_state` | Get the full project state as structured JSON. |
| 14 | `rename_instance` | Rename any node in the project. |
| 15 | `set_project_name` | Set the project name (used as filename on export). |
| 16 | `export_project` | Save the project state to `projects/{name}/{name}.json`. |
| 17 | `load_project` | Load a previously saved project from disk. |
| 18 | `list_projects` | List all saved project files. |
| 19 | `instantiate_in_acm` | Generate ACM XML scripts from an exported project JSON and invoke ACMConsole to instantiate the project in ACM. Uses separate BEGINCREATE/ENDCREATE blocks for project, controller, and objects so the controller's pre-built tasks are fully instantiated before objects reference them. Pre-built items (CLX auto-created tasks/programs) are excluded from the objects XML — their parent HSL4 definitions create them automatically. Does not generate an ACD file — use `export_acd` for that. |
| 20 | `export_acd` | Export the ACM project to an ACD file. Must be called after `instantiate_in_acm`. |
| 21 | `reset_project` | Reset the project, clearing all in-memory state. Use `export_project` first to save work. |
## Typical Workflow
1. Server injects planning context automatically via FastMCP `instructions` (loaded from `prompts/context_injection.md`)
2. Client clarifies vague requests before making tool calls — asks the user what they want, where it goes, and any configuration choices
3. Client searches the catalog with `search_libraries`, `fuzzy_search_libraries`, or `list_controllers` — presents results to user, never assumes first match
4. Client calls `resolve_library` to inspect dependencies before committing
5. Client adds a controller with `add_controller`
6. Client creates tasks/programs with `add_task` / `add_program` (required for plain controllers; MachineBuilder controllers come with pre-built tasks)
7. Client adds library items with `add_item` — placement is automatic, AOI dependencies are auto-registered
8. Client presents `todo` items to the user for each `add_item` response — never silently resolves links
9. Client adds all needed items (in any order), then resolves dependencies with `resolve_link` after presenting binding choices to the user
10. Client calls `get_unresolved_links` to see all pending dependencies across the project
11. Client calls `get_project_views` after each change to show the user
12. Client calls `export_project` to save (blocks if mandatory links are unresolved)
13. Optionally, client calls `instantiate_in_acm` to instantiate the project in ACM, then `export_acd` to generate an ACD file
## Project Structure
```
acm_mcp/
├── server.py # MCP server entry point (21 tools)
├── config.yaml # Server and ACM configuration
├── pyproject.toml # uv project definition
├── uv.lock # Locked dependencies
├── .python-version # Python 3.13
├── models/
│ ├── library.py # Library, PlacementRole, LinkedLibraryRef
│ └── project_state.py # InstanceNode, ProjectState tree
├── services/
│ ├── library_store.py # In-memory catalog with search indexes
│ ├── resolver.py # Recursive linked library resolution
│ ├── project_manager.py # Stateful project builder + export/load
│ └── renderer.py # Class View and Preview tree rendering
├── prompts/
│ └── context_injection.md # LLM planning context (loaded as FastMCP instructions)
├── data/
│ ├── data.db # SQLite catalog (524 libraries)
│ ├── Templates/ # 524 Template.xml source files
│ └── instance_templates/ # XML templates for ACD generation (project, controller, object)
└── projects/ # Saved project files (JSON)
```
## Key Concepts
- **Routine-First Thinking**: The routine is the fundamental working piece — the function call that invokes the AOI (the function definition). When a user asks to "add a conveyor," start by finding the routine and work outward to program, task, and controller.
- **PlacementRole**: Determined by ContentType + LibraryType. Controls where items go in the hierarchy (controller, task, program, routine, AOI).
- **Linked Library Resolution**: Recursive (max depth 10) with explicit, generic (wildcard), template, and conditional resolution modes. Recursion only enters AOI_AUTO_ADDED dependencies — REQUIRED items get their own resolution when manually added, preventing transitive dependencies from leaking onto parent nodes as duplicate ILLib entries.
- **Dependency Tags**: Each dependency gets one tag: `AOI_AUTO_ADDED`, `DEDUP`, `REQUIRED`, `OPTIONAL`, `CONDITIONAL-MET`, `CONDITIONAL-SKIP`, `CONDITIONAL-UNKNOWN`, `SELECT`, `TEMPLATE`, `NOT-FOUND`, or `RESOLVED`. AOIs are auto-handled; all others require explicit user decisions.
- **Instance-Level Binding**: Links bind to specific project instances via `target_node_id`, not catalog numbers. This supports many-to-many scenarios (e.g., multiple Equipment Modules linking to different Unit Modules).
- **No Auto-Resolver**: Dependencies are not silently resolved. The agent must present choices to the user and call `resolve_link` explicitly. Items can be added in any order.
- **AOI Deduplication**: Asset-Control items are globally deduplicated — one definition serves all instances. Tier 1 and tier 3 condition-met AOIs are auto-registered; tier 2 AOIs require user confirmation.
- **Cascade Removal with Link Cleanup**: `remove_item` deletes the target node and all its children, then walks the remaining project tree to find any resolved links that pointed to a removed node. Those links are automatically unresolved and restored to their original tag (e.g., SELECT, REQUIRED). Broken links are reported in the response. Orphaned AOI definitions are not auto-removed — they must be cleaned up manually if no longer needed.
- **Parameter Overrides**: Both `add_controller` and `add_item` accept `parameter_overrides` (JSON string). You may include parameters not present in the library definition — ACM will accept them if the names are valid. **Parameter names are case-sensitive** and must match exactly; incorrect casing causes ACM to silently ignore the parameter.
- **MachineBuilder Controllers**: Come with pre-built periodic tasks at various scan rates. Guard variants include a SafetyTask.
- **CLX Auto-Created Children**: Some libraries (e.g., `raM_Robot_Dvc_DeviceHandler`) define CLX children — tasks and programs that ACM's HSL4 engine creates automatically during instantiation. These are flagged `is_prebuilt=True` in the project state and excluded from the objects XML to avoid conflicts with unresolvable substitution tokens (e.g., `{MotionGroupName}`, `{RbtDvcItf$RobotName}`). ACM resolves these tokens internally through its interface link chain.
- **ACM Console Script Structure**: The instantiation script uses separate BEGINCREATE/ENDCREATE blocks for project, controller, and objects. This ensures the controller's HSL4 fully executes (creating pre-built tasks like `ms0008p08`, `PowerUp_Handler`, `SafetyTask`) before CREATEOBJECTS runs, allowing objects to reference those tasks as ParObj/Task.
- **Export Ordering**: The `catalog_items` in exported JSON preserve insertion order (the order items were added by the user), not alphabetical order. This has no effect on ACM instantiation — ACM parses all objects into an insert list before processing — but reflects the logical build sequence.
- **State**: In-memory during the session. Use `export_project` / `load_project` to persist across sessions.
## Client Configuration
```json
{
"mcpServers": {
"acm_mcp": {
"command": "npx.cmd",
"args": [
"mcp-remote",
"http://127.0.0.1:8012/mcp",
"--allow-http"
]
}
}
}
```
## Dependencies
- `mcp[cli]` — FastMCP framework
- `pyyaml` — Configuration loading
- `uvicorn` — ASGI server for streamable-http transport
- `acm-browser` — Local .whl for ACM library catalog extraction (SQLAlchemy + lxml)This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues