Skip to content

fix(validators): distinguish a missing package from a missing version on PyPI/NPM#1411

Open
sronix wants to merge 2 commits into
modelcontextprotocol:mainfrom
sronix:fix/pypi-npm-version-not-found-553
Open

fix(validators): distinguish a missing package from a missing version on PyPI/NPM#1411
sronix wants to merge 2 commits into
modelcontextprotocol:mainfrom
sronix:fix/pypi-npm-version-not-found-553

Conversation

@sronix

@sronix sronix commented Jul 1, 2026

Copy link
Copy Markdown

Fixes #553.

The PyPI and npm validators fetch the version-specific metadata endpoint (/pypi/{name}/{version}/json and the npm equivalent) and report any non-200 as <pkg> not found. That endpoint 404s for two different reasons though: the package doesn't exist, or it exists and only the version is missing (e.g. a release that hasn't propagated yet). So a valid package reads as gone:

- PyPI package 'requests' not found (status: 404)
+ PyPI package 'requests' exists, but version '99.99.99' was not found (status: 404). A newly published release can take a moment to appear on PyPI. Wait and retry, or publish version '99.99.99' before registering it

On a version 404 it probes the package-level endpoint (/pypi/{name}/json, /{name}) with a HEAD to tell the two apart, and reports 429/5xx as transient rather than "not found". HEAD so it reads a status without pulling the whole packument. The probe carries its own 3s deadline: it only refines the error message, so a hung probe must not stretch the validator past the ~10s-per-registry budget the publish path assumes. Same probe-and-classify shape as the existing cargo validator.

The fetch is split into validatePyPIPackage / validateNPMPackage behind export_test.go so the branches are testable with httptest, the same seam cargo uses.

Testing

  • hermetic httptest for the status matrix (version-missing, package-missing, 5xx, 429, inconclusive probe, positive path, scoped npm); the mocks pin the expected method per endpoint (GET fetch, HEAD probe)
  • hermetic deadline test: a hung probe is cut off at ~3s and reported as inconclusive, not "not found"
  • existing live package tests still pass, now also hitting the HEAD probe; on a live 429/5xx they now t.Skip as inconclusive instead of flaking (deliberate: CI runs these against the real registries with no short-mode gating)
  • gofmt / go vet / golangci-lint clean

Out of scope: auto-retry for the propagation race, and SSRF redirect-pinning parity for the pypi/npm clients (pre-existing, shared with nuget).

sronix added 2 commits July 1, 2026 14:12
… on PyPI/NPM

The version-specific metadata endpoint 404s both when a package is missing and when only the requested version is absent. On a version 404, probe the package-level endpoint with HEAD to tell the two apart, and treat 429/5xx as transient. Mirrors the cargo validator's probe-and-classify pattern.

Fixes modelcontextprotocol#553.
The probe only refines an error message, so give it a 3s context deadline
instead of letting it ride the client's full 10s timeout. Pin the expected
methods in the httptest mocks (GET fetch, HEAD probe) and skip the live
RealPackages cases on transient 429/5xx responses instead of failing them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Receive "title":"Bad Request","status":400,"detail":"Failed to publish server" for package not found

1 participant