industriagents-ethercat-mcp 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. industriagents_ethercat_mcp-0.3.0/.env.example +30 -0
  2. industriagents_ethercat_mcp-0.3.0/.gitignore +9 -0
  3. industriagents_ethercat_mcp-0.3.0/.python-version +1 -0
  4. industriagents_ethercat_mcp-0.3.0/PKG-INFO +91 -0
  5. industriagents_ethercat_mcp-0.3.0/README.md +74 -0
  6. industriagents_ethercat_mcp-0.3.0/pyproject.toml +51 -0
  7. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/__init__.py +16 -0
  8. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/__main__.py +5 -0
  9. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/_contract/tools.json +1217 -0
  10. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/_version.py +10 -0
  11. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/adapters/__init__.py +1 -0
  12. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/adapters/ec_master.py +1210 -0
  13. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/__init__.py +1 -0
  14. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/common.py +45 -0
  15. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/diagnostics.py +47 -0
  16. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/discovery.py +13 -0
  17. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/execution.py +82 -0
  18. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/metadata.py +32 -0
  19. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/operations.py +14 -0
  20. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/read.py +54 -0
  21. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/resources.py +16 -0
  22. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/application/write.py +92 -0
  23. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/cli.py +172 -0
  24. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/codec.py +198 -0
  25. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/config.py +137 -0
  26. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/domain/__init__.py +1 -0
  27. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/domain/errors.py +31 -0
  28. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/domain/model.py +10 -0
  29. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/domain/policy.py +34 -0
  30. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/domain/ports.py +35 -0
  31. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/ec_master.py +7 -0
  32. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/esi_parser.py +375 -0
  33. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/infrastructure/__init__.py +1 -0
  34. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/infrastructure/audit.py +45 -0
  35. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/infrastructure/maps.py +89 -0
  36. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/mock_bridge.py +326 -0
  37. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/protocol/__init__.py +1 -0
  38. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/protocol/contract.py +158 -0
  39. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/protocol/support.py +8 -0
  40. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/protocol/tools.py +214 -0
  41. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/server.py +248 -0
  42. industriagents_ethercat_mcp-0.3.0/src/ethercat_mcp/tools.py +34 -0
  43. industriagents_ethercat_mcp-0.3.0/tests/conftest.py +99 -0
  44. industriagents_ethercat_mcp-0.3.0/tests/fake_pysoem.py +507 -0
  45. industriagents_ethercat_mcp-0.3.0/tests/fixtures/sample_esi.xml +128 -0
  46. industriagents_ethercat_mcp-0.3.0/tests/test_architecture_contract.py +184 -0
  47. industriagents_ethercat_mcp-0.3.0/tests/test_cancellation.py +788 -0
  48. industriagents_ethercat_mcp-0.3.0/tests/test_cli.py +192 -0
  49. industriagents_ethercat_mcp-0.3.0/tests/test_codec.py +72 -0
  50. industriagents_ethercat_mcp-0.3.0/tests/test_config.py +65 -0
  51. industriagents_ethercat_mcp-0.3.0/tests/test_esi_parser.py +239 -0
  52. industriagents_ethercat_mcp-0.3.0/tests/test_integration_mock.py +206 -0
  53. industriagents_ethercat_mcp-0.3.0/tests/test_mcp_internals.py +77 -0
  54. industriagents_ethercat_mcp-0.3.0/tests/test_mock_bridge.py +185 -0
  55. industriagents_ethercat_mcp-0.3.0/tests/test_mock_lifecycle.py +54 -0
  56. industriagents_ethercat_mcp-0.3.0/tests/test_pysoem_backend.py +288 -0
  57. industriagents_ethercat_mcp-0.3.0/tests/test_safety_regressions.py +554 -0
  58. industriagents_ethercat_mcp-0.3.0/tests/test_stdio.py +199 -0
  59. industriagents_ethercat_mcp-0.3.0/tests/test_tools.py +361 -0
  60. industriagents_ethercat_mcp-0.3.0/uv.lock +976 -0
@@ -0,0 +1,30 @@
1
+ # Example settings for ethercat-mcp. `ethercat-mcp --help` lists them all.
2
+ #
3
+ # Either pass a copy with --env-file PATH (any setting), or copy it to
4
+ # ethercat-python/.env, the only .env the server reads by itself (from a source
5
+ # checkout; never the working directory or a parent, none for an installed
6
+ # package). That implicit .env may only set ETHERCAT_* and SLAVE_MAP_FILE, and
7
+ # never the safety settings: ETHERCAT_WRITES_ENABLED,
8
+ # ETHERCAT_STATE_CHANGE_ENABLED, ETHERCAT_PD_WATCHDOG_MS and
9
+ # ETHERCAT_EXPECTED_WKC are ignored there with a warning, in any letter case.
10
+ # Set those in the process environment or an --env-file.
11
+
12
+ # pysoem = a real bus through PySOEM; mock = ethercat-mock-slave (simulated)
13
+ ETHERCAT_BACKEND=pysoem
14
+ ETHERCAT_INTERFACE=eth0
15
+ # ETHERCAT_MOCK_HOST=127.0.0.1
16
+ # ETHERCAT_MOCK_PORT=6700
17
+
18
+ # Process-data cycle (us), state-change timeout (us)
19
+ # ETHERCAT_CYCLE_TIME=1000
20
+ # ETHERCAT_TIMEOUT=500000
21
+
22
+ # SLAVE_MAP_FILE=/absolute/path/to/slaves.json
23
+ # ETHERCAT_ESI_PATH=/absolute/path/to/esi
24
+ # ETHERCAT_ESI_MAX_MB=16
25
+
26
+ # Only from the environment or --env-file (ignored in ethercat-python/.env):
27
+ # ETHERCAT_WRITES_ENABLED=false
28
+ # ETHERCAT_STATE_CHANGE_ENABLED=false
29
+ # ETHERCAT_PD_WATCHDOG_MS=100
30
+ # ETHERCAT_EXPECTED_WKC=0
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ .env
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ __pycache__/
7
+ *.py[cod]
8
+ dist/
9
+ build/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.5
2
+ Name: industriagents-ethercat-mcp
3
+ Version: 0.3.0
4
+ Summary: Model Context Protocol server for EtherCAT (PySOEM + MCP tooling).
5
+ Project-URL: Repository, https://github.com/IndustriAgents/EtherCAT-MCP
6
+ Author: IndustriAgents
7
+ License: MIT
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: anyio>=4.7.0
10
+ Requires-Dist: jsonschema<5,>=4.23
11
+ Requires-Dist: lxml>=4.9.3
12
+ Requires-Dist: mcp[cli]<2,>=1.22.0
13
+ Requires-Dist: pydantic<2.13,>=2.11.0
14
+ Requires-Dist: pysoem>=1.1.12
15
+ Requires-Dist: python-dotenv>=1.1.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ > **0.3.0 package and tool migration:** Install `industriagents-ethercat-mcp`
19
+ > from npm (requires uv) or PyPI; the command remains `ethercat-mcp`.
20
+ > The advertised catalog now contains **11 tools**. Older tool names in
21
+ > the examples below remain compatibility calls. See the
22
+ > [architecture and canonical tool guide](../docs/architecture.md)
23
+ > and [release guide](../docs/release.md).
24
+
25
+ # ethercat-python
26
+
27
+ 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).
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.11+
32
+ - [uv](https://github.com/astral-sh/uv)
33
+ - For a real bus: a network interface wired to the EtherCAT segment, and root (or `cap_net_raw` and `cap_net_admin` on Linux)
34
+ - A C toolchain where PySOEM has no wheel (Apple silicon, Linux on ARM); on Windows, [Npcap](https://npcap.com/)
35
+
36
+ ## Quick Start
37
+
38
+ Run these from this folder (`ethercat-python/`).
39
+
40
+ Against the mock slave (start it first, see [`../ethercat-mock-slave`](../ethercat-mock-slave)):
41
+
42
+ ```bash
43
+ uv sync
44
+ npx @modelcontextprotocol/inspector -e ETHERCAT_BACKEND=mock uv run ethercat-mcp
45
+ ```
46
+
47
+ Against a real bus, as root:
48
+
49
+ ```bash
50
+ uv sync
51
+ sudo ETHERCAT_INTERFACE=eth0 PYTHONDONTWRITEBYTECODE=1 .venv/bin/ethercat-mcp
52
+ ```
53
+
54
+ 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.
55
+
56
+ [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.
57
+
58
+ ## Layout
59
+
60
+ ```
61
+ ethercat-python/
62
+ ├── README.md
63
+ ├── pyproject.toml
64
+ ├── uv.lock
65
+ ├── .python-version
66
+ ├── .env.example
67
+ ├── src/ethercat_mcp
68
+ │ ├── cli.py # argparse, .env loading, startup
69
+ │ ├── config.py # EthercatConfig.from_env()
70
+ │ ├── server.py # FastMCP wiring, envelope for bad arguments, stdout guard
71
+ │ ├── tools.py # the 14 MCP tools, slave map
72
+ │ ├── ec_master.py # PySOEM backend, cyclic process data, async facade with the gates
73
+ │ ├── mock_bridge.py # mock transport (JSON over TCP to ethercat-mock-slave)
74
+ │ ├── codec.py # argument parsing, CoE value encoding
75
+ │ └── esi_parser.py # ETG.2000 ESI parser
76
+ └── tests/
77
+ ├── fake_pysoem.py # stand-in for the PySOEM API used by the backend
78
+ ├── test_safety_regressions.py # stale outputs, live-segment scan, working counters, no repeats
79
+ ├── fixtures/ # hand-written, ESI-shaped sample (not a vendor file)
80
+ └── test_*.py # unit tests, stdio tests, end-to-end test against the mock
81
+ ```
82
+
83
+ ## Tests
84
+
85
+ ```bash
86
+ uv sync
87
+ uv run pytest
88
+ uv run ruff check . && uv run ruff format --check .
89
+ ```
90
+
91
+ `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.
@@ -0,0 +1,74 @@
1
+ > **0.3.0 package and tool migration:** Install `industriagents-ethercat-mcp`
2
+ > from npm (requires uv) or PyPI; the command remains `ethercat-mcp`.
3
+ > The advertised catalog now contains **11 tools**. Older tool names in
4
+ > the examples below remain compatibility calls. See the
5
+ > [architecture and canonical tool guide](../docs/architecture.md)
6
+ > and [release guide](../docs/release.md).
7
+
8
+ # ethercat-python
9
+
10
+ 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).
11
+
12
+ ## Requirements
13
+
14
+ - Python 3.11+
15
+ - [uv](https://github.com/astral-sh/uv)
16
+ - For a real bus: a network interface wired to the EtherCAT segment, and root (or `cap_net_raw` and `cap_net_admin` on Linux)
17
+ - A C toolchain where PySOEM has no wheel (Apple silicon, Linux on ARM); on Windows, [Npcap](https://npcap.com/)
18
+
19
+ ## Quick Start
20
+
21
+ Run these from this folder (`ethercat-python/`).
22
+
23
+ Against the mock slave (start it first, see [`../ethercat-mock-slave`](../ethercat-mock-slave)):
24
+
25
+ ```bash
26
+ uv sync
27
+ npx @modelcontextprotocol/inspector -e ETHERCAT_BACKEND=mock uv run ethercat-mcp
28
+ ```
29
+
30
+ Against a real bus, as root:
31
+
32
+ ```bash
33
+ uv sync
34
+ sudo ETHERCAT_INTERFACE=eth0 PYTHONDONTWRITEBYTECODE=1 .venv/bin/ethercat-mcp
35
+ ```
36
+
37
+ 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.
38
+
39
+ [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.
40
+
41
+ ## Layout
42
+
43
+ ```
44
+ ethercat-python/
45
+ ├── README.md
46
+ ├── pyproject.toml
47
+ ├── uv.lock
48
+ ├── .python-version
49
+ ├── .env.example
50
+ ├── src/ethercat_mcp
51
+ │ ├── cli.py # argparse, .env loading, startup
52
+ │ ├── config.py # EthercatConfig.from_env()
53
+ │ ├── server.py # FastMCP wiring, envelope for bad arguments, stdout guard
54
+ │ ├── tools.py # the 14 MCP tools, slave map
55
+ │ ├── ec_master.py # PySOEM backend, cyclic process data, async facade with the gates
56
+ │ ├── mock_bridge.py # mock transport (JSON over TCP to ethercat-mock-slave)
57
+ │ ├── codec.py # argument parsing, CoE value encoding
58
+ │ └── esi_parser.py # ETG.2000 ESI parser
59
+ └── tests/
60
+ ├── fake_pysoem.py # stand-in for the PySOEM API used by the backend
61
+ ├── test_safety_regressions.py # stale outputs, live-segment scan, working counters, no repeats
62
+ ├── fixtures/ # hand-written, ESI-shaped sample (not a vendor file)
63
+ └── test_*.py # unit tests, stdio tests, end-to-end test against the mock
64
+ ```
65
+
66
+ ## Tests
67
+
68
+ ```bash
69
+ uv sync
70
+ uv run pytest
71
+ uv run ruff check . && uv run ruff format --check .
72
+ ```
73
+
74
+ `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.
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "industriagents-ethercat-mcp"
3
+ version = "0.3.0"
4
+ description = "Model Context Protocol server for EtherCAT (PySOEM + MCP tooling)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "IndustriAgents" }]
9
+ # Floors are the lowest versions the test suite passes with (CI's lowest-direct job).
10
+ # mcp is capped below 2: server.py relies on FastMCP internals (tests/test_mcp_internals.py).
11
+ dependencies = [
12
+ "jsonschema>=4.23,<5",
13
+ "anyio>=4.7.0",
14
+ # Contract wire errors require CallToolResult passthrough (tested on 1.22+).
15
+ "mcp[cli]>=1.22.0,<2",
16
+ # The supported MCP 1.x floor uses typing helpers removed in Pydantic 2.13.
17
+ "pydantic>=2.11.0,<2.13",
18
+ "python-dotenv>=1.1.0",
19
+ "pysoem>=1.1.12",
20
+ "lxml>=4.9.3",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/IndustriAgents/EtherCAT-MCP"
25
+
26
+ [project.scripts]
27
+ ethercat-mcp = "ethercat_mcp.cli:main"
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "pytest>=8.2.0",
32
+ "ruff>=0.16.0",
33
+ ]
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.ruff]
40
+ line-length = 120
41
+ target-version = "py311"
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "W", "I", "B", "UP"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ addopts = "-ra"
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/ethercat_mcp"]
@@ -0,0 +1,16 @@
1
+ """EtherCAT MCP server package."""
2
+
3
+ from typing import Any
4
+
5
+ from ._version import __version__
6
+
7
+ __all__ = ["EthercatMCPServer", "__version__"]
8
+
9
+
10
+ def __getattr__(name: str) -> Any:
11
+ # Lazy, so `ethercat-mcp --help` does not import the MCP SDK.
12
+ if name == "EthercatMCPServer":
13
+ from .server import EthercatMCPServer
14
+
15
+ return EthercatMCPServer
16
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,5 @@
1
+ """``python -m ethercat_mcp`` runs the same entry point as ``ethercat-mcp``."""
2
+
3
+ from .cli import main
4
+
5
+ main()