Repository: modelcontextprotocol/registry
File: tools/validate-schemas/main.go (Go)

Code before:
	validatedCount := 0

	for _, schemaFile := range schemas {
		log.Printf("Validating %s...", schemaFile.name)

		if err := validateSchema(compiler, schemaFile.path); err != nil {
			log.Printf("  ❌ Invalid: %v", err)
			allValid = false
		} else {
			log.Printf("  ✅ Valid JSON Schema")
			validatedCount++
		}
	}

	if !allValid {
		return fmt.Errorf("one or more schemas are invalid")
	}

	if validatedCount != expectedSchemaCount {
		return fmt.Errorf("validation count mismatch: expected to validate %d schemas but only %d passed",
			expectedSchemaCount, validatedCount)
	}

	log.Printf("\nSuccessfully validated all %d schemas!", validatedCount)
	return nil
}

func validateSchema(compiler *jsonschema.Compiler, path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("failed to read file: %w", err)
	}

	var schemaData interface{}
	if err := json.Unmarshal(data, &schemaData); err != nil {
		return fmt.Errorf("invalid JSON: %w", err)
	}

	// For registry-schema.json, we need to register the base schema it references
	if strings.Contains(path, "registry-schema.json") {
		basePath := filepath.Join(filepath.Dir(path), "schema.json")
		baseData, err := os.ReadFile(basePath)
		if err != nil {
			return fmt.Errorf("failed to read base schema: %w", err)
		}

		// Add the base schema to the compiler with the expected URL
		if err := compiler.AddResource("https://modelcontextprotocol.io/schemas/draft/2025-07-09/server.json", bytes.NewReader(baseData)); err != nil {
			return fmt.Errorf("failed to add base schema resource: %w", err)
		}
	}

Review comment (suggestion):
It would be better to make the additional resource the caller's responsibility so that this function doesn't need foreknowledge of its inputs. I see that may not be worth the extra effort though, so I'm fine with this

Code after:

	if validatedCount != expectedSchemaCount {
		return fmt.Errorf("validation failed: expected to validate %d schemas but only %d passed",
			expectedSchemaCount, validatedCount)
	}

	log.Printf("\nSuccessfully validated all %d schemas!", validatedCount)
	return nil
}

func validateSchema(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("failed to read file: %w", err)
	}

	var schemaData any
	if err := json.Unmarshal(data, &schemaData); err != nil {
		return fmt.Errorf("invalid JSON: %w", err)
	}

	compiler := jsonschema.NewCompiler()
	compiler.Draft = jsonschema.Draft7

	// For registry-schema.json, we need to register the base schema it references
	if strings.Contains(path, "registry-schema.json") {
		basePath := filepath.Join(filepath.Dir(path), "schema.json")
		baseData, err := os.ReadFile(basePath)
		if err != nil {
			return fmt.Errorf("failed to read base schema: %w", err)
		}

		// Add the base schema to the compiler with the expected URL
		if err := compiler.AddResource("https://modelcontextprotocol.io/schemas/draft/2025-07-09/server.json", bytes.NewReader(baseData)); err != nil {
			return fmt.Errorf("failed to add base schema resource: %w", err)
		}
	}

	if _, err := compiler.Compile(path); err != nil {
		return fmt.Errorf("invalid schema: %w", err)
	}

	return nil
}