Ban 'latest' as a reserved version string (#413)
## Summary - Adds validation to prevent servers from being published with version string "latest" - "latest" is now a reserved keyword that cannot be used as a version string ## Implementation - Added `ErrReservedVersionString` error constant - Created `validateVersion` function to check for reserved version strings - Integrated validation into the package field validation flow - Added comprehensive test coverage --- This is so we can potentially support things like https://github.com/modelcontextprotocol/registry/discussions/333 in future more easily, as I imagine we might want to.
This commit is contained in:
parent
64b7fc6408
commit
41aacc1846
3 changed files with 45 additions and 5 deletions
|
|
@ -10,6 +10,7 @@ var (
|
||||||
|
|
||||||
// Package validation errors
|
// Package validation errors
|
||||||
ErrPackageNameHasSpaces = errors.New("package name cannot contain spaces")
|
ErrPackageNameHasSpaces = errors.New("package name cannot contain spaces")
|
||||||
|
ErrReservedVersionString = errors.New("version string 'latest' is reserved and cannot be used")
|
||||||
|
|
||||||
// Remote validation errors
|
// Remote validation errors
|
||||||
ErrInvalidRemoteURL = errors.New("invalid remote URL")
|
ErrInvalidRemoteURL = errors.New("invalid remote URL")
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,11 @@ func validatePackageField(obj *model.Package) error {
|
||||||
return ErrPackageNameHasSpaces
|
return ErrPackageNameHasSpaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate version string
|
||||||
|
if err := validateVersion(obj.Version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Validate runtime arguments
|
// Validate runtime arguments
|
||||||
for _, arg := range obj.RuntimeArguments {
|
for _, arg := range obj.RuntimeArguments {
|
||||||
if err := validateArgument(&arg); err != nil {
|
if err := validateArgument(&arg); err != nil {
|
||||||
|
|
@ -95,6 +100,16 @@ func validatePackageField(obj *model.Package) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateVersion validates the version string
|
||||||
|
// NB: we decided that we would not enforce strict semver for version strings
|
||||||
|
func validateVersion(version string) error {
|
||||||
|
if version == "latest" {
|
||||||
|
return ErrReservedVersionString
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateArgument validates argument details
|
// validateArgument validates argument details
|
||||||
func validateArgument(obj *model.Argument) error {
|
func validateArgument(obj *model.Argument) error {
|
||||||
if obj.Type == model.ArgumentTypeNamed {
|
if obj.Type == model.ArgumentTypeNamed {
|
||||||
|
|
@ -144,12 +159,12 @@ func validateArgumentValueFields(name, value, defaultValue string) error {
|
||||||
// collectAvailableVariables collects all available template variables from a package
|
// collectAvailableVariables collects all available template variables from a package
|
||||||
func collectAvailableVariables(pkg *model.Package) []string {
|
func collectAvailableVariables(pkg *model.Package) []string {
|
||||||
var variables []string
|
var variables []string
|
||||||
|
|
||||||
// Add environment variable names
|
// Add environment variable names
|
||||||
for _, env := range pkg.EnvironmentVariables {
|
for _, env := range pkg.EnvironmentVariables {
|
||||||
variables = append(variables, env.Name)
|
variables = append(variables, env.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add runtime argument names and value hints
|
// Add runtime argument names and value hints
|
||||||
for _, arg := range pkg.RuntimeArguments {
|
for _, arg := range pkg.RuntimeArguments {
|
||||||
if arg.Name != "" {
|
if arg.Name != "" {
|
||||||
|
|
@ -159,7 +174,7 @@ func collectAvailableVariables(pkg *model.Package) []string {
|
||||||
variables = append(variables, arg.ValueHint)
|
variables = append(variables, arg.ValueHint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add package argument names and value hints
|
// Add package argument names and value hints
|
||||||
for _, arg := range pkg.PackageArguments {
|
for _, arg := range pkg.PackageArguments {
|
||||||
if arg.Name != "" {
|
if arg.Name != "" {
|
||||||
|
|
@ -169,7 +184,7 @@ func collectAvailableVariables(pkg *model.Package) []string {
|
||||||
variables = append(variables, arg.ValueHint)
|
variables = append(variables, arg.ValueHint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return variables
|
return variables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,7 +208,7 @@ func validatePackageTransport(transport *model.Transport, availableVariables []s
|
||||||
// Check if it's a template variable issue or basic URL issue
|
// Check if it's a template variable issue or basic URL issue
|
||||||
templateVars := extractTemplateVariables(transport.URL)
|
templateVars := extractTemplateVariables(transport.URL)
|
||||||
if len(templateVars) > 0 {
|
if len(templateVars) > 0 {
|
||||||
return fmt.Errorf("%w: template variables in URL %s reference undefined variables. Available variables: %v",
|
return fmt.Errorf("%w: template variables in URL %s reference undefined variables. Available variables: %v",
|
||||||
ErrInvalidRemoteURL, transport.URL, availableVariables)
|
ErrInvalidRemoteURL, transport.URL, availableVariables)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, transport.URL)
|
return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, transport.URL)
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,30 @@ func TestValidate(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: validators.ErrPackageNameHasSpaces.Error(),
|
expectedError: validators.ErrPackageNameHasSpaces.Error(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "package with reserved version 'latest'",
|
||||||
|
serverDetail: apiv0.ServerJSON{
|
||||||
|
Name: "com.example/test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
Repository: model.Repository{
|
||||||
|
URL: "https://github.com/owner/repo",
|
||||||
|
Source: "github",
|
||||||
|
},
|
||||||
|
Version: "1.0.0",
|
||||||
|
Packages: []model.Package{
|
||||||
|
{
|
||||||
|
Identifier: "test-package",
|
||||||
|
RegistryType: "npm",
|
||||||
|
RegistryBaseURL: "https://registry.npmjs.org",
|
||||||
|
Version: "latest",
|
||||||
|
Transport: model.Transport{
|
||||||
|
Type: "stdio",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: validators.ErrReservedVersionString.Error(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "multiple packages with one invalid",
|
name: "multiple packages with one invalid",
|
||||||
serverDetail: apiv0.ServerJSON{
|
serverDetail: apiv0.ServerJSON{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue