Metadata-Version: 2.5
Name: industriagents-ethercat-mcp
Version: 0.3.0
Summary: Model Context Protocol server for EtherCAT (PySOEM + MCP tooling).
Project-URL: Repository, https://github.com/IndustriAgents/EtherCAT-MCP
Author: IndustriAgents
License: MIT
Requires-Python: >=3.11
Requires-Dist: anyio>=4.7.0
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: lxml>=4.9.3
Requires-Dist: mcp[cli]<2,>=1.22.0
Requires-Dist: pydantic<2.13,>=2.11.0
Requires-Dist: pysoem>=1.1.12
Requires-Dist: python-dotenv>=1.1.0
Description-Content-Type: text/markdown

> **0.3.0 package and tool migration:** Install `industriagents-ethercat-mcp`
> from npm (requires uv) or PyPI; the command remains `ethercat-mcp`.
> The advertised catalog now contains **11 tools**. Older tool names in
> the examples below remain compatibility calls. See the
> [architecture and canonical tool guide](../docs/architecture.md)
> and [release guide](../docs/release.md).

# ethercat-python

Python MCP server for EtherCAT using [PySOEM](https://pysoem.readthedocs.io/). It exposes the `ethercat-mcp` CLI so MCP-compatible clients can scan the bus, read slave states, read and write CoE objects (SDOs), exchange process data (PDOs), request state changes and parse ESI files. Writes and state changes are off by default. Every tool returns a `{ success, data, error, meta }` envelope, invalid arguments included, and `meta.backend` says whether a real bus (`pysoem`) or the mock slave (`mock`) answered. The PySOEM paths are unit-tested against a fake `pysoem` and have not been verified on hardware yet; see [Known limitations](../README.md#known-limitations).

## Requirements

- Python 3.11+
- [uv](https://github.com/astral-sh/uv)
- For a real bus: a network interface wired to the EtherCAT segment, and root (or `cap_net_raw` and `cap_net_admin` on Linux)
- A C toolchain where PySOEM has no wheel (Apple silicon, Linux on ARM); on Windows, [Npcap](https://npcap.com/)

## Quick Start

Run these from this folder (`ethercat-python/`).

Against the mock slave (start it first, see [`../ethercat-mock-slave`](../ethercat-mock-slave)):

```bash
uv sync
npx @modelcontextprotocol/inspector -e ETHERCAT_BACKEND=mock uv run ethercat-mcp
```

Against a real bus, as root:

```bash
uv sync
sudo ETHERCAT_INTERFACE=eth0 PYTHONDONTWRITEBYTECODE=1 .venv/bin/ethercat-mcp
```

sudo resets the environment, so variables you exported and an MCP client's `env` block do not reach the root process. Put every setting on the sudo line as above, or use `--env-file PATH`. The project's own `ethercat-python/.env` is read too (never a `.env` in the working directory), but it can never set `ETHERCAT_WRITES_ENABLED`, `ETHERCAT_STATE_CHANGE_ENABLED`, `ETHERCAT_PD_WATCHDOG_MS` or `ETHERCAT_EXPECTED_WKC`. `--print-config` shows what the server sees, and the process then waits on stdin for an MCP client (for example `npx @modelcontextprotocol/inspector sudo ETHERCAT_INTERFACE=eth0 PYTHONDONTWRITEBYTECODE=1 .venv/bin/ethercat-mcp`). The server runs from `.venv` because `sudo uv run` usually stops with `sudo: uv: command not found`: sudo's `secure_path` (Ubuntu, Debian, Fedora, RHEL) leaves out `~/.local/bin`, where uv's installer and pipx put `uv`. `PYTHONDONTWRITEBYTECODE=1` keeps root from leaving root-owned `__pycache__` folders that would break your next `uv sync`. `eth0` is a Linux example; macOS names interfaces `en0`, `en1`, and so on. To let an MCP client start the server without sudo on Linux (`setcap` on the interpreter), see [Real bus](../README.md#real-bus) in the top-level README.

[Configuration](../README.md#configuration) in the top-level README lists every setting, and [`.env.example`](.env.example) shows them. All of them are read at startup, after `ethercat-python/.env` (found from the package's location, never from the working directory or a parent; none for an installed package) or the `--env-file` is loaded; variables already in the environment win. The implicit `.env` may only set `ETHERCAT_*` and `SLAVE_MAP_FILE`, and never the safety settings; anything else in it is ignored with a warning.

## Layout

```
ethercat-python/
├── README.md
├── pyproject.toml
├── uv.lock
├── .python-version
├── .env.example
├── src/ethercat_mcp
│   ├── cli.py           # argparse, .env loading, startup
│   ├── config.py        # EthercatConfig.from_env()
│   ├── server.py        # FastMCP wiring, envelope for bad arguments, stdout guard
│   ├── tools.py         # the 14 MCP tools, slave map
│   ├── ec_master.py     # PySOEM backend, cyclic process data, async facade with the gates
│   ├── mock_bridge.py   # mock transport (JSON over TCP to ethercat-mock-slave)
│   ├── codec.py         # argument parsing, CoE value encoding
│   └── esi_parser.py    # ETG.2000 ESI parser
└── tests/
    ├── fake_pysoem.py   # stand-in for the PySOEM API used by the backend
    ├── test_safety_regressions.py  # stale outputs, live-segment scan, working counters, no repeats
    ├── fixtures/        # hand-written, ESI-shaped sample (not a vendor file)
    └── test_*.py        # unit tests, stdio tests, end-to-end test against the mock
```

## Tests

```bash
uv sync
uv run pytest
uv run ruff check . && uv run ruff format --check .
```

`tests/test_integration_mock.py` starts `../ethercat-mock-slave/ethercat_mock_slave.py` on a free loopback port and drives `ethercat-mcp` over stdio with the MCP client SDK. Nothing in the suite opens a network interface.
