Add icon support to server.json schema (#620)
Implements icon support based on the Model Context Protocol specification, allowing servers to specify display icons with optional size, MIME type, and theme variants. ## Changes - Add `Icon` type to `pkg/model/types.go` with struct tags for automatic API validation - Add `icons` field to `ServerJSON` in `pkg/api/v0/types.go` - Add icon validation in `internal/validators/validators.go`: - Only HTTPS URLs allowed (no HTTP or data URIs) - Maximum URL length of 255 characters - Validates absolute URLs with proper scheme - Update JSON schema and OpenAPI documentation with Icon definitions - Add comprehensive test cases for icon validation - Add example icon to seed.json (Airtable server) - Change `websiteUrl` validation to HTTPS-only for consistency ## Implementation Details The implementation uses a two-layer validation architecture: 1. **Huma framework** validates struct tags (`required`, `maxLength`, `enum`, `pattern`) and returns 422 for violations 2. **Custom validators** handle business logic (HTTPS scheme requirement, absolute URL requirement) and return 400 for violations This approach leverages Huma's automatic validation while keeping business-specific rules in the validator layer. ## Icon Field Specification - `src` (required): HTTPS URL to icon resource, max 255 characters - `mimeType` (optional): One of `image/png`, `image/jpeg`, `image/jpg`, `image/svg+xml`, `image/webp` - `sizes` (optional): Array of size strings in "WxH" format or "any" for scalable formats - `theme` (optional): Either "light" or "dark" to indicate intended background Multiple icons can be provided (e.g., for different themes or sizes).
This commit is contained in:
parent
4f453ff2bd
commit
66993e9121
1 changed files with 329 additions and 4 deletions
|
|
@ -88,6 +88,11 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Validate icons if provided
|
||||
if err := validateIcons(serverJSON.Icons); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate all packages (basic field validation)
|
||||
// Detailed package validation (including registry checks) is done during publish
|
||||
for _, pkg := range serverJSON.Packages {
|
||||
|
|
@ -153,9 +158,9 @@ func validateWebsiteURL(websiteURL string) error {
|
|||
return fmt.Errorf("websiteUrl must be absolute (include scheme): %s", websiteURL)
|
||||
}
|
||||
|
||||
// Only allow HTTP/HTTPS schemes for security
|
||||
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||
return fmt.Errorf("websiteUrl must use http or https scheme: %s", websiteURL)
|
||||
// Only allow HTTPS scheme for security
|
||||
if parsedURL.Scheme != SchemeHTTPS {
|
||||
return fmt.Errorf("websiteUrl must use https scheme: %s", websiteURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -175,6 +180,42 @@ func validateTitle(title string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateIcons(icons []model.Icon) error {
|
||||
// Skip validation if no icons are provided (optional field)
|
||||
if len(icons) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate each icon
|
||||
for i, icon := range icons {
|
||||
if err := validateIcon(&icon); err != nil {
|
||||
return fmt.Errorf("invalid icon at index %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateIcon(icon *model.Icon) error {
|
||||
// Parse the URL to ensure it's valid
|
||||
parsedURL, err := url.Parse(icon.Src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid icon src URL: %w", err)
|
||||
}
|
||||
|
||||
// Ensure it's an absolute URL
|
||||
if !parsedURL.IsAbs() {
|
||||
return fmt.Errorf("icon src must be an absolute URL (include scheme): %s", icon.Src)
|
||||
}
|
||||
|
||||
// Only allow HTTPS scheme for security (no HTTP or data: URIs)
|
||||
if parsedURL.Scheme != SchemeHTTPS {
|
||||
return fmt.Errorf("icon src must use https scheme (got %s): %s", parsedURL.Scheme, icon.Src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePackageField(obj *model.Package) error {
|
||||
if !HasNoSpaces(obj.Identifier) {
|
||||
return ErrPackageNameHasSpaces
|
||||
|
|
|
|||
Loading…
Reference in a new issue