Deduplicate server JSON structs between publisher and registry (#318)
<!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> The following PR eliminates duplicate struct definitions by migrating the publisher tool to use canonical types from pkg/model, creating a single source of truth across the codebase. The publisher tool previously had its own copies of structs that were duplicated, ie. ServerJSON, Package, Repository, VersionDetail, EnvironmentVariable, RuntimeArgument and a few others which was not optimal **Changes in the PR:** - Refactored the structs to single canonical types located at pkg/model - Refactor the implementation to use/build canonical structures directly - Remove all duplicate definitions (5 structs eliminated) - Publisher now uses model.ServerJSON, model.Package, model.Repository, etc. - Renamed some of the structs to better reflect their use case, i.e. ServerJSON instead of ServerDetail - Fixes an issue where the Remote property was missing from the previous publisher copy of the Server **Benefits:** - Single source of truth - Schema changes in one place - Type safety - Compiler catches mismatches - Enhanced validation - Field constraints and bson tags - Zero breaking changes - Same CLI interface and JSON output - Future-proof - New canonical fields automatically available Supersedes: #217 and #240 ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> Locally ## Breaking Changes <!-- Will users need to update their code or configurations? --> No ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [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 <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions --> --------- Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
This commit is contained in:
parent
9d04671ee3
commit
099a2d702e
1 changed files with 1280 additions and 1408 deletions
|
|
@ -13,6 +13,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
"github.com/modelcontextprotocol/registry/tools/publisher/auth"
|
||||
)
|
||||
|
||||
|
|
@ -27,50 +28,6 @@ const (
|
|||
)
|
||||
|
||||
// Server structure types for JSON generation
|
||||
type Repository struct {
|
||||
URL string `json:"url"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type VersionDetail struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type EnvironmentVariable struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type RuntimeArgument struct {
|
||||
Description string `json:"description"`
|
||||
IsRequired bool `json:"is_required"`
|
||||
Format string `json:"format"`
|
||||
Value string `json:"value"`
|
||||
Default string `json:"default"`
|
||||
Type string `json:"type"`
|
||||
ValueHint string `json:"value_hint"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
RegistryType string `json:"registry_type,omitempty"`
|
||||
RegistryBaseURL string `json:"registry_base_url,omitempty"`
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
RuntimeHint string `json:"runtime_hint,omitempty"`
|
||||
RuntimeArguments []RuntimeArgument `json:"runtime_arguments,omitempty"`
|
||||
PackageArguments []RuntimeArgument `json:"package_arguments,omitempty"`
|
||||
EnvironmentVariables []EnvironmentVariable `json:"environment_variables,omitempty"`
|
||||
}
|
||||
|
||||
type ServerJSON struct {
|
||||
Schema string `json:"$schema"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Repository Repository `json:"repository"`
|
||||
VersionDetail VersionDetail `json:"version_detail"`
|
||||
Packages []Package `json:"packages"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
|
|
@ -410,29 +367,31 @@ func publishToRegistry(registryURL string, mcpData []byte, token string) error {
|
|||
}
|
||||
|
||||
// parseEnvironmentVariables parses environment variable specifications
|
||||
func parseEnvironmentVariables(envVars []string) []EnvironmentVariable {
|
||||
var environmentVariables []EnvironmentVariable
|
||||
func parseEnvironmentVariables(envVars []string) []model.KeyValueInput {
|
||||
var environmentVariables []model.KeyValueInput
|
||||
for _, envVar := range envVars {
|
||||
parts := strings.SplitN(envVar, ":", 2)
|
||||
description := fmt.Sprintf("Environment variable for %s", parts[0])
|
||||
if len(parts) == 2 {
|
||||
environmentVariables = append(environmentVariables, EnvironmentVariable{
|
||||
Name: parts[0],
|
||||
Description: parts[1],
|
||||
})
|
||||
} else {
|
||||
// If no description provided, use a default
|
||||
environmentVariables = append(environmentVariables, EnvironmentVariable{
|
||||
Name: parts[0],
|
||||
Description: fmt.Sprintf("Environment variable for %s", parts[0]),
|
||||
})
|
||||
description = parts[1]
|
||||
}
|
||||
|
||||
environmentVariables = append(environmentVariables, model.KeyValueInput{
|
||||
Name: parts[0],
|
||||
InputWithVariables: model.InputWithVariables{
|
||||
Input: model.Input{
|
||||
Description: description,
|
||||
Format: model.FormatString,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return environmentVariables
|
||||
}
|
||||
|
||||
// parsePackageArguments parses package argument specifications
|
||||
func parsePackageArguments(packageArgs []string) []RuntimeArgument {
|
||||
var packageArguments []RuntimeArgument
|
||||
func parsePackageArguments(packageArgs []string) []model.Argument {
|
||||
var packageArguments []model.Argument
|
||||
for i, pkgArg := range packageArgs {
|
||||
parts := strings.SplitN(pkgArg, ":", 2)
|
||||
value := parts[0]
|
||||
|
|
@ -441,22 +400,26 @@ func parsePackageArguments(packageArgs []string) []RuntimeArgument {
|
|||
description = parts[1]
|
||||
}
|
||||
|
||||
packageArguments = append(packageArguments, RuntimeArgument{
|
||||
Description: description,
|
||||
IsRequired: true, // Package arguments are typically required
|
||||
Format: "string",
|
||||
Value: value,
|
||||
Default: value,
|
||||
Type: "positional",
|
||||
ValueHint: value,
|
||||
packageArguments = append(packageArguments, model.Argument{
|
||||
Type: model.ArgumentTypePositional,
|
||||
ValueHint: value,
|
||||
InputWithVariables: model.InputWithVariables{
|
||||
Input: model.Input{
|
||||
Description: description,
|
||||
IsRequired: true, // Package arguments are typically required
|
||||
Format: model.FormatString,
|
||||
Value: value,
|
||||
Default: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return packageArguments
|
||||
}
|
||||
|
||||
// parseRuntimeArguments parses the execute command to create runtime arguments
|
||||
func parseRuntimeArguments(execute string) []RuntimeArgument {
|
||||
var runtimeArguments []RuntimeArgument
|
||||
func parseRuntimeArguments(execute string) []model.Argument {
|
||||
var runtimeArguments []model.Argument
|
||||
if execute == "" {
|
||||
return runtimeArguments
|
||||
}
|
||||
|
|
@ -470,14 +433,18 @@ func parseRuntimeArguments(execute string) []RuntimeArgument {
|
|||
// Skip the first part (command) and add each argument as a runtime argument
|
||||
for i, arg := range parts[1:] {
|
||||
description := getArgumentDescription(arg, i, parts)
|
||||
runtimeArguments = append(runtimeArguments, RuntimeArgument{
|
||||
Description: description,
|
||||
IsRequired: false,
|
||||
Format: "string",
|
||||
Value: arg,
|
||||
Default: arg,
|
||||
Type: "positional",
|
||||
ValueHint: arg,
|
||||
runtimeArguments = append(runtimeArguments, model.Argument{
|
||||
Type: model.ArgumentTypePositional,
|
||||
ValueHint: arg,
|
||||
InputWithVariables: model.InputWithVariables{
|
||||
Input: model.Input{
|
||||
Description: description,
|
||||
IsRequired: false,
|
||||
Format: model.FormatString,
|
||||
Value: arg,
|
||||
Default: arg,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +474,7 @@ func createServerStructure(
|
|||
name, description, version, repoURL, repoSource, registryName,
|
||||
packageName, packageVersion, runtimeHint, execute string,
|
||||
envVars []string, packageArgs []string, status string,
|
||||
) ServerJSON {
|
||||
) model.ServerJSON {
|
||||
// Parse environment variables
|
||||
environmentVariables := parseEnvironmentVariables(envVars)
|
||||
|
||||
|
|
@ -562,31 +529,42 @@ func createServerStructure(
|
|||
}
|
||||
|
||||
// Create package with new structured fields
|
||||
pkg := Package{
|
||||
pkg := model.Package{
|
||||
RegistryType: registryTypeValue,
|
||||
RegistryBaseURL: registryBaseURLValue,
|
||||
Identifier: identifier,
|
||||
Version: packageVersion,
|
||||
RuntimeHint: runtimeHint,
|
||||
RunTimeHint: runtimeHint,
|
||||
RuntimeArguments: runtimeArguments,
|
||||
PackageArguments: packageArguments,
|
||||
EnvironmentVariables: environmentVariables,
|
||||
}
|
||||
|
||||
// Convert string status to model.Status type
|
||||
var statusValue model.Status
|
||||
switch status {
|
||||
case "active":
|
||||
statusValue = model.StatusActive
|
||||
case "deprecated":
|
||||
statusValue = model.StatusDeprecated
|
||||
default:
|
||||
statusValue = model.StatusActive // fallback to active
|
||||
}
|
||||
|
||||
// Create server structure
|
||||
return ServerJSON{
|
||||
return model.ServerJSON{
|
||||
Schema: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
||||
Name: name,
|
||||
Description: description,
|
||||
Status: status,
|
||||
Repository: Repository{
|
||||
Status: statusValue,
|
||||
Repository: model.Repository{
|
||||
URL: repoURL,
|
||||
Source: repoSource,
|
||||
},
|
||||
VersionDetail: VersionDetail{
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: version,
|
||||
},
|
||||
Packages: []Package{pkg},
|
||||
Packages: []model.Package{pkg},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue