feat: refactor to account for updated openAPI schema
* Update the model to handle new changes to schema based on https://github.com/modelcontextprotocol/registry/pull/33 * Made the import script a little simpler. * Add a new updated seed file * update template mcp.json file for publisher
This commit is contained in:
parent
5ef4d769ed
commit
5c238eceef
11 changed files with 17637 additions and 359 deletions
17446
data/seed_2025_05_16.json
Normal file
17446
data/seed_2025_05_16.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -13,8 +13,8 @@ import (
|
|||
|
||||
// Response is a paginated API response
|
||||
type PaginatedResponse struct {
|
||||
Data []model.Entry `json:"servers"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Data []model.Server `json:"servers"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Metadata contains pagination metadata
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ var (
|
|||
// Database defines the interface for database operations on MCPRegistry entries
|
||||
type Database interface {
|
||||
// List retrieves all MCPRegistry entries with optional filtering
|
||||
List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Entry, string, error)
|
||||
List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Server, string, error)
|
||||
// GetByID retrieves a single ServerDetail by it's ID
|
||||
GetByID(ctx context.Context, id string) (*model.ServerDetail, error)
|
||||
// Publish adds a new ServerDetail to the database
|
||||
|
|
|
|||
|
|
@ -60,231 +60,26 @@ func readSeedFile(path string) ([]model.ServerDetail, error) {
|
|||
return nil, fmt.Errorf("failed to parse JSON: %v (original error: %v)", jsonErr, err)
|
||||
}
|
||||
|
||||
// Convert raw data to model format
|
||||
servers = convertToServerDetails(rawData)
|
||||
}
|
||||
|
||||
log.Printf("Found %d server entries in seed file", len(servers))
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// convertToServerDetails converts raw JSON data to ServerDetail structs
|
||||
func convertToServerDetails(rawData []map[string]interface{}) []model.ServerDetail {
|
||||
servers := make([]model.ServerDetail, 0, len(rawData))
|
||||
|
||||
for _, data := range rawData {
|
||||
server := model.ServerDetail{}
|
||||
|
||||
// Extract basic fields
|
||||
if id, ok := data["id"].(string); ok {
|
||||
server.ID = id
|
||||
}
|
||||
if name, ok := data["name"].(string); ok {
|
||||
server.Name = name
|
||||
}
|
||||
if desc, ok := data["description"].(string); ok {
|
||||
server.Description = desc
|
||||
}
|
||||
if version, ok := data["version"].(string); ok {
|
||||
if version == "" || version == "latest" {
|
||||
version = "0.0.1"
|
||||
}
|
||||
server.VersionDetail = model.VersionDetail{
|
||||
Version: version,
|
||||
ReleaseDate: time.Now().Format(time.RFC3339),
|
||||
IsLatest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Extract repository
|
||||
if repo, ok := data["repository"].(map[string]interface{}); ok {
|
||||
server.Repository = model.Repository{
|
||||
URL: getStringValue(repo, "url"),
|
||||
SubFolder: getStringValue(repo, "subfolder"),
|
||||
Branch: getStringValue(repo, "branch"),
|
||||
Commit: getStringValue(repo, "commit"),
|
||||
}
|
||||
}
|
||||
|
||||
// Extract registries
|
||||
if registries, ok := data["registries"].([]interface{}); ok {
|
||||
for _, reg := range registries {
|
||||
if regMap, ok := reg.(map[string]interface{}); ok {
|
||||
registry := model.Registries{
|
||||
Name: getStringValue(regMap, "name"),
|
||||
PackageName: getStringValue(regMap, "packagename"),
|
||||
License: getStringValue(regMap, "license"),
|
||||
}
|
||||
|
||||
// Handle command arguments if present
|
||||
if cmd, ok := regMap["command_arguments"].(map[string]interface{}); ok {
|
||||
commandArgs := model.Command{}
|
||||
|
||||
// Extract sub commands
|
||||
if subCmds, ok := cmd["sub_commands"].([]interface{}); ok {
|
||||
for _, sc := range subCmds {
|
||||
if scMap, ok := sc.(map[string]interface{}); ok {
|
||||
subCmd := model.SubCommand{
|
||||
Name: getStringValue(scMap, "name"),
|
||||
Description: getStringValue(scMap, "description"),
|
||||
}
|
||||
|
||||
// Extract named arguments in sub commands
|
||||
if namedArgs, ok := scMap["named_arguments"].([]interface{}); ok {
|
||||
for _, na := range namedArgs {
|
||||
if naMap, ok := na.(map[string]interface{}); ok {
|
||||
namedArg := extractNamedArgument(naMap)
|
||||
subCmd.NamedArguments = append(subCmd.NamedArguments, namedArg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandArgs.SubCommands = append(commandArgs.SubCommands, subCmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract positional arguments
|
||||
if posArgs, ok := cmd["positional_arguments"].([]interface{}); ok {
|
||||
for _, pa := range posArgs {
|
||||
if paMap, ok := pa.(map[string]interface{}); ok {
|
||||
posArg := model.PositionalArgument{}
|
||||
|
||||
if pos, ok := paMap["position"].(float64); ok {
|
||||
posArg.Position = int(pos)
|
||||
}
|
||||
|
||||
if arg, ok := paMap["argument"].(map[string]interface{}); ok {
|
||||
posArg.Argument = extractArgument(arg)
|
||||
}
|
||||
|
||||
commandArgs.PositionalArguments = append(commandArgs.PositionalArguments, posArg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract environment variables
|
||||
if envVars, ok := cmd["environment_variables"].([]interface{}); ok {
|
||||
for _, ev := range envVars {
|
||||
if evMap, ok := ev.(map[string]interface{}); ok {
|
||||
envVar := model.EnvironmentVariable{
|
||||
Name: getStringValue(evMap, "name"),
|
||||
Description: getStringValue(evMap, "description"),
|
||||
}
|
||||
|
||||
if required, ok := evMap["required"].(bool); ok {
|
||||
envVar.Required = required
|
||||
}
|
||||
|
||||
commandArgs.EnvironmentVariables = append(commandArgs.EnvironmentVariables, envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract named arguments
|
||||
if namedArgs, ok := cmd["named_arguments"].([]interface{}); ok {
|
||||
for _, na := range namedArgs {
|
||||
if naMap, ok := na.(map[string]interface{}); ok {
|
||||
namedArg := extractNamedArgument(naMap)
|
||||
commandArgs.NamedArguments = append(commandArgs.NamedArguments, namedArg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.CommandArguments = commandArgs
|
||||
}
|
||||
|
||||
server.Registries = append(server.Registries, registry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract remotes
|
||||
if remotes, ok := data["remotes"].([]interface{}); ok {
|
||||
for _, rem := range remotes {
|
||||
if remMap, ok := rem.(map[string]interface{}); ok {
|
||||
remote := model.Remotes{
|
||||
TransportType: getStringValue(remMap, "transport_type"),
|
||||
Url: getStringValue(remMap, "url"),
|
||||
}
|
||||
server.Remotes = append(server.Remotes, remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// extractArgument extracts an Argument struct from a map
|
||||
func extractArgument(data map[string]interface{}) model.Argument {
|
||||
arg := model.Argument{
|
||||
Name: getStringValue(data, "name"),
|
||||
Description: getStringValue(data, "description"),
|
||||
DefaultValue: getStringValue(data, "default_value"),
|
||||
}
|
||||
|
||||
// Extract boolean fields
|
||||
if isRequired, ok := data["is_required"].(bool); ok {
|
||||
arg.IsRequired = isRequired
|
||||
}
|
||||
if isEditable, ok := data["is_editable"].(bool); ok {
|
||||
arg.IsEditable = isEditable
|
||||
}
|
||||
if isRepeatable, ok := data["is_repeatable"].(bool); ok {
|
||||
arg.IsRepeatable = isRepeatable
|
||||
}
|
||||
|
||||
// Extract string array for choices
|
||||
if choices, ok := data["choices"].([]interface{}); ok {
|
||||
for _, choice := range choices {
|
||||
if strChoice, ok := choice.(string); ok {
|
||||
arg.Choices = append(arg.Choices, strChoice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arg
|
||||
}
|
||||
|
||||
// extractNamedArgument extracts a NamedArguments struct from a map
|
||||
func extractNamedArgument(data map[string]interface{}) model.NamedArguments {
|
||||
namedArg := model.NamedArguments{
|
||||
ShortFlag: getStringValue(data, "short_flag"),
|
||||
LongFlag: getStringValue(data, "long_flag"),
|
||||
}
|
||||
|
||||
if requiresValue, ok := data["requires_value"].(bool); ok {
|
||||
namedArg.RequiresValue = requiresValue
|
||||
}
|
||||
|
||||
if arg, ok := data["argument"].(map[string]interface{}); ok {
|
||||
namedArg.Argument = extractArgument(arg)
|
||||
}
|
||||
|
||||
return namedArg
|
||||
}
|
||||
|
||||
// getStringValue safely extracts a string value from a map
|
||||
func getStringValue(data map[string]interface{}, key string) string {
|
||||
if val, ok := data[key].(string); ok {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// importData imports the seed data into MongoDB
|
||||
func importData(ctx context.Context, collection *mongo.Collection, servers []model.ServerDetail) {
|
||||
log.Printf("Importing %d servers into collection %s", len(servers), collection.Name())
|
||||
|
||||
for i, server := range servers {
|
||||
if server.ID == "" || server.Name == "" {
|
||||
log.Printf("Skipping server %d: ID or Name is empty", i+1)
|
||||
continue
|
||||
}
|
||||
// Create filter based on server ID
|
||||
filter := bson.M{"id": server.ID}
|
||||
|
||||
if server.VersionDetail.Version == "" {
|
||||
server.VersionDetail.Version = "0.0.1"
|
||||
server.VersionDetail.Version = "0.0.1-seed"
|
||||
server.VersionDetail.ReleaseDate = time.Now().Format(time.RFC3339)
|
||||
server.VersionDetail.IsLatest = true
|
||||
|
||||
|
|
|
|||
|
|
@ -10,19 +10,19 @@ import (
|
|||
|
||||
// MemoryDB is an in-memory implementation of the Database interface
|
||||
type MemoryDB struct {
|
||||
entries map[string]*model.Entry
|
||||
entries map[string]*model.Server
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMemoryDB creates a new instance of the in-memory database
|
||||
func NewMemoryDB(e map[string]*model.Entry) *MemoryDB {
|
||||
func NewMemoryDB(e map[string]*model.Server) *MemoryDB {
|
||||
return &MemoryDB{
|
||||
entries: e,
|
||||
}
|
||||
}
|
||||
|
||||
// List retrieves all MCPRegistry entries with optional filtering and pagination
|
||||
func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Entry, string, error) {
|
||||
func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Server, string, error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, "", ctx.Err()
|
||||
}
|
||||
|
|
@ -35,14 +35,14 @@ func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cur
|
|||
defer db.mu.RUnlock()
|
||||
|
||||
// Convert all entries to a slice for pagination
|
||||
var allEntries []*model.Entry
|
||||
var allEntries []*model.Server
|
||||
for _, entry := range db.entries {
|
||||
entryCopy := *entry
|
||||
allEntries = append(allEntries, &entryCopy)
|
||||
}
|
||||
|
||||
// Simple filtering implementation
|
||||
var filteredEntries []*model.Entry
|
||||
var filteredEntries []*model.Server
|
||||
for _, entry := range allEntries {
|
||||
include := true
|
||||
|
||||
|
|
@ -98,11 +98,11 @@ func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cur
|
|||
endIdx = len(filteredEntries)
|
||||
}
|
||||
|
||||
var result []*model.Entry
|
||||
var result []*model.Server
|
||||
if startIdx < len(filteredEntries) {
|
||||
result = filteredEntries[startIdx:endIdx]
|
||||
} else {
|
||||
result = []*model.Entry{}
|
||||
result = []*model.Server{}
|
||||
}
|
||||
|
||||
// Determine next cursor
|
||||
|
|
@ -125,11 +125,13 @@ func (db *MemoryDB) GetByID(ctx context.Context, id string) (*model.ServerDetail
|
|||
|
||||
if entry, exists := db.entries[id]; exists {
|
||||
return &model.ServerDetail{
|
||||
ID: entry.ID,
|
||||
Name: entry.Name,
|
||||
Description: entry.Description,
|
||||
VersionDetail: entry.VersionDetail,
|
||||
Repository: entry.Repository,
|
||||
Server: model.Server{
|
||||
ID: entry.ID,
|
||||
Name: entry.Name,
|
||||
Description: entry.Description,
|
||||
VersionDetail: entry.VersionDetail,
|
||||
Repository: entry.Repository,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +164,7 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail *model.ServerDetai
|
|||
return ErrInvalidInput
|
||||
}
|
||||
|
||||
db.entries[serverDetail.ID] = &model.Entry{
|
||||
db.entries[serverDetail.ID] = &model.Server{
|
||||
ID: serverDetail.ID,
|
||||
Name: serverDetail.Name,
|
||||
Description: serverDetail.Description,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func NewMongoDB(ctx context.Context, connectionURI, databaseName, collectionName
|
|||
}
|
||||
|
||||
// List retrieves MCPRegistry entries with optional filtering and pagination
|
||||
func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Entry, string, error) {
|
||||
func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, cursor string, limit int) ([]*model.Server, string, error) {
|
||||
if limit <= 0 {
|
||||
// Set default limit if not provided
|
||||
limit = 10
|
||||
|
|
@ -84,14 +84,16 @@ func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, curs
|
|||
|
||||
// Convert Go map to MongoDB filter
|
||||
mongoFilter := bson.M{
|
||||
"versiondetail.islatest": true,
|
||||
"version_detail.is_latest": true,
|
||||
}
|
||||
// Map common filter keys to MongoDB document paths
|
||||
for k, v := range filter {
|
||||
// Handle nested fields with dot notation
|
||||
switch k {
|
||||
case "publisher.trusted":
|
||||
mongoFilter["publisher.trusted"] = v
|
||||
case "version":
|
||||
mongoFilter["version_detail.version"] = v
|
||||
case "name":
|
||||
mongoFilter["name"] = v
|
||||
default:
|
||||
mongoFilter[k] = v
|
||||
}
|
||||
|
|
@ -108,7 +110,7 @@ func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, curs
|
|||
}
|
||||
|
||||
// Fetch the document at the cursor to get its sort values
|
||||
var cursorDoc model.Entry
|
||||
var cursorDoc model.Server
|
||||
err := db.collection.FindOne(ctx, bson.M{"id": cursor}).Decode(&cursorDoc)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
|
|
@ -138,7 +140,7 @@ func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, curs
|
|||
defer mongoCursor.Close(ctx)
|
||||
|
||||
// Decode results
|
||||
var results []*model.Entry
|
||||
var results []*model.Server
|
||||
if err = mongoCursor.All(ctx, &results); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
@ -183,8 +185,8 @@ func (db *MongoDB) Publish(ctx context.Context, serverDetail *model.ServerDetail
|
|||
}
|
||||
// find a server detail with the same name and check that the current version is greater than the existing one
|
||||
filter := bson.M{
|
||||
"name": serverDetail.Name,
|
||||
"versiondetail.islatest": true,
|
||||
"name": serverDetail.Name,
|
||||
"version_detail.is_latest": true,
|
||||
}
|
||||
|
||||
var existingEntry model.ServerDetail
|
||||
|
|
|
|||
|
|
@ -24,88 +24,105 @@ type PublishRequest struct {
|
|||
AuthStatusToken string `json:"-"` // Used internally for device flows
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Repository Repository `json:"repository,omitempty"`
|
||||
VersionDetail VersionDetail `json:"version_detail,omitempty"`
|
||||
}
|
||||
|
||||
// Repository represents a source code repository as defined in the spec
|
||||
type Repository struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
SubFolder string `json:"subfolder,omitempty"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
URL string `json:"url" bson:"url"`
|
||||
Source string `json:"source" bson:"source"`
|
||||
ID string `json:"id" bson:"id"`
|
||||
}
|
||||
|
||||
type VersionDetail struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
ReleaseDate string `json:"release_date,omitempty"` //RFC 3339 date format
|
||||
IsLatest bool `json:"is_latest,omitempty"`
|
||||
// ServerList represents the response for listing servers as defined in the spec
|
||||
type ServerList struct {
|
||||
Servers []Server `json:"servers" bson:"servers"`
|
||||
Next string `json:"next,omitempty" bson:"next,omitempty"`
|
||||
TotalCount int `json:"total_count" bson:"total_count"`
|
||||
}
|
||||
|
||||
type ServerDetail struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
VersionDetail VersionDetail `json:"version_detail,omitempty"`
|
||||
Repository Repository `json:"repository,omitempty"`
|
||||
PackageCanonical string `json:"package_canonical,omitempty"`
|
||||
Registries []Registries `json:"registries,omitempty"`
|
||||
Remotes []Remotes `json:"remotes,omitempty"`
|
||||
// create an enum for Format
|
||||
type Format string
|
||||
|
||||
const (
|
||||
FormatString Format = "string"
|
||||
FormatNumber Format = "number"
|
||||
FormatBoolean Format = "boolean"
|
||||
FormatFilePath Format = "file_path"
|
||||
)
|
||||
|
||||
// UserInput represents a user input as defined in the spec
|
||||
type Input struct {
|
||||
Description string `json:"description,omitempty" bson:"description,omitempty"`
|
||||
IsRequired bool `json:"is_required,omitempty" bson:"is_required,omitempty"`
|
||||
Format Format `json:"format,omitempty" bson:"format,omitempty"`
|
||||
Value string `json:"value,omitempty" bson:"value,omitempty"`
|
||||
IsSecret bool `json:"is_secret,omitempty" bson:"is_secret,omitempty"`
|
||||
Default string `json:"default,omitempty" bson:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty" bson:"choices,omitempty"`
|
||||
Template string `json:"template,omitempty" bson:"template,omitempty"`
|
||||
Properties map[string]Input `json:"properties,omitempty" bson:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type Registries struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
PackageName string `json:"package_name,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
CommandArguments Command `json:"command_arguments,omitempty"`
|
||||
type InputWithVariables struct {
|
||||
Input `json:",inline" bson:",inline"`
|
||||
Variables map[string]Input `json:"variables,omitempty" bson:"variables,omitempty"`
|
||||
}
|
||||
|
||||
type Remotes struct {
|
||||
TransportType string `json:"transport_type,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
type KeyValueInput struct {
|
||||
InputWithVariables `json:",inline" bson:",inline"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
}
|
||||
type ArgumentType string
|
||||
|
||||
type Command struct {
|
||||
SubCommands []SubCommand `json:"sub_commands,omitempty"`
|
||||
PositionalArguments []PositionalArgument `json:"positional_arguments,omitempty"`
|
||||
EnvironmentVariables []EnvironmentVariable `json:"environment_variables,omitempty"`
|
||||
NamedArguments []NamedArguments `json:"named_arguments,omitempty"`
|
||||
}
|
||||
|
||||
type EnvironmentVariable struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
const (
|
||||
ArgumentTypePositional ArgumentType = "positional"
|
||||
ArgumentTypeNamed ArgumentType = "named"
|
||||
)
|
||||
|
||||
// RuntimeArgument defines a type that can be either a PositionalArgument or a NamedArgument
|
||||
type Argument struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
DefaultValue string `json:"default_value,omitempty"`
|
||||
IsRequired bool `json:"is_required,omitempty"`
|
||||
IsEditable bool `json:"is_editable,omitempty"`
|
||||
IsRepeatable bool `json:"is_repeatable,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
InputWithVariables `json:",inline" bson:",inline"`
|
||||
Type ArgumentType `json:"type" bson:"type"`
|
||||
Name string `json:"name,omitempty" bson:"name,omitempty"`
|
||||
IsRepeated bool `json:"is_repeated,omitempty" bson:"is_repeated,omitempty"`
|
||||
ValueHint string `json:"value_hint,omitempty" bson:"value_hint,omitempty"`
|
||||
}
|
||||
|
||||
type PositionalArgument struct {
|
||||
Position int `json:"position,omitempty"`
|
||||
Argument Argument `json:"argument,omitempty"`
|
||||
type Package struct {
|
||||
RegistryName string `json:"registry_name" bson:"registry_name"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Version string `json:"version" bson:"version"`
|
||||
RunTimeHint string `json:"runtime_hint,omitempty" bson:"runtime_hint,omitempty"`
|
||||
RuntimeArguments []Argument `json:"runtime_arguments,omitempty" bson:"runtime_arguments,omitempty"`
|
||||
PackageArguments []Argument `json:"package_arguments,omitempty" bson:"package_arguments,omitempty"`
|
||||
EnvironmentVariables []KeyValueInput `json:"environment_variables,omitempty" bson:"environment_variables,omitempty"`
|
||||
}
|
||||
|
||||
type SubCommand struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
NamedArguments []NamedArguments `json:"named_arguments,omitempty"`
|
||||
// Remote represents a remote connection endpoint
|
||||
type Remote struct {
|
||||
TransportType string `json:"transport_type" bson:"transport_type"`
|
||||
URL string `json:"url" bson:"url"`
|
||||
Headers []Input `json:"headers,omitempty" bson:"headers,omitempty"`
|
||||
}
|
||||
type NamedArguments struct {
|
||||
ShortFlag string `json:"short_flag,omitempty"`
|
||||
LongFlag string `json:"long_flag,omitempty"`
|
||||
RequiresValue bool `json:"requires_value,omitempty"`
|
||||
Argument Argument `json:"argument,omitempty"`
|
||||
|
||||
// VersionDetail represents the version details of a server
|
||||
type VersionDetail struct {
|
||||
Version string `json:"version" bson:"version"`
|
||||
ReleaseDate string `json:"release_date" bson:"release_date"`
|
||||
IsLatest bool `json:"is_latest" bson:"is_latest"`
|
||||
}
|
||||
|
||||
// Server represents a basic server information as defined in the spec
|
||||
type Server struct {
|
||||
ID string `json:"id" bson:"id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
Repository Repository `json:"repository" bson:"repository"`
|
||||
VersionDetail VersionDetail `json:"version_detail" bson:"version_detail"`
|
||||
}
|
||||
|
||||
// ServerDetail represents detailed server information as defined in the spec
|
||||
type ServerDetail struct {
|
||||
Server `json:",inline" bson:",inline"`
|
||||
PackageCanonical string `json:"package_canonical,omitempty" bson:"package_canonical,omitempty"`
|
||||
Packages []Package `json:"packages,omitempty" bson:"packages,omitempty"`
|
||||
Remotes []Remote `json:"remotes,omitempty" bson:"remotes,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,16 +17,15 @@ type fakeRegistryService struct {
|
|||
// NewFakeRegistryService creates a new fake registry service with pre-populated data
|
||||
func NewFakeRegistryService() RegistryService {
|
||||
// Sample registry entries with updated model structure
|
||||
registries := []*model.Entry{
|
||||
registries := []*model.Server{
|
||||
{
|
||||
ID: uuid.New().String(),
|
||||
Name: "bluegreen/mcp-server",
|
||||
Description: "A dummy MCP registry for testing",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/mcp-1",
|
||||
SubFolder: "server",
|
||||
Branch: "main",
|
||||
Commit: "abc123def456",
|
||||
URL: "https://github.com/example/mcp-1",
|
||||
Source: "github",
|
||||
ID: "example/mcp-1",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
|
|
@ -39,10 +38,9 @@ func NewFakeRegistryService() RegistryService {
|
|||
Name: "orangepurple/mcp-server",
|
||||
Description: "Another dummy MCP registry for testing",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/mcp-2",
|
||||
SubFolder: "mcp",
|
||||
Branch: "develop",
|
||||
Commit: "def456ghi789",
|
||||
URL: "https://github.com/example/mcp-2",
|
||||
Source: "github",
|
||||
ID: "example/mcp-2",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "0.9.0",
|
||||
|
|
@ -55,10 +53,9 @@ func NewFakeRegistryService() RegistryService {
|
|||
Name: "greenyellow/mcp-server",
|
||||
Description: "Yet another dummy MCP registry for testing",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/mcp-3",
|
||||
SubFolder: "",
|
||||
Branch: "feature/mcp-server",
|
||||
Commit: "789jkl012mno",
|
||||
URL: "https://github.com/example/mcp-3",
|
||||
Source: "github",
|
||||
ID: "example/mcp-3",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "0.9.5",
|
||||
|
|
@ -69,7 +66,7 @@ func NewFakeRegistryService() RegistryService {
|
|||
}
|
||||
|
||||
// Create a new in-memory database
|
||||
registryMap := make(map[string]*model.Entry)
|
||||
registryMap := make(map[string]*model.Server)
|
||||
for _, entry := range registries {
|
||||
registryMap[entry.ID] = entry
|
||||
}
|
||||
|
|
@ -80,7 +77,7 @@ func NewFakeRegistryService() RegistryService {
|
|||
}
|
||||
|
||||
// List retrieves MCPRegistry entries with optional filtering and pagination
|
||||
func (s *fakeRegistryService) List(cursor string, limit int) ([]model.Entry, string, error) {
|
||||
func (s *fakeRegistryService) List(cursor string, limit int) ([]model.Server, string, error) {
|
||||
// Create a timeout context for the database operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -90,9 +87,8 @@ func (s *fakeRegistryService) List(cursor string, limit int) ([]model.Entry, str
|
|||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Convert from []*model.Entry to []model.Entry
|
||||
result := make([]model.Entry, len(entries))
|
||||
// Convert from []*model.Server to []model.Server
|
||||
result := make([]model.Server, len(entries))
|
||||
for i, entry := range entries {
|
||||
result[i] = *entry
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func NewRegistryServiceWithDB(db database.Database) RegistryService {
|
|||
}
|
||||
|
||||
// GetAll returns all registry entries
|
||||
func (s *registryServiceImpl) GetAll() ([]model.Entry, error) {
|
||||
func (s *registryServiceImpl) GetAll() ([]model.Server, error) {
|
||||
// Create a timeout context for the database operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -32,8 +32,8 @@ func (s *registryServiceImpl) GetAll() ([]model.Entry, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Convert from []*model.Entry to []model.Entry
|
||||
result := make([]model.Entry, len(entries))
|
||||
// Convert from []*model.Server to []model.Server
|
||||
result := make([]model.Server, len(entries))
|
||||
for i, entry := range entries {
|
||||
result[i] = *entry
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ func (s *registryServiceImpl) GetAll() ([]model.Entry, error) {
|
|||
}
|
||||
|
||||
// List returns registry entries with cursor-based pagination
|
||||
func (s *registryServiceImpl) List(cursor string, limit int) ([]model.Entry, string, error) {
|
||||
func (s *registryServiceImpl) List(cursor string, limit int) ([]model.Server, string, error) {
|
||||
// Create a timeout context for the database operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -58,8 +58,8 @@ func (s *registryServiceImpl) List(cursor string, limit int) ([]model.Entry, str
|
|||
return nil, "", err
|
||||
}
|
||||
|
||||
// Convert from []*model.MCPRegistry to []model.MCPRegistry
|
||||
result := make([]model.Entry, len(entries))
|
||||
// Convert from []*model.Server to []model.Server
|
||||
result := make([]model.Server, len(entries))
|
||||
for i, entry := range entries {
|
||||
result[i] = *entry
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "github.com/modelcontextprotocol/registry/internal/model"
|
|||
|
||||
// RegistryService defines the interface for registry operations
|
||||
type RegistryService interface {
|
||||
List(cursor string, limit int) ([]model.Entry, string, error)
|
||||
List(cursor string, limit int) ([]model.Server, string, error)
|
||||
GetByID(id string) (*model.ServerDetail, error)
|
||||
Publish(serverDetail *model.ServerDetail) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,58 @@
|
|||
{
|
||||
"name": "io.github.<username>/<repository>",
|
||||
"description": "<description>",
|
||||
"version_detail": {
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"registries": [
|
||||
{
|
||||
"name": "npm",
|
||||
"package_name": "<npm_package_name>",
|
||||
"license": "MIT",
|
||||
"command_arguments": {
|
||||
"sub_commands": [
|
||||
{
|
||||
"name": "start",
|
||||
"description": "Start the server",
|
||||
"named_arguments": null
|
||||
}
|
||||
],
|
||||
"positional_arguments": null,
|
||||
"environment_variables": [
|
||||
{
|
||||
"name": "PORT",
|
||||
"description": "Port to run the server on",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"named_arguments": null
|
||||
}
|
||||
"description": "<your description here>",
|
||||
"name": "io.github.<owner>/<server-name>",
|
||||
"package_canonical": "<package_registry",
|
||||
"packages": [
|
||||
{
|
||||
"registry_name": "npm",
|
||||
"name": "@<owner>/<server-name>",
|
||||
"version": "0.2.23",
|
||||
"package_arguments": [
|
||||
{
|
||||
"description": "Specify services and permissions.",
|
||||
"is_required": true,
|
||||
"format": "string",
|
||||
"value": "-s",
|
||||
"default": "-s",
|
||||
"type": "positional",
|
||||
"value_hint": "-s"
|
||||
}
|
||||
],
|
||||
"environment_variables": [
|
||||
{
|
||||
"description": "API Key to access the server",
|
||||
"name": "API_KEY"
|
||||
}
|
||||
]
|
||||
},{
|
||||
"registry_name": "docker>",
|
||||
"name": "@<owner>/<server-name>-cli",
|
||||
"version": "0.123.223",
|
||||
"runtime_hint": "docker",
|
||||
"runtime_arguments": [
|
||||
{
|
||||
"description": "Specify services and permissions.",
|
||||
"is_required": true,
|
||||
"format": "string",
|
||||
"value": "--mount",
|
||||
"default": "--mount",
|
||||
"type": "positional",
|
||||
"value_hint": "--mount"
|
||||
}
|
||||
],
|
||||
"environment_variables": [
|
||||
{
|
||||
"description": "API Key to access the server",
|
||||
"name": "API_KEY"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"url": "https://github.com//<owner>/<server-name>",
|
||||
"source": "github"
|
||||
},
|
||||
"version_detail": {
|
||||
"version": "0.0.1-<publisher_version>"
|
||||
}
|
||||
],
|
||||
"remotes": [
|
||||
{
|
||||
"transporttype": "http",
|
||||
"url": "http://example.com/api"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue