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).
107 lines
4.2 KiB
Go
107 lines
4.2 KiB
Go
package model
|
|
|
|
// Status represents the lifecycle status of a server
|
|
type Status string
|
|
|
|
const (
|
|
StatusActive Status = "active"
|
|
StatusDeprecated Status = "deprecated"
|
|
StatusDeleted Status = "deleted"
|
|
)
|
|
|
|
// Transport represents transport configuration with optional URL templating
|
|
type Transport struct {
|
|
Type string `json:"type"`
|
|
URL string `json:"url,omitempty"`
|
|
Headers []KeyValueInput `json:"headers,omitempty"`
|
|
}
|
|
|
|
// Package represents a package configuration
|
|
type Package struct {
|
|
// RegistryType indicates how to download packages (e.g., "npm", "pypi", "oci", "mcpb")
|
|
RegistryType string `json:"registryType" minLength:"1"`
|
|
// RegistryBaseURL is the base URL of the package registry
|
|
RegistryBaseURL string `json:"registryBaseUrl,omitempty"`
|
|
// Identifier is the package identifier - either a package name (for registries) or URL (for direct downloads)
|
|
Identifier string `json:"identifier" minLength:"1"`
|
|
Version string `json:"version" minLength:"1"`
|
|
FileSHA256 string `json:"fileSha256,omitempty"`
|
|
RunTimeHint string `json:"runtimeHint,omitempty"`
|
|
Transport Transport `json:"transport,omitempty"`
|
|
RuntimeArguments []Argument `json:"runtimeArguments,omitempty"`
|
|
PackageArguments []Argument `json:"packageArguments,omitempty"`
|
|
EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"`
|
|
}
|
|
|
|
// Repository represents a source code repository as defined in the spec
|
|
type Repository struct {
|
|
URL string `json:"url"`
|
|
Source string `json:"source"`
|
|
ID string `json:"id,omitempty"`
|
|
Subfolder string `json:"subfolder,omitempty"`
|
|
}
|
|
|
|
// Format represents the input format type
|
|
type Format string
|
|
|
|
const (
|
|
FormatString Format = "string"
|
|
FormatNumber Format = "number"
|
|
FormatBoolean Format = "boolean"
|
|
FormatFilePath Format = "file_path"
|
|
)
|
|
|
|
// Input represents a configuration input
|
|
type Input struct {
|
|
Description string `json:"description,omitempty"`
|
|
IsRequired bool `json:"isRequired,omitempty"`
|
|
Format Format `json:"format,omitempty"`
|
|
Value string `json:"value,omitempty"`
|
|
IsSecret bool `json:"isSecret,omitempty"`
|
|
Default string `json:"default,omitempty"`
|
|
Choices []string `json:"choices,omitempty"`
|
|
}
|
|
|
|
// InputWithVariables represents an input that can contain variables
|
|
type InputWithVariables struct {
|
|
Input `json:",inline"`
|
|
Variables map[string]Input `json:"variables,omitempty"`
|
|
}
|
|
|
|
// KeyValueInput represents a named input with variables
|
|
type KeyValueInput struct {
|
|
InputWithVariables `json:",inline"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// ArgumentType represents the type of argument
|
|
type ArgumentType string
|
|
|
|
const (
|
|
ArgumentTypePositional ArgumentType = "positional"
|
|
ArgumentTypeNamed ArgumentType = "named"
|
|
)
|
|
|
|
// Argument defines a type that can be either a PositionalArgument or a NamedArgument
|
|
type Argument struct {
|
|
InputWithVariables `json:",inline"`
|
|
Type ArgumentType `json:"type"`
|
|
Name string `json:"name,omitempty"`
|
|
IsRepeated bool `json:"isRepeated,omitempty"`
|
|
ValueHint string `json:"valueHint,omitempty"`
|
|
}
|
|
|
|
// Icon represents an optionally-sized icon that can be displayed in a user interface
|
|
type Icon struct {
|
|
// Src is a standard URI pointing to an icon resource (HTTPS URL only for registry)
|
|
Src string `json:"src" required:"true" format:"uri" maxLength:"255"`
|
|
// MimeType is an optional MIME type override if the source MIME type is missing or generic
|
|
MimeType *string `json:"mimeType,omitempty" enum:"image/png,image/jpeg,image/jpg,image/svg+xml,image/webp"`
|
|
// Sizes is an optional array of strings that specify sizes at which the icon can be used
|
|
// Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for scalable formats
|
|
Sizes []string `json:"sizes,omitempty" pattern:"^(\\d+x\\d+|any)$"`
|
|
// Theme is an optional specifier for the theme this icon is designed for
|
|
// "light" indicates the icon is designed for light backgrounds
|
|
// "dark" indicates the icon is designed for dark backgrounds
|
|
Theme *string `json:"theme,omitempty" enum:"light,dark"`
|
|
}
|