labmcp-epics 0.1.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.
- labmcp_epics-0.1.0/.gitignore +15 -0
- labmcp_epics-0.1.0/PKG-INFO +139 -0
- labmcp_epics-0.1.0/README.md +125 -0
- labmcp_epics-0.1.0/pyproject.toml +41 -0
- labmcp_epics-0.1.0/server.json +46 -0
- labmcp_epics-0.1.0/src/labmcp_epics/__init__.py +1 -0
- labmcp_epics-0.1.0/src/labmcp_epics/driver.py +473 -0
- labmcp_epics-0.1.0/src/labmcp_epics/server.py +450 -0
- labmcp_epics-0.1.0/src/labmcp_epics/simulator.py +237 -0
- labmcp_epics-0.1.0/tests/test_server.py +217 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: labmcp-epics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for EPICS Channel Access (accelerators, synchrotrons, neutron sources, telescopes, physics labs): read, monitor and (guarded) write process variables via caproto.
|
|
5
|
+
Project-URL: Homepage, https://github.com/K-Dense-AI/lab-instrument-mcps/tree/main/servers/protocols/epics
|
|
6
|
+
Author: K-Dense and LabMCP contributors
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Keywords: accelerator,beamline,caproto,channel-access,control-system,epics,lab-instrument,mcp,synchrotron
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: caproto<2,>=1.1
|
|
11
|
+
Requires-Dist: labmcp<0.2,>=0.1
|
|
12
|
+
Requires-Dist: numpy>=1.24
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# EPICS Channel Access โ MCP Server
|
|
16
|
+
|
|
17
|
+
<!-- mcp-name: io.github.K-Dense-AI/labmcp-epics -->
|
|
18
|
+
|
|
19
|
+
Let an AI agent read, monitor and, when you allow it, carefully write **EPICS process variables (PVs)** over **Channel Access**. EPICS is the control system behind most particle accelerators, synchrotron and neutron beamlines, large telescopes, fusion experiments and many physics labs. The server uses [caproto](https://github.com/caproto/caproto), a pure-Python CA implementation, so there is nothing to compile and no `libca` to install.
|
|
20
|
+
|
|
21
|
+
> โ ๏ธ **Facility control systems are safety-critical.** Writing a PV can move motors and sample stages, open shutters, ramp magnets or high voltage, and change cryogenic or vacuum systems. **Follow your facility's rules. Get authorisation from the responsible scientist or control-room staff before enabling writes, and run with `--read-only` unless writes are explicitly authorised.** This server is not an interlock or a personnel-protection system and never replaces them. Where possible, connect through the facility's CA gateway with its access-security rules, and limit writes with `--option put_allowlist=`.
|
|
22
|
+
|
|
23
|
+
| | |
|
|
24
|
+
|---|---|
|
|
25
|
+
| **Package** | `labmcp-epics` |
|
|
26
|
+
| **Instruments** | Any EPICS IOC serving Channel Access: EPICS Base 3.14โ7.x soft/hard IOCs, areaDetector, motor, asyn, StreamDevice, caproto/pythonSoftIOC IOCs, CA gateways |
|
|
27
|
+
| **Interfaces** | Ethernet: CA search on UDP 5064, circuits on TCP 5064, repeater on UDP 5065 |
|
|
28
|
+
| **Protocol** | EPICS Channel Access v4.13 ([protocol specification](https://docs.epics-controls.org/en/latest/specs/ca_protocol.html), [ao record: DRVH/DRVL](https://docs.epics-controls.org/projects/base/en/latest/aoRecord.html), [caproto threading client](https://caproto.github.io/caproto/master/threading-client.html)) |
|
|
29
|
+
| **Status** | ๐งช **simulated**: tested against an in-process caproto IOC (real CA over localhost), not yet verified at a facility. [Report a hardware test](https://github.com/K-Dense-AI/lab-instrument-mcps/issues/new?template=hardware-verification.yml) |
|
|
30
|
+
|
|
31
|
+
## Try it without hardware
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uvx labmcp-epics --simulate --check
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The simulator is a real Channel Access server running in-process. It is bound to `127.0.0.1` on random free ports, and the client is pointed only at that address (auto address list off), so it neither broadcasts on nor answers your network. It serves `SIM:TEMP` (ยฐC, with alarm limits, following `SIM:TEMP:SP` while `SIM:HEATER` is On), `SIM:MTR` (a motor whose put-callback completes only when the move is done, with `SIM:MTR:RBV`, `:DMOV`, `:STOP` and `:VELO`), `SIM:DET:FRAMES`, `SIM:SAMPLE` (string), `SIM:SPECTRUM` (a 512-point waveform), `SIM:BEAM:CURRENT` and `SIM:STATUS`.
|
|
38
|
+
|
|
39
|
+
## Connect to your control system
|
|
40
|
+
|
|
41
|
+
1. **Network:** your computer must reach the IOCs, or the facility's CA gateway: UDP 5064/5065 and TCP 5064 through any firewall. Most facilities put control networks behind a gateway, so ask your controls group for its address.
|
|
42
|
+
2. **Address list:** Channel Access finds PVs by UDP search. Either use your usual EPICS environment (`EPICS_CA_ADDR_LIST`, `EPICS_CA_AUTO_ADDR_LIST`), or pass it explicitly:
|
|
43
|
+
```bash
|
|
44
|
+
uvx labmcp-epics --read-only --address 10.0.1.20 --option check_pv=SR:CURRENT --check # one IOC / gateway
|
|
45
|
+
uvx labmcp-epics --read-only --option ca_addr_list="gw1.facility.org 10.0.1.21:5064" --option check_pv=BL7:RING:I --check
|
|
46
|
+
uvx labmcp-epics --read-only --option check_pv=BL7:RING:I --check # use the environment as-is
|
|
47
|
+
```
|
|
48
|
+
Passing an address list switches the automatic (broadcast) list off unless you add `--option auto_addr_list=yes`. With `--option check_pv=<a PV you know exists>`, `--check` reads that PV and prints its value, or the reason it can't be reached. Without it, `--check` only confirms that the CA client starts.
|
|
49
|
+
|
|
50
|
+
## Add to your MCP client
|
|
51
|
+
|
|
52
|
+
**Claude Code** (read-only, recommended)
|
|
53
|
+
```bash
|
|
54
|
+
claude mcp add epics -- uvx labmcp-epics --read-only --address gw1.facility.org
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Claude Desktop / Cursor / Windsurf** (`claude_desktop_config.json`, `.cursor/mcp.json`, โฆ)
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"epics": {
|
|
62
|
+
"command": "uvx",
|
|
63
|
+
"args": ["labmcp-epics", "--read-only", "--address", "gw1.facility.org"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Authorised writes** (example: a beamline scientist lets the agent move only the sample stage and the filter wheel, and configures an emergency safe state):
|
|
70
|
+
```bash
|
|
71
|
+
uvx labmcp-epics --address 10.0.7.10 \
|
|
72
|
+
--option put_allowlist='BL7:(SAMPLE:(X|Y)|FILTER:POS)' \
|
|
73
|
+
--option require_ctrl_limits=true \
|
|
74
|
+
--option safe_state='BL7:SAMPLE:X.STOP=1;BL7:SAMPLE:Y.STOP=1;BL7:SHUTTER:CMD=Close'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Tools
|
|
78
|
+
|
|
79
|
+
<!-- TOOLS:START -->
|
|
80
|
+
| Tool | Kind | Description |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `apply_safe_state` | ๐ safety | Emergency action: write the scientist-configured safe-state PVs (--option safe_state, e.g. motor STOP fields, shutter close, HV off), all of them even if one fails. Available in read-only mode. It does not replace the facility's own interlocks and stop buttons. |
|
|
83
|
+
| `get_command_log` | ๐ read | Return the most recent raw commands sent to / replies received from the instrument (newest last). Useful for debugging and for recording what was done. |
|
|
84
|
+
| `get_connection_info` | ๐ read | Report which instrument is connected (identity, address, simulated or real), whether the server is read-only, and the active safety limits. Call this first. |
|
|
85
|
+
| `get_pv` | ๐ read | Read one PV with its metadata: value, units, precision, alarm severity/status, IOC timestamp and age, display/alarm/warning/control limits and enum state names. Waveforms come back downsampled with min/max/mean statistics. |
|
|
86
|
+
| `get_pvs` | ๐ read | Read many PVs at once (e.g. all motors or vacuum gauges of a beamline). PVs that cannot be reached are listed in `errors` instead of failing the whole call. |
|
|
87
|
+
| `monitor_pv` | ๐ read | Subscribe to a PV and collect every value change for `duration_s` seconds (or until `max_updates`), then return statistics (min/max/mean/std, drift rate) and the updates. |
|
|
88
|
+
| `put_pv` | โ ๏ธ hazard | Write a PV. This can move motors, open shutters, change magnet or high-voltage setpoints and heat or cool samples: tell the user exactly what will change first. The write is refused unless the PV matches the put allow-list, the IOC grants write access, and the value has the right type and lies within the PV's control limits (DRVL/DRVH). Returns the read-back value. |
|
|
89
|
+
| `put_pvs` | โ ๏ธ hazard | Write several PVs in order (e.g. set both slit blades). Every write is validated first (allow- list, access rights, type, control limits); if any is invalid nothing is written. Writing stops at the first failure and the rest are reported as not written. |
|
|
90
|
+
| `pv_info` | ๐ read | Connection details of a PV: serving IOC host:port, native type, element count, and whether this client has read/write access (EPICS access security) and passes the put allow-list. |
|
|
91
|
+
| `reconnect` | ๐ safety | Close and re-open the connection to the instrument (e.g. after it was power cycled or a cable was re-plugged). |
|
|
92
|
+
<!-- TOOLS:END -->
|
|
93
|
+
|
|
94
|
+
## Safety limits
|
|
95
|
+
|
|
96
|
+
| Limit | Default | Meaning |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `max_monitor_duration_s` | 60 s | Longest `monitor_pv` collection |
|
|
99
|
+
| `max_put_batch` | 10 PVs | Most PVs one `put_pvs` call may write |
|
|
100
|
+
| `max_put_wait_s` | 60 s | Longest wait for one put-callback (completion) |
|
|
101
|
+
|
|
102
|
+
Override at launch, e.g. `--limit max_put_batch=4`. The main write guards are options, not numeric limits:
|
|
103
|
+
|
|
104
|
+
| Guard | How |
|
|
105
|
+
|---|---|
|
|
106
|
+
| Read-only mode | `--read-only` hides `put_pv` and `put_pvs` entirely |
|
|
107
|
+
| Allow-list | `--option put_allowlist=<regex>`: a PV name must **fully** match it to be written |
|
|
108
|
+
| EPICS access security | writes are refused up front if the IOC or gateway grants this client read-only access |
|
|
109
|
+
| Control limits | numeric writes outside the PV's control limits are refused. For output records these are DRVL/DRVH, which the IOC would otherwise **silently clip** to |
|
|
110
|
+
| No limits configured | allowed with a warning, or refused with `--option require_ctrl_limits=true` |
|
|
111
|
+
| Type checks | enum states must be valid names or indices, integers must be integral and in range, strings are at most 39 characters, arrays must fit the element count |
|
|
112
|
+
| Batches | `put_pvs` validates every write before sending any, and stops at the first failure |
|
|
113
|
+
| Put-completion | `put_pv` waits for the IOC's put-callback. On timeout it says the IOC may still be processing and not to repeat blindly |
|
|
114
|
+
| Safe state | `apply_safe_state` (SAFETY, available even in read-only mode) writes only the PVs the scientist configured with `--option safe_state` |
|
|
115
|
+
|
|
116
|
+
## Example prompts
|
|
117
|
+
|
|
118
|
+
- "What is the storage-ring current, and are any of the BL7 vacuum gauges in alarm?"
|
|
119
|
+
- "Read BL7:MONO:ENERGY and its readback, and tell me the units and control limits."
|
|
120
|
+
- "Monitor the sample temperature for 30 s and tell me whether it is still drifting."
|
|
121
|
+
- "Move the sample stage X to 12.5 mm and confirm the readback when the move is done."
|
|
122
|
+
- "Record the detector spectrum waveform and tell me where the peak is."
|
|
123
|
+
- "Stop everything: apply the safe state."
|
|
124
|
+
|
|
125
|
+
## Notes
|
|
126
|
+
|
|
127
|
+
- **Setpoint versus readback:** for motor records, `NAME` (VAL) is the target and `NAME.RBV` is the actual position. `put_pv` returns the value read back from the PV you wrote, so read the readback PV separately to confirm physical motion.
|
|
128
|
+
- **Control limits** come from the CA `DBR_CTRL` response. EPICS convention treats equal low and high limits (normally 0/0) as "not configured", and those PVs are only type-checked.
|
|
129
|
+
- **Alarms:** every reading includes severity (`NO_ALARM`, `MINOR_ALARM`, `MAJOR_ALARM`, `INVALID_ALARM`), status (`HIHI`, `LOLO`, `UDF`, `COMM`, โฆ) and `age_s`, the time since the IOC timestamp, so stale values stand out.
|
|
130
|
+
- **Waveforms** are downsampled by block averaging to `max_elements`, with min/max/mean/argmax statistics. CHAR waveforms are also decoded to text (`as_string`), the usual EPICS long-string convention. When writing, a text value is accepted for CHAR waveforms.
|
|
131
|
+
- **pvAccess:** only Channel Access is supported. caproto 1.x implements CA only, and pvAccess would need [p4p](https://pypi.org/project/p4p/), a compiled EPICS Base binding that is deliberately not a dependency. EPICS 7 IOCs serve their records over both CA and PVA, so this covers normal records. PVA-only data (for example NTNDArray images from areaDetector's PVA plugin, or QSRV group PVs) can't be read.
|
|
132
|
+
- The CA client is configured through `EPICS_CA_*` environment variables, as in EPICS Base. The server sets them for its own process from `--address`/`--option` and restores them on disconnect. If `put_allowlist` holds a comma, pass it on the command line or give `LABMCP_OPTIONS` in its JSON form (`{"put_allowlist": "BL7:(A|B){1,2}"}`), because the `a=1,b=2` form splits on commas.
|
|
133
|
+
- `get_command_log` records every write (`caput -c NAME value`) and its read-back.
|
|
134
|
+
|
|
135
|
+
## Hardware verification
|
|
136
|
+
|
|
137
|
+
| Facility / IOC | EPICS version | Interface | Verified by | Date |
|
|
138
|
+
|---|---|---|---|---|
|
|
139
|
+
| *none yet: [be the first](https://github.com/K-Dense-AI/lab-instrument-mcps/issues/new?template=hardware-verification.yml)* | | | | |
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# EPICS Channel Access โ MCP Server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.K-Dense-AI/labmcp-epics -->
|
|
4
|
+
|
|
5
|
+
Let an AI agent read, monitor and, when you allow it, carefully write **EPICS process variables (PVs)** over **Channel Access**. EPICS is the control system behind most particle accelerators, synchrotron and neutron beamlines, large telescopes, fusion experiments and many physics labs. The server uses [caproto](https://github.com/caproto/caproto), a pure-Python CA implementation, so there is nothing to compile and no `libca` to install.
|
|
6
|
+
|
|
7
|
+
> โ ๏ธ **Facility control systems are safety-critical.** Writing a PV can move motors and sample stages, open shutters, ramp magnets or high voltage, and change cryogenic or vacuum systems. **Follow your facility's rules. Get authorisation from the responsible scientist or control-room staff before enabling writes, and run with `--read-only` unless writes are explicitly authorised.** This server is not an interlock or a personnel-protection system and never replaces them. Where possible, connect through the facility's CA gateway with its access-security rules, and limit writes with `--option put_allowlist=`.
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Package** | `labmcp-epics` |
|
|
12
|
+
| **Instruments** | Any EPICS IOC serving Channel Access: EPICS Base 3.14โ7.x soft/hard IOCs, areaDetector, motor, asyn, StreamDevice, caproto/pythonSoftIOC IOCs, CA gateways |
|
|
13
|
+
| **Interfaces** | Ethernet: CA search on UDP 5064, circuits on TCP 5064, repeater on UDP 5065 |
|
|
14
|
+
| **Protocol** | EPICS Channel Access v4.13 ([protocol specification](https://docs.epics-controls.org/en/latest/specs/ca_protocol.html), [ao record: DRVH/DRVL](https://docs.epics-controls.org/projects/base/en/latest/aoRecord.html), [caproto threading client](https://caproto.github.io/caproto/master/threading-client.html)) |
|
|
15
|
+
| **Status** | ๐งช **simulated**: tested against an in-process caproto IOC (real CA over localhost), not yet verified at a facility. [Report a hardware test](https://github.com/K-Dense-AI/lab-instrument-mcps/issues/new?template=hardware-verification.yml) |
|
|
16
|
+
|
|
17
|
+
## Try it without hardware
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uvx labmcp-epics --simulate --check
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The simulator is a real Channel Access server running in-process. It is bound to `127.0.0.1` on random free ports, and the client is pointed only at that address (auto address list off), so it neither broadcasts on nor answers your network. It serves `SIM:TEMP` (ยฐC, with alarm limits, following `SIM:TEMP:SP` while `SIM:HEATER` is On), `SIM:MTR` (a motor whose put-callback completes only when the move is done, with `SIM:MTR:RBV`, `:DMOV`, `:STOP` and `:VELO`), `SIM:DET:FRAMES`, `SIM:SAMPLE` (string), `SIM:SPECTRUM` (a 512-point waveform), `SIM:BEAM:CURRENT` and `SIM:STATUS`.
|
|
24
|
+
|
|
25
|
+
## Connect to your control system
|
|
26
|
+
|
|
27
|
+
1. **Network:** your computer must reach the IOCs, or the facility's CA gateway: UDP 5064/5065 and TCP 5064 through any firewall. Most facilities put control networks behind a gateway, so ask your controls group for its address.
|
|
28
|
+
2. **Address list:** Channel Access finds PVs by UDP search. Either use your usual EPICS environment (`EPICS_CA_ADDR_LIST`, `EPICS_CA_AUTO_ADDR_LIST`), or pass it explicitly:
|
|
29
|
+
```bash
|
|
30
|
+
uvx labmcp-epics --read-only --address 10.0.1.20 --option check_pv=SR:CURRENT --check # one IOC / gateway
|
|
31
|
+
uvx labmcp-epics --read-only --option ca_addr_list="gw1.facility.org 10.0.1.21:5064" --option check_pv=BL7:RING:I --check
|
|
32
|
+
uvx labmcp-epics --read-only --option check_pv=BL7:RING:I --check # use the environment as-is
|
|
33
|
+
```
|
|
34
|
+
Passing an address list switches the automatic (broadcast) list off unless you add `--option auto_addr_list=yes`. With `--option check_pv=<a PV you know exists>`, `--check` reads that PV and prints its value, or the reason it can't be reached. Without it, `--check` only confirms that the CA client starts.
|
|
35
|
+
|
|
36
|
+
## Add to your MCP client
|
|
37
|
+
|
|
38
|
+
**Claude Code** (read-only, recommended)
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add epics -- uvx labmcp-epics --read-only --address gw1.facility.org
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Claude Desktop / Cursor / Windsurf** (`claude_desktop_config.json`, `.cursor/mcp.json`, โฆ)
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"epics": {
|
|
48
|
+
"command": "uvx",
|
|
49
|
+
"args": ["labmcp-epics", "--read-only", "--address", "gw1.facility.org"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Authorised writes** (example: a beamline scientist lets the agent move only the sample stage and the filter wheel, and configures an emergency safe state):
|
|
56
|
+
```bash
|
|
57
|
+
uvx labmcp-epics --address 10.0.7.10 \
|
|
58
|
+
--option put_allowlist='BL7:(SAMPLE:(X|Y)|FILTER:POS)' \
|
|
59
|
+
--option require_ctrl_limits=true \
|
|
60
|
+
--option safe_state='BL7:SAMPLE:X.STOP=1;BL7:SAMPLE:Y.STOP=1;BL7:SHUTTER:CMD=Close'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
<!-- TOOLS:START -->
|
|
66
|
+
| Tool | Kind | Description |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `apply_safe_state` | ๐ safety | Emergency action: write the scientist-configured safe-state PVs (--option safe_state, e.g. motor STOP fields, shutter close, HV off), all of them even if one fails. Available in read-only mode. It does not replace the facility's own interlocks and stop buttons. |
|
|
69
|
+
| `get_command_log` | ๐ read | Return the most recent raw commands sent to / replies received from the instrument (newest last). Useful for debugging and for recording what was done. |
|
|
70
|
+
| `get_connection_info` | ๐ read | Report which instrument is connected (identity, address, simulated or real), whether the server is read-only, and the active safety limits. Call this first. |
|
|
71
|
+
| `get_pv` | ๐ read | Read one PV with its metadata: value, units, precision, alarm severity/status, IOC timestamp and age, display/alarm/warning/control limits and enum state names. Waveforms come back downsampled with min/max/mean statistics. |
|
|
72
|
+
| `get_pvs` | ๐ read | Read many PVs at once (e.g. all motors or vacuum gauges of a beamline). PVs that cannot be reached are listed in `errors` instead of failing the whole call. |
|
|
73
|
+
| `monitor_pv` | ๐ read | Subscribe to a PV and collect every value change for `duration_s` seconds (or until `max_updates`), then return statistics (min/max/mean/std, drift rate) and the updates. |
|
|
74
|
+
| `put_pv` | โ ๏ธ hazard | Write a PV. This can move motors, open shutters, change magnet or high-voltage setpoints and heat or cool samples: tell the user exactly what will change first. The write is refused unless the PV matches the put allow-list, the IOC grants write access, and the value has the right type and lies within the PV's control limits (DRVL/DRVH). Returns the read-back value. |
|
|
75
|
+
| `put_pvs` | โ ๏ธ hazard | Write several PVs in order (e.g. set both slit blades). Every write is validated first (allow- list, access rights, type, control limits); if any is invalid nothing is written. Writing stops at the first failure and the rest are reported as not written. |
|
|
76
|
+
| `pv_info` | ๐ read | Connection details of a PV: serving IOC host:port, native type, element count, and whether this client has read/write access (EPICS access security) and passes the put allow-list. |
|
|
77
|
+
| `reconnect` | ๐ safety | Close and re-open the connection to the instrument (e.g. after it was power cycled or a cable was re-plugged). |
|
|
78
|
+
<!-- TOOLS:END -->
|
|
79
|
+
|
|
80
|
+
## Safety limits
|
|
81
|
+
|
|
82
|
+
| Limit | Default | Meaning |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `max_monitor_duration_s` | 60 s | Longest `monitor_pv` collection |
|
|
85
|
+
| `max_put_batch` | 10 PVs | Most PVs one `put_pvs` call may write |
|
|
86
|
+
| `max_put_wait_s` | 60 s | Longest wait for one put-callback (completion) |
|
|
87
|
+
|
|
88
|
+
Override at launch, e.g. `--limit max_put_batch=4`. The main write guards are options, not numeric limits:
|
|
89
|
+
|
|
90
|
+
| Guard | How |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Read-only mode | `--read-only` hides `put_pv` and `put_pvs` entirely |
|
|
93
|
+
| Allow-list | `--option put_allowlist=<regex>`: a PV name must **fully** match it to be written |
|
|
94
|
+
| EPICS access security | writes are refused up front if the IOC or gateway grants this client read-only access |
|
|
95
|
+
| Control limits | numeric writes outside the PV's control limits are refused. For output records these are DRVL/DRVH, which the IOC would otherwise **silently clip** to |
|
|
96
|
+
| No limits configured | allowed with a warning, or refused with `--option require_ctrl_limits=true` |
|
|
97
|
+
| Type checks | enum states must be valid names or indices, integers must be integral and in range, strings are at most 39 characters, arrays must fit the element count |
|
|
98
|
+
| Batches | `put_pvs` validates every write before sending any, and stops at the first failure |
|
|
99
|
+
| Put-completion | `put_pv` waits for the IOC's put-callback. On timeout it says the IOC may still be processing and not to repeat blindly |
|
|
100
|
+
| Safe state | `apply_safe_state` (SAFETY, available even in read-only mode) writes only the PVs the scientist configured with `--option safe_state` |
|
|
101
|
+
|
|
102
|
+
## Example prompts
|
|
103
|
+
|
|
104
|
+
- "What is the storage-ring current, and are any of the BL7 vacuum gauges in alarm?"
|
|
105
|
+
- "Read BL7:MONO:ENERGY and its readback, and tell me the units and control limits."
|
|
106
|
+
- "Monitor the sample temperature for 30 s and tell me whether it is still drifting."
|
|
107
|
+
- "Move the sample stage X to 12.5 mm and confirm the readback when the move is done."
|
|
108
|
+
- "Record the detector spectrum waveform and tell me where the peak is."
|
|
109
|
+
- "Stop everything: apply the safe state."
|
|
110
|
+
|
|
111
|
+
## Notes
|
|
112
|
+
|
|
113
|
+
- **Setpoint versus readback:** for motor records, `NAME` (VAL) is the target and `NAME.RBV` is the actual position. `put_pv` returns the value read back from the PV you wrote, so read the readback PV separately to confirm physical motion.
|
|
114
|
+
- **Control limits** come from the CA `DBR_CTRL` response. EPICS convention treats equal low and high limits (normally 0/0) as "not configured", and those PVs are only type-checked.
|
|
115
|
+
- **Alarms:** every reading includes severity (`NO_ALARM`, `MINOR_ALARM`, `MAJOR_ALARM`, `INVALID_ALARM`), status (`HIHI`, `LOLO`, `UDF`, `COMM`, โฆ) and `age_s`, the time since the IOC timestamp, so stale values stand out.
|
|
116
|
+
- **Waveforms** are downsampled by block averaging to `max_elements`, with min/max/mean/argmax statistics. CHAR waveforms are also decoded to text (`as_string`), the usual EPICS long-string convention. When writing, a text value is accepted for CHAR waveforms.
|
|
117
|
+
- **pvAccess:** only Channel Access is supported. caproto 1.x implements CA only, and pvAccess would need [p4p](https://pypi.org/project/p4p/), a compiled EPICS Base binding that is deliberately not a dependency. EPICS 7 IOCs serve their records over both CA and PVA, so this covers normal records. PVA-only data (for example NTNDArray images from areaDetector's PVA plugin, or QSRV group PVs) can't be read.
|
|
118
|
+
- The CA client is configured through `EPICS_CA_*` environment variables, as in EPICS Base. The server sets them for its own process from `--address`/`--option` and restores them on disconnect. If `put_allowlist` holds a comma, pass it on the command line or give `LABMCP_OPTIONS` in its JSON form (`{"put_allowlist": "BL7:(A|B){1,2}"}`), because the `a=1,b=2` form splits on commas.
|
|
119
|
+
- `get_command_log` records every write (`caput -c NAME value`) and its read-back.
|
|
120
|
+
|
|
121
|
+
## Hardware verification
|
|
122
|
+
|
|
123
|
+
| Facility / IOC | EPICS version | Interface | Verified by | Date |
|
|
124
|
+
|---|---|---|---|---|
|
|
125
|
+
| *none yet: [be the first](https://github.com/K-Dense-AI/lab-instrument-mcps/issues/new?template=hardware-verification.yml)* | | | | |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "labmcp-epics"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for EPICS Channel Access (accelerators, synchrotrons, neutron sources, telescopes, physics labs): read, monitor and (guarded) write process variables via caproto."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [{ name = "K-Dense and LabMCP contributors" }]
|
|
9
|
+
keywords = ["mcp", "lab-instrument", "epics", "channel-access", "caproto", "synchrotron", "accelerator", "beamline", "control-system"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"labmcp>=0.1,<0.2",
|
|
12
|
+
"caproto>=1.1,<2",
|
|
13
|
+
"numpy>=1.24",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
labmcp-epics = "labmcp_epics.server:main"
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/K-Dense-AI/lab-instrument-mcps/tree/main/servers/protocols/epics"
|
|
21
|
+
|
|
22
|
+
[tool.labmcp]
|
|
23
|
+
name = "EPICS Channel Access"
|
|
24
|
+
domain = "protocols"
|
|
25
|
+
category = "Control systems"
|
|
26
|
+
vendor = "EPICS collaboration (any EPICS IOC)"
|
|
27
|
+
models = [
|
|
28
|
+
"Any EPICS IOC serving Channel Access (EPICS Base 3.14-7.x, areaDetector, motor, asyn, PyDevice, caproto IOCs)",
|
|
29
|
+
"CA gateways",
|
|
30
|
+
]
|
|
31
|
+
interfaces = ["Ethernet (CA over UDP/TCP 5064/5065)"]
|
|
32
|
+
protocol = "EPICS Channel Access v4.13 (caproto)"
|
|
33
|
+
summary = "Read PVs with units, precision, alarms, timestamps and limits; monitor PVs for a bounded time; guarded writes (allow-list regex, access rights, DRVH/DRVL control limits, put-callback completion); configurable safe-state action."
|
|
34
|
+
status = "simulated"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/labmcp_epics"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.K-Dense-AI/labmcp-epics",
|
|
4
|
+
"title": "EPICS Channel Access",
|
|
5
|
+
"description": "MCP server for EPICS Channel Access (accelerators, synchrotrons, neutron sources, telescopes, physic",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/K-Dense-AI/lab-instrument-mcps",
|
|
9
|
+
"source": "github",
|
|
10
|
+
"subfolder": "servers/protocols/epics"
|
|
11
|
+
},
|
|
12
|
+
"websiteUrl": "https://github.com/K-Dense-AI/lab-instrument-mcps/tree/main/servers/protocols/epics",
|
|
13
|
+
"packages": [
|
|
14
|
+
{
|
|
15
|
+
"registryType": "pypi",
|
|
16
|
+
"registryBaseUrl": "https://pypi.org",
|
|
17
|
+
"identifier": "labmcp-epics",
|
|
18
|
+
"version": "0.1.0",
|
|
19
|
+
"transport": {
|
|
20
|
+
"type": "stdio"
|
|
21
|
+
},
|
|
22
|
+
"environmentVariables": [
|
|
23
|
+
{
|
|
24
|
+
"name": "LABMCP_ADDRESS",
|
|
25
|
+
"description": "Instrument address, e.g. serial:///dev/ttyUSB0 or tcp://192.168.1.50:5025",
|
|
26
|
+
"isRequired": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "LABMCP_SIMULATE",
|
|
30
|
+
"description": "Set to 1 to use the built-in simulator (no hardware)",
|
|
31
|
+
"isRequired": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "LABMCP_READ_ONLY",
|
|
35
|
+
"description": "Set to 1 to disable all state-changing tools",
|
|
36
|
+
"isRequired": false
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "LABMCP_LIMITS",
|
|
40
|
+
"description": "Safety limit overrides, e.g. max_temperature_c=80",
|
|
41
|
+
"isRequired": false
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LabMCP server for EPICS Channel Access process variables."""
|