Repository navigation
v2.2.1
Changed
- SemVer comparison now uses the
semverlibrary instead of a hand-written
key (new dependency, pure-Python, no transitive dependencies). The npm and
Cargo lane needs true SemVer 2.0.0 precedence because PEP 440 disagrees with
it — PEP 440 reads1.0.0-1as the post-release1.0.0.post1, SemVer as a
prerelease below1.0.0— and 79 of the manifest's 107 servers are npm.
This module replaced hand-rolled version logic withpackagingprecisely
because every hand-rolled form of it produced a fabricated-notice bug; the
SemVer lane was the last piece still hand-written.
Fixed
-
A stale descriptions cache is no longer pinned when the FETCHED version is
unreadable. The "already up to date" short-circuit negates a comparator
that fails closed, so an orderability guard was added to stop an unreadable
version reading as current — but it checked only the cached side. With a
cached1.0.0and a fetchednightly, the guard passed, the comparison
failed closed, and the negation still reported "up to date", never
refreshing.Checking each side individually turned out to be insufficient too:
comparability is a property of the pair.1.0.0andabcdef123456are
each orderable on their own, but a version and a digest cannot be ordered
against each other, so the same "up to date" answer came back for a server
whose cache entry was reused by name after its package type changed. A new
are_versions_comparable(current, latest, package_type)asks about the pair,
and that is what now guards the short-circuit. -
An all-numeric truncated image digest is documented as incomparable
without a package type, and callers are now pinned to pass one.
get_docker_versiontruncates SHA-256 to 12 hex characters, which can be all
digits — the same shape as a calendar version like202612180000. Resolving
that by guessing (promoting the numeric side when its partner is a digest)
was implemented and rejected: the guess fabricates an update when the numeric
side really is a calendar version, which is whatis_version_newer's
fail-closed contract exists to prevent. A mixed pair therefore stays
incomparable, and a test now enforces that every caller passes the package
type, which is what actually resolves it. -
A
sha256:-prefixed digest with no hex letter is now recognised. The
pattern required a hex letter even when the prefix was present, so an
all-numeric prefixed digest was rejected outright. -
The intermittent
test_ec_p2_7_reconnect_does_not_leak_transportsfailure
is fixed at its root.sse_starlette.sse.AppStatus.should_exitis a
process-global class attribute that uvicorn's shutdown handler latchesTrue
and never resets. The fake-remote test server cleared it on teardown, which
protects against its own shutdown but not against a server started elsewhere
in the same interpreter — andtests/mcp2x, which stops uvicorn servers,
sorts immediately beforetests/runtime. Inheriting the latched flag made
every SSE stream end instantly, so the test failed in CI while passing in
isolation. The flag is now cleared on entry as well as exit, with a test that
latches it deliberately and asserts a connection still works. -
The CHANGELOG CI guard no longer treats a failed label lookup as "no
label". A live-lookup failure now falls back to the frozen event payload
before concluding theskip-changeloglabel is absent, so an API hiccup
cannot block a PR that really was labelled. A lookup that succeeds and
returns no labels stays authoritative — otherwise removing the label would
not re-enable the check. -
gateway.update_serverno longer risks restarting onto a different package
than the one it probed. The tool resolved the server's config, ran an update
probe with a 60-second timeout, and then letgateway.restart_serverresolve
the config a second, independent time. The config loaders re-read from disk on
every call, so a.mcp.jsonor manifest edit landing inside that window could
make pmcp probe and install package A, restart onto package B, and then record
A's version as B's — the silent-misreport class, reached through a race rather
than through resolver divergence.update_servernow re-resolves after the probe, verifies the result still
describes the same downstream process, and restarts onto that exact verified
config. If the configuration changed, or the server is gone from the config
entirely, the update is refused: the package was fetched but is not
activated and no version is recorded, with a message saying so.The check runs before
gateway.refresh(), which is itself a diff-based
reconcile that can disconnect and reconnect a changed server on its own —
checking afterwards would have allowed the fetched package to be activated
before the refusal was reported.The verification covers configuration-driven changes to the spawned process
environment as well as to the command: dropping an explicitenventry whose
value happens to match the ambient one would otherwise compare as unchanged
while silently removing a PMCP-managed credential from the restarted server
(or, reversed, newly exposing one to it). It deliberately does not freeze the
ambient environment across the update — a shell or secret-store change
during the probe affects the probe and the restart alike.This matters more since 2.2.0: with the automatic update notices removed,
gateway.update_serveris the only update path.