fix: Named arguments should identify the argument name (#321)

Originally by @dmartinol. This branch is an attempt by @domdomegg to fix
the merge conflicts...

---

## Motivation and Context
Closes #145 

Add comprehensive validation for named arguments in MCP server packages
to ensure argument names, values, and defaults follow proper format
conventions. This prevents common issues where argument names contain
embedded values or descriptions, and where values/defaults incorrectly
start with the argument name itself.

Key changes:
- Add ArgumentValidator with validation rules for named argument format
- Enhance PackageValidator to validate runtime and package arguments
- Update import process to log warnings for invalid servers instead of
failing
- Fix 396 servers in seed.json with malformed named arguments
- Extended `validate-examples` tool to apply the object validator (it
was completely missed before)
- Add comprehensive test coverage for all validation scenarios

Examples of fixes applied:
- "--directory <path>" → "--directory" with value "<path>"
- "--with-editable $REPO" → "--with-editable" with value "$REPO"
- "recipient@example.com" → "--recipient" (proper argument naming)

All servers now pass validation during both API publishing and data
import.

🤖 Generated with [Claude Code](https://claude.ai/code)

## How Has This Been Tested?
With ad-hoc unit tests.
Manual integration tests of built artifact and the seed data.

## Breaking Changes
New registrations need to follow the argument name conventions,
otherwise the import would fail for the inconsistent servers.

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

---------

Signed-off-by: Daniele Martinoli <dmartino@redhat.com>
Co-authored-by: Daniele Martinoli <dmartino@redhat.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
adam jones 2025-08-29 12:17:53 +01:00 • committed by GitHub
commit edf3929504
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -15,6 +15,8 @@ import (
"strings"
jsonschema "github.com/santhosh-tekuri/jsonschema/v5"
"github.com/modelcontextprotocol/registry/pkg/model"
"github.com/modelcontextprotocol/registry/internal/validators"
)
const (
@ -65,52 +67,11 @@ func runValidation() error {
}
validatedCount := 0
for i, example := range examples {
log.Printf("Example %d:", i+1)
var data any
if err := json.Unmarshal([]byte(example.content), &data); err != nil {
log.Printf(" ❌ Invalid JSON: %v", err)
continue
}
// Extract server portion if this is a PublishRequest format
serverData := data
publishRequestValid := true
if dataMap, ok := data.(map[string]any); ok {
if server, exists := dataMap["server"]; exists {
// This is a PublishRequest format - validate only expected properties exist
for key := range dataMap {
if key != "server" && key != "x-publisher" {
log.Printf(" Invalid PublishRequest property: ❌ %s (only 'server' and optional 'x-publisher' are allowed)", key)
publishRequestValid = false
}
}
serverData = server
}
}
baseValid := false
registryValid := false
if err := baseSchema.Validate(serverData); err != nil {
log.Printf(" Validating against server.schema.json: ❌")
log.Printf(" Error: %v", err)
} else {
log.Printf(" Validating against server.schema.json: ✅")
baseValid = true
}
if err := registrySchema.Validate(serverData); err != nil {
log.Printf(" Validating against registry-schema.json: ❌")
log.Printf(" Error: %v", err)
} else {
log.Printf(" Validating against registry-schema.json: ✅")
registryValid = true
}
// Only count as validated if all validations passed
if publishRequestValid && baseValid && registryValid {
if validateExample(example, baseSchema, registrySchema) {
validatedCount++
}
@ -118,7 +79,7 @@ func runValidation() error {
}
if validatedCount != expectedExampleCount {
return fmt.Errorf("validation failed: expected %d examples to pass both validations but only %d did",
return fmt.Errorf("validation failed: expected %d examples to pass all validations but only %d did",
expectedExampleCount, validatedCount)
}
@ -126,6 +87,72 @@ func runValidation() error {
return nil
}
func validateExample(ex example, baseSchema, registrySchema *jsonschema.Schema) bool {
var data any
if err := json.Unmarshal([]byte(ex.content), &data); err != nil {
log.Printf(" ❌ Invalid JSON: %v", err)
return false
}
// Extract server portion if this is a PublishRequest format
serverData := data
publishRequestValid := true
if dataMap, ok := data.(map[string]any); ok {
if server, exists := dataMap["server"]; exists {
// This is a PublishRequest format - validate only expected properties exist
for key := range dataMap {
if key != "server" && key != "x-publisher" {
log.Printf(" Invalid PublishRequest property: ❌ %s (only 'server' and optional 'x-publisher' are allowed)", key)
publishRequestValid = false
}
}
serverData = server
}
}
baseValid := validateAgainstSchema(serverData, baseSchema, "server.schema.json")
registryValid := validateAgainstSchema(serverData, registrySchema, "registry-schema.json")
goValidatorValid := validateWithObjectValidator(serverData)
// Only count as validated if all validations passed
return publishRequestValid && baseValid && registryValid && goValidatorValid
}
func validateAgainstSchema(data any, schema *jsonschema.Schema, schemaName string) bool {
if err := schema.Validate(data); err != nil {
log.Printf(" Validating against %s: ❌", schemaName)
log.Printf(" Error: %v", err)
return false
}
log.Printf(" Validating against %s: ✅", schemaName)
return true
}
func validateWithObjectValidator(serverData any) bool {
var serverDetail model.ServerJSON
serverDataBytes, err := json.Marshal(serverData)
if err != nil {
log.Printf(" Validating with Go Validator: ❌")
log.Printf(" Error marshaling server data: %v", err)
return false
}
if err := json.Unmarshal(serverDataBytes, &serverDetail); err != nil {
log.Printf(" Validating with Go Validator: ❌")
log.Printf(" Error unmarshaling to ServerDetail: %v", err)
return false
}
if err := validators.ValidateServerJSON(&serverDetail); err != nil {
log.Printf(" Validating with Go Validator: ❌")
log.Printf(" Error: %v", err)
return false
}
log.Printf(" Validating with Go Validator: ✅")
return true
}
type example struct {
content string
line int