MCPcopy
hub / github.com/modelcontextprotocol/registry / ValidatePyPI

Function ValidatePyPI

internal/validators/registries/pypi.go:29–95  ·  view source on GitHub ↗

ValidatePyPI validates that a PyPI package contains the correct MCP server name

(ctx context.Context, pkg model.Package, serverName string)

Source from the content-addressed store, hash-verified

27
28// ValidatePyPI validates that a PyPI package contains the correct MCP server name
29func ValidatePyPI(ctx context.Context, pkg model.Package, serverName string) error {
30 // Set default registry base URL if empty
31 if pkg.RegistryBaseURL == "" {
32 pkg.RegistryBaseURL = model.RegistryURLPyPI
33 }
34
35 if pkg.Identifier == "" {
36 return ErrMissingIdentifierForPyPI
37 }
38
39 if pkg.Version == "" {
40 return ErrMissingVersionForPyPi
41 }
42
43 // Validate that MCPB-specific fields are not present
44 if pkg.FileSHA256 != "" {
45 return fmt.Errorf("PyPI packages must not have 'fileSha256' field - this is only for MCPB packages")
46 }
47
48 // Validate that the registry base URL matches PyPI exactly
49 if pkg.RegistryBaseURL != model.RegistryURLPyPI {
50 return fmt.Errorf("registry type and base URL do not match: '%s' is not valid for registry type '%s'. Expected: %s",
51 pkg.RegistryBaseURL, model.RegistryTypePyPI, model.RegistryURLPyPI)
52 }
53
54 client := &http.Client{Timeout: 10 * time.Second}
55
56 // PathEscape so an identifier that smuggles "/" or ".." cannot redirect
57 // the metadata fetch to a different package than the one being claimed.
58 fetchURL := fmt.Sprintf("%s/pypi/%s/%s/json",
59 pkg.RegistryBaseURL,
60 url.PathEscape(pkg.Identifier),
61 url.PathEscape(pkg.Version))
62 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fetchURL, nil)
63 if err != nil {
64 return fmt.Errorf("failed to create request: %w", err)
65 }
66
67 req.Header.Set("User-Agent", "MCP-Registry-Validator/1.0")
68 req.Header.Set("Accept", "application/json")
69
70 resp, err := client.Do(req)
71 if err != nil {
72 return fmt.Errorf("failed to fetch package metadata from PyPI: %w", err)
73 }
74 defer resp.Body.Close()
75
76 if resp.StatusCode != http.StatusOK {
77 return fmt.Errorf("PyPI package '%s' not found (status: %d)", pkg.Identifier, resp.StatusCode)
78 }
79
80 var pypiResp PyPIPackageResponse
81 if err := json.NewDecoder(resp.Body).Decode(&pypiResp); err != nil {
82 return fmt.Errorf("failed to parse PyPI package metadata: %w", err)
83 }
84
85 // Check description (README) content
86 description := pypiResp.Info.Description

Callers 2

ValidatePackageFunction · 0.92

Calls 2

SetMethod · 0.80
CloseMethod · 0.65

Tested by 1

Used in the wild real call sites across dependent graphs

searching dependent graphs…