Atlas / MCP servers / epicdirk / EPICS-MCP

EPICS-MCPBLOCK

mcp/epicdirk/epics-mcp

Ask an AI assistant about your EPICS control system: live PV values, why a PV is disconnected, and where it appears across ChannelFinder, the Archiver and the alarm tree. Read-only by default.

Verdict
BLOCK
Grade
D
Trust score
69 /100
Exposed tools
3 3r · 0w · 0d
Transport
streamable-http
License
MIT
Stars
5
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

[](https://github.com/epicDirk/EPICS-MCP/actions/workflows/ci.yml) [](https://pypi.org/project/epics-mcp/) [](https://github.com/epicDirk/EPICS-MCP/blob/main/LICENSE) [](https://www.python.org/)

Ask an AI assistant about your EPICS control system, whether you are on shift or writing code against it. Why is this PV disconnected? Which IOC was supposed to serve it? When did the archive last see a value, and did anybody log a problem with that device this week?

That is one question, and its answer sits in four different services. A facility runs its live layer, a channel registry, an archiver, an alarm logger and a logbook side by side. Each has its own port, its own query language, its own idea of how a date is written, and often its own login. This server puts them behind one set of Model Context Protocol (MCP) tools, live values over p4p and the other planes over their own REST APIs. The question is asked once instead of four times, and each service you have configured answers the part it holds.

Read-only by default. The one tool that can write to your control system is triple-gated, and off until you turn it on. Logbook writes sit behind a separate gate of their own. The full posture, including what leaves your machine, is Safety and network posture.

It also answers what no single service can. Which of the channels this IOC serves is nobody archiving? The registry holds one list, the archive holds another, and nobody holds the difference. An assistant asks for both and works it out, with no script to write first.

Project status. Pre-1.0 and under active development. To
Read from source at commit a61fe91489d3OBSERVED · 2026-10-11
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add epics-mcp --env EPICS_MCP_OLOG_TEST_OTHER_PASSWORD=${EPICS_MCP_OLOG_TEST_OTHER_PASSWORD} --env EPICS_MCP_OLOG_WRITE_PASSWORD=${EPICS_MCP_OLOG_WRITE_PASSWORD} --env OA1C_WRITE_PASSWORD=${OA1C_WRITE_PASSWORD} -- uvx epics-mcp
claude-desktop
{
  "mcpServers": {
    "epics-mcp": {
      "command": "uvx",
      "args": [
        "epics-mcp"
      ],
      "env": {
        "EPICS_MCP_OLOG_TEST_OTHER_PASSWORD": "${EPICS_MCP_OLOG_TEST_OTHER_PASSWORD}",
        "EPICS_MCP_OLOG_WRITE_PASSWORD": "${EPICS_MCP_OLOG_WRITE_PASSWORD}",
        "OA1C_WRITE_PASSWORD": "${OA1C_WRITE_PASSWORD}"
      }
    }
  }
}
03

Exposed tools (3)

3 read · 0 write · 0 destructive.

ToolRiskDescription
namedread
seekread
untypedreadReturn nothing typed.
04

Trust audit

BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (2 observation(s))
Network
declared (4 observation(s))
Shell
declared (1 observation(s))
Dependencies
pinned
Secrets in source
none-found

Findings (25)

HIGHNetwork egress · net.tls_off · CWE-200, CWE-319
src/epics_mcp/prompts.py:188
"   - EPICS_MCP_TLS_VERIFY=false as the answer to question 7. The answer to an internal CA "
Why it matters. certificate verification is disabled
Fix. leave verification on
MEDIUMCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
src/epics_mcp/cli_common.py:141
importlib.import_module(_ENGINE_ENTRY_POINT)
MEDIUMNetwork egress · net.beacon_words · CWE-200, CWE-319
src/epics_mcp/services/naming_client.py:214
f"swagger beacon (probe: {self._identity}); a 'not registered' answer is withheld "
LOWInventory / provenance · inv.hidden_file · CWE-1104
.pre-commit-config.yaml
.pre-commit-config.yaml
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
scripts/.language-exceptions
.language-exceptions
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
scripts/.typography-exceptions
.typography-exceptions
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_cli_version.py:71
module = importlib.import_module(module_name)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_cli_without_display_engine.py:341
module = importlib.import_module(module_name)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_cli_without_display_engine.py:371
module = importlib.import_module(module_name)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_cli_without_display_engine.py:399
module = importlib.import_module(module_name)
LOWNetwork egress · net.beacon_words · CWE-200, CWE-319
tests/test_checkers.py:565
("identity", "swagger beacon"),
LOWNetwork egress · net.beacon_words · CWE-200, CWE-319
tests/test_doctor.py:985
get_read_throttle().check()  # spend the only token: the beacon below is refused
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
docs/write-gate-contract.md:170
parser and never a substring of the authority (`http://127.0.0.1@logbook-remote/...` has host
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_audit_record.py:129
olog_url="http://127.0.0.1:8080/Olog",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_cli_init.py:1019
os.environ["HTTP_PROXY"] = "http://127.0.0.1:9"
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_cli_init.py:1058
os.environ["HTTP_PROXY"] = "http://127.0.0.1:9"
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
tests/test_cli_init.py:1086
os.environ["HTTP_PROXY"] = "http://127.0.0.1:9"
LOWNetwork egress · net.tls_off · CWE-200, CWE-319
tests/test_cli_init.py:1223
cli_init.main([*_REST_ARGV, "--set", "EPICS_MCP_TLS_VERIFY=false"])
Why it matters. certificate verification is disabled
Fix. leave verification on
LOWNetwork egress · net.tls_off · CWE-200, CWE-319
tests/test_http.py:157
session = build_retrying_session(verify=False)
Why it matters. certificate verification is disabled
Fix. leave verification on
LOWNetwork egress · net.tls_off · CWE-200, CWE-319
tests/test_http.py:178
lambda: EpicsConfig(ca_bundle="", tls_verify=False),
Why it matters. certificate verification is disabled
Fix. leave verification on
LOWNetwork egress · net.tls_off · CWE-200, CWE-319
tests/test_resources.py:408
_with_config(monkeypatch, tls_verify=False)
Why it matters. certificate verification is disabled
Fix. leave verification on
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
src/epics_mcp/services/olog_attachments.py:198
return base64.b64decode(value, validate=True)
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
tests/test_olog_attachments_live.py:73
_PNG = base64.b64decode(
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
tests/test_olog_remote_https_live.py:129
_PNG = base64.b64decode(
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/test_safety.py:659
probe = "SIM:PS-01:Cur-RB 12 μA 50 Ω probe-äöü"

Gates applied: no_behavioural_pass.

Audited 2026-10-11 · audit v0.4.1 · source sha a61fe91489d3full audit observations/trust-audit/mcp-server/epicdirk__epics-mcp.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-11a61fe91489d3BLOCKD69first audit
06

Questions

What is the EPICS-MCP MCP server?

Ask an AI assistant about your EPICS control system: live PV values, why a PV is disconnected, and where it appears across ChannelFinder, the Archiver and the alarm tree. Read-only by default.

What tools does EPICS-MCP expose?

3 in total: 3 read-only, 0 that write, and 0 that can delete or overwrite. Every one is listed on this page with its risk.

Is EPICS-MCP safe to connect to an agent?

No — not without reading the findings first. The audit graded it D (69/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on.

What credentials does EPICS-MCP need?

It reads EPICS_MCP_OLOG_TEST_OTHER_PASSWORD, EPICS_MCP_OLOG_WRITE_PASSWORD and OA1C_WRITE_PASSWORD from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does EPICS-MCP run?

It speaks streamable-http, so it runs as a service you connect to over the network. It is published on PyPI as epics-mcp.

How current is this page?

The grade is for one exact copy of the source (a61fe91489d3), read on 2026-10-11. The repository is watched and re-audited when it changes.

Advertisement