From 5b3e6b587ba8188d8dbed73c89ecf15fcd1f2baa Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Wed, 3 Jun 2026 18:04:11 +0300 Subject: [PATCH] fix(validators): adopt boundary-anchored mcp-name match in PyPI and NuGet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacked on the cargo follow-up (introduces containsMCPNameToken). This extends the boundary-anchored ownership-token match to the PyPI and NuGet validators, replacing their bare strings.Contains checks so a README declaring a longer name (e.g. io.github.acme/widget-pro) no longer satisfies a claim for a shorter prefix (io.github.acme/widget). ⚠️ BEHAVIOR CHANGE for PyPI/NuGet (not just additive): The new match is strictly stricter — it can only flip a previously-passing publish to failing, never the reverse. The realistic case that flips is a README whose ONLY occurrence of the token is immediately followed by a server-name character [A-Za-z0-9._/-], e.g. a trailing period in prose ("...published as mcp-name: io.github.acme/widget."). The token on its own line, in backticks, or followed by whitespace/newline/HTML-tag is unaffected. Re-validation runs only at publish time (CreateServer); edits/status updates do not re-check ownership and there is no background re-validation, so already- stored servers are not affected — but an existing PyPI/NuGet publisher pushing a NEW VERSION with the token in the glued form would fail where it previously passed. Given the v0.1 API freeze, this should land deliberately and not be promoted to prod without sign-off. Live positive tests (time-mcp-pypi, TimeMcpServer) still pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/validators/registries/nuget.go | 8 ++++---- internal/validators/registries/pypi.go | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/internal/validators/registries/nuget.go b/internal/validators/registries/nuget.go index 0c6d4c51..f8f97d7a 100644 --- a/internal/validators/registries/nuget.go +++ b/internal/validators/registries/nuget.go @@ -237,10 +237,10 @@ func validateReadme(ctx context.Context, serverName, lowerID, lowerVersion strin readmeContent := string(readmeBytes) - // Check for mcp-name: format (more specific) - mcpNamePattern := "mcp-name: " + serverName - if strings.Contains(readmeContent, mcpNamePattern) { - return ValidReadme, nil // Found as mcp-name: format + // Check for the mcp-name: ownership token (boundary-anchored + // to avoid prefix confusion — see containsMCPNameToken). + if containsMCPNameToken(readmeContent, serverName) { + return ValidReadme, nil } return InvalidReadme, nil diff --git a/internal/validators/registries/pypi.go b/internal/validators/registries/pypi.go index 2bac843b..04068a3e 100644 --- a/internal/validators/registries/pypi.go +++ b/internal/validators/registries/pypi.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" "net/url" - "strings" "time" "github.com/modelcontextprotocol/registry/pkg/model" @@ -85,10 +84,10 @@ func ValidatePyPI(ctx context.Context, pkg model.Package, serverName string) err // Check description (README) content description := pypiResp.Info.Description - // Check for mcp-name: format (more specific) - mcpNamePattern := "mcp-name: " + serverName - if strings.Contains(description, mcpNamePattern) { - return nil // Found as mcp-name: format + // Check for the mcp-name: ownership token (boundary-anchored to + // avoid prefix confusion — see containsMCPNameToken). + if containsMCPNameToken(description, serverName) { + return nil } return fmt.Errorf("PyPI package '%s' ownership validation failed. The server name '%s' must appear as 'mcp-name: %s' in the package README", pkg.Identifier, serverName, serverName)