v0.4.0
Added
-
Every
epics-doctorline that reports a problem now names its remedy. Three statuses already
did (ca_errornamed the CA bundle variable,api_errorthe mgmt/retrieval mix-up,config_error
the variable to set) and four did not:unreachableprinted only the transport error,
backend_downnamed the consequence rather than the fix, anddisconnectedand
identity_probe_failedsaid nothing about what to do. The remedies now live in one table keyed by
status, appended to the observation and never replacing it, so--jsonreaders get them in the same
detailfield. Anunreachableplane also names the variable it reads its URL from, which is the
one case where the reader cannot tell from the message which variable to look at.unverified
andno_ingestdeliberately stay without one: the first already carries the specific clue it
measured, and the second is a fault inside the appliance, not in this configuration. -
--versionon all five console commands (epics-mcphad it;epics-doctor,
epics-diagnose,epics-coverageandepics-crossplanedid not), from one shared helper, so the
version source and the command-name prefix cannot drift apart. Each prints its own name and the
package version. Note: on the two display-aware commands the engine check still runs before the
arguments are parsed, so on a core-only install those two report the missing engine instead; that
limit is pinned by a test rather than left to be rediscovered. -
epics-doctornotices an Archiver appliance that is not ingesting.getApplianceInfoproves
that an appliance is answering and nothing more: one whose engine has never spoken to its IOCs
answers it exactly like a healthy one, so the doctor printedokfor a deployment that was
archiving nothing. That is a wiring fault, the class this tool exists to catch. The identified
appliance is now also askedgetApplianceMetrics, and two things make it a finding: channels held
with none connected, or the appliance's ownstatusreporting a stopped webapp. The second is
the worse fault and is invisible in the counts, because when the engine webapp does not reply,
mgmt cannot merge its numbers and they vanish from the row whilepvCountsurvives, with the
payload still served as HTTP 200. -
New plane status
no_ingest(~), exit0. Deliberately not a failure: a freshly
commissioned or fully paused appliance is legitimately in this state, and failing the run would
makeepics-doctorcry wolf in every CI job that calls it. It is not silent either, and that is
what the next entry is for. -
New
--jsonfielddegraded_planes. The planes that proved their identity and are measurably
not doing their job. It exists because every other signal stays clean for such a plane:okand
verification_completeremaintrue,unverified_planesandinconclusive_identity_planes
remain empty, andidentified_planeseven lists it, since its identity IS proven. A script
written against the documented field list would otherwise read a non-archiving archiver as
positively confirmed. Purely additive; no existing field changes meaning and no exit code moves.
Changed
- The archiver plane now issues three requests on a healthy run rather than two, and the ingest
probe uses a single attempt with a 15 s floor instead of the shared retrying session. urllib3
applies the timeout per attempt, and this route fans out to three internal requests per cluster
member: measured against a 16-member cluster it answers in 7.3 s but takes 23.3 s to fail under
the default 3-retry policy, which would have made the check blind there while slowing every run. - The archiver identity probe goes through the same shared beacon fetcher as every other plane. It
was the one plane building its request inline, which already made that helper's "the one place
every identity probe issues its request" contract untrue. epics-doctor'sconfig_errorline now names the variable to set at its START, and the
remedy points at that position. Measured against the last published release: 0.3.0 ended the
observation with "Set EPICS_MCP_ARCHIVER_URL (the MGMT webapp URL)", the remedy table then took
that instruction over, and it now reads "Set the variable named at the start of this finding",
the constructionunreachablealready used. This matters beyond wording becausedetailis the
field--jsonreaders are told to use, so a consumer matching the older text will not find it.
The position is guarded; the wording deliberately is not.- BREAKING: the import package is now
epics_mcp(wasepics_pv_mcp).import epics_pv_mcp
stops working;import epics_mcpreplaces it, one for one, with no other change to the API. This
completes the rename begun in 0.3.0, where the distribution, the repository, the server command
and the server's MCP identity already becameepics-mcpand only the import package did not. It
is done now rather than later because every release under the old import name grows the set of
installations a rename breaks. - BREAKING: the
epics-pv-mcpconsole command is removed. Useepics-mcp, which has been the
primary command since 0.3.0. The alias was added in 0.3.0 for anyone following pre-rename docs,
and 0.3.0 shipped it: every installation of that release carriesepics-pv-mcp, so upgrading to
0.4.0 removes a command that works today. Check your wrapper scripts and MCP client configs for
it. Nothing older is affected, because 0.3.0 was this project's first published release of any
kind. The four diagnostic commands (epics-doctor,epics-diagnose,epics-crossplane,
epics-coverage) are unaffected. - BREAKING: the
allextra is removed.pip install epics-mcp[all]no longer resolves; use
epics-mcp[dev], which is whatallcontained. It was exactlyepics-mcp[dev], so it promised a
reader everything the package can do and delivered the developer toolchain, and the display-aware
tools it seemed to imply are not an extra at all but a local dependency group.devis now the
only extra. Nothing else changes: no dependency is added or dropped by this. - The product is called EPICS MCP. The prose name was still "EPICS PV MCP Server" in 19 places,
including the README H1 (which is the project page title on the index), theCITATION.cfftitle
that published work cites, and the first line of the operator guide that ships in the wheel. The
rename toepics-mcpin 0.3.0 was made because the PV plane is one of six, so a title saying PV
contradicted its own reason. Nothing user-facing behaves differently; the distribution, the
commands, the import package and the MCP identity are unchanged. epics-doctorexits 1, not 2, on an internal error. 2 is the usage-error code across these
commands and in argparse, so the old value told a wrapper the caller had passed something wrong
when the command itself had failed. A genuine usage error (an unknown flag) still exits 2.
Note the cost: exit 1 is also "a configured plane hard failed", so the exit code alone no longer
separates the two. They differ on the streams: an internal error writes adoctor:line to
stderr and no report to stdout.
Fixed
- The display-aware CLIs tell a broken engine apart from a missing one.
epics-crossplaneand
epics-coverageprobed for theopi_navigationengine with a name lookup only, so an engine
that was installed but did not import (typically a missing transitive dependency) passed the
check and the command then died with a bare traceback. It now reports the failure with the
underlying exception named and exits 1, while a genuinely absent engine keeps its explanation and
exit 2. The advice differs too: installing the engine will not fix a broken one. - The core server no longer dies over an OPTIONAL capability. The display-capability probe called
importlib.util.find_specbare, and it runs at module level, so an import hook that RAISES for
opi_navigationtook the wholeepics_mcp.serverimport down: exit 1 with a traceback, on a
server whose PV tools never needed the display engine at all. The probe now answers instead of
propagating. A module a finder reports as genuinely absent is the supported core-only state and
stays silent, as before; a finder that could not answer is a different claim and is logged loud
rather than wearing the "not installed" message. The sibling CLIs already behaved this way, so one
package was answering the same question two ways. - A long live value in the
find_devicereport is capped at 80 characters, not 82, as its own
documentation promised. - The ready-to-paste write-enabled MCP client block now starts. As shipped it omitted
EPICS_MCP_AUDIT_LOG_FILEand the loopback reach settings, both of which a write-enabled server
refuses to start without, so a reader who pasted it saw only "server not connected". - The setup instructions no longer describe a
.envfile. Four places told adopters to copy
.env.exampleto.env, or to runepics-doctorto confirm one. Nothing in the server ever
loads a dotenv file; configuration is read from the process environment, and a variable that does
not reach the process is dropped silently..env.exampleis a reference to copy lines OUT of. EPICS_MCP_ARCHIVER_RETRIEVAL_URLis spelled with its prefix in the README. The unprefixed
form does not bind, so a split-appliance operator who followed it got history requests sent to
the mgmt port with no error to explain it..env.examplenames all four tools the Olog write gate covers, not two. It was the only
place that understated the gate's reach.- Every link on the PyPI project page now goes somewhere. The README is the
long_description,
and on that page there is no repository around it, so its relative targets resolved against
pypi.org and landed nowhere; measured on the rendered page, all of them. They are absolute GitHub
URLs now, pinned tomainso the page always points at current documentation. Anchors were
already fine there (the index rewrites them) and are untouched. The link guard follows the new
spelling rather than skipping it as a URL, so a renamed page still fails the build. - The Naming-Service modules describe their own origin correctly, and no longer ship a developer
path. Both called themselves "vendored" from pvValidator, which is GPL-3.0-only while this
package is MIT, so the wording asserted a licence problem that does not exist: measured with
difflibover the whole files, the longest identical run of lines is four (two of them non-blank,
both imports), and the shared remainder is signatures the REST endpoint dictates. They now say
they follow that client's API shape and carry none of its code, the measurement is recorded in
the known limits, and attribution sits in the README credits. One of the two also carried an
absolute path from the author's machine into the published wheel; that string is gone, and the
repo-wide facility guard now rejects a local drive path so it cannot come back. - The operator guide no longer promises an
op=correlation the refusals cannot honour, and its
Olog separator table is right about+. The write-posture section said every stage of a
set_pv_valuewrite isop-correlated; the id is issued when a write is dispatched, so the two
pre-dispatch refusals (DENY,BOUNDS_DENY) carry none, and a reader correlating an audit trail
byopwould have looked for a token that is never written. The filter table listed a
separators-only value as dropped forlevelas well, while the same page explains ten lines lower
that+splits onlytitleand is an ordinary level. The guide ships in the wheel and is served
as theepics-pv://guideresource, so both were wrong at the point of use. - The start conditions of the PV write gate are now listed completely wherever they are listed at
all. The tool description, the server instructions, the configuration reference and the
deployment guide each enumerated what a write-enabled server needs and left out the loopback-only
search reach, while other places state it. An enumeration that stops one item short reads as
complete, so an operator learned that the network posture and the write gate were independent
settings, when in fact writes on plus a reach beyond loopback is a start-time refusal. The
configuration reference also gained theEPICS_CA_NAME_SERVERSrow: it takes part in that check
but was missing from the EPICS network table. - The bug report template no longer calls this an unqualified read-only server. It can write,
behind gates; the reason not to paste credentials is that each REST plane takes an
EPICS_MCP_*_AUTHvalue and an error string can carry the request URL. epics-mcp --helpprints help, and--versionprints the version.epics-mcpaccepted no
options at all, so--helpreached the stdio transport instead of a parser: the command appeared
to succeed, printed nothing and exited 0, because a server started on a closed stdin ends at once.
An unknown option is now a usage error (exit 2).--versionanswers the question the bug report
template asks, which no command line could answer before. The usage line of all five commands is
also pinned to the command's own name; on Python 3.14 argparse derived it from the interpreter and
printed the absolute path of the installed script instead.
Internal
- The two prose guards no longer leak their exemptions: the language guard's self-exemption is keyed
on file paths rather than bare file names, and both guards anchor an exception's path key on a
path-component boundary, so a key written forscripts/x.pyno longer also frees
myscripts/x.py. - The GitHub Actions workflows moved off the deprecated Node 20 runtime (
setup-uv,
upload-artifact,download-artifact).