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
|
// Response is a paginated API response
|
||||||
type PaginatedResponse struct {
|
type PaginatedResponse struct {
|
||||||
Data []model.Entry `json:"servers"`
|
Data []model.Server `json:"servers"`
|
||||||
Metadata Metadata `json:"metadata,omitempty"`
|
Metadata Metadata `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata contains pagination metadata
|
// Metadata contains pagination metadata
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ var (
|
||||||
// Database defines the interface for database operations on MCPRegistry entries
|
// Database defines the interface for database operations on MCPRegistry entries
|
||||||
type Database interface {
|
type Database interface {
|
||||||
// List retrieves all MCPRegistry entries with optional filtering
|
// 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 retrieves a single ServerDetail by it's ID
|
||||||
GetByID(ctx context.Context, id string) (*model.ServerDetail, error)
|
GetByID(ctx context.Context, id string) (*model.ServerDetail, error)
|
||||||
// Publish adds a new ServerDetail to the database
|
// 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)
|
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))
|
log.Printf("Found %d server entries in seed file", len(servers))
|
||||||
return servers, nil
|
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
|
// importData imports the seed data into MongoDB
|
||||||
func importData(ctx context.Context, collection *mongo.Collection, servers []model.ServerDetail) {
|
func importData(ctx context.Context, collection *mongo.Collection, servers []model.ServerDetail) {
|
||||||
log.Printf("Importing %d servers into collection %s", len(servers), collection.Name())
|
log.Printf("Importing %d servers into collection %s", len(servers), collection.Name())
|
||||||
|
|
||||||
for i, server := range servers {
|
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
|
// Create filter based on server ID
|
||||||
filter := bson.M{"id": server.ID}
|
filter := bson.M{"id": server.ID}
|
||||||
|
|
||||||
if server.VersionDetail.Version == "" {
|
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.ReleaseDate = time.Now().Format(time.RFC3339)
|
||||||
server.VersionDetail.IsLatest = true
|
server.VersionDetail.IsLatest = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,19 @@ import (
|
||||||
|
|
||||||
// MemoryDB is an in-memory implementation of the Database interface
|
// MemoryDB is an in-memory implementation of the Database interface
|
||||||
type MemoryDB struct {
|
type MemoryDB struct {
|
||||||
entries map[string]*model.Entry
|
entries map[string]*model.Server
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemoryDB creates a new instance of the in-memory database
|
// 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{
|
return &MemoryDB{
|
||||||
entries: e,
|
entries: e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves all MCPRegistry entries with optional filtering and pagination
|
// 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 {
|
if ctx.Err() != nil {
|
||||||
return nil, "", ctx.Err()
|
return nil, "", ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
@ -35,14 +35,14 @@ func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cur
|
||||||
defer db.mu.RUnlock()
|
defer db.mu.RUnlock()
|
||||||
|
|
||||||
// Convert all entries to a slice for pagination
|
// Convert all entries to a slice for pagination
|
||||||
var allEntries []*model.Entry
|
var allEntries []*model.Server
|
||||||
for _, entry := range db.entries {
|
for _, entry := range db.entries {
|
||||||
entryCopy := *entry
|
entryCopy := *entry
|
||||||
allEntries = append(allEntries, &entryCopy)
|
allEntries = append(allEntries, &entryCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple filtering implementation
|
// Simple filtering implementation
|
||||||
var filteredEntries []*model.Entry
|
var filteredEntries []*model.Server
|
||||||
for _, entry := range allEntries {
|
for _, entry := range allEntries {
|
||||||
include := true
|
include := true
|
||||||
|
|
||||||
|
|
@ -98,11 +98,11 @@ func (db *MemoryDB) List(ctx context.Context, filter map[string]interface{}, cur
|
||||||
endIdx = len(filteredEntries)
|
endIdx = len(filteredEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []*model.Entry
|
var result []*model.Server
|
||||||
if startIdx < len(filteredEntries) {
|
if startIdx < len(filteredEntries) {
|
||||||
result = filteredEntries[startIdx:endIdx]
|
result = filteredEntries[startIdx:endIdx]
|
||||||
} else {
|
} else {
|
||||||
result = []*model.Entry{}
|
result = []*model.Server{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine next cursor
|
// 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 {
|
if entry, exists := db.entries[id]; exists {
|
||||||
return &model.ServerDetail{
|
return &model.ServerDetail{
|
||||||
ID: entry.ID,
|
Server: model.Server{
|
||||||
Name: entry.Name,
|
ID: entry.ID,
|
||||||
Description: entry.Description,
|
Name: entry.Name,
|
||||||
VersionDetail: entry.VersionDetail,
|
Description: entry.Description,
|
||||||
Repository: entry.Repository,
|
VersionDetail: entry.VersionDetail,
|
||||||
|
Repository: entry.Repository,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +164,7 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail *model.ServerDetai
|
||||||
return ErrInvalidInput
|
return ErrInvalidInput
|
||||||
}
|
}
|
||||||
|
|
||||||
db.entries[serverDetail.ID] = &model.Entry{
|
db.entries[serverDetail.ID] = &model.Server{
|
||||||
ID: serverDetail.ID,
|
ID: serverDetail.ID,
|
||||||
Name: serverDetail.Name,
|
Name: serverDetail.Name,
|
||||||
Description: serverDetail.Description,
|
Description: serverDetail.Description,
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ func NewMongoDB(ctx context.Context, connectionURI, databaseName, collectionName
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves MCPRegistry entries with optional filtering and pagination
|
// 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 {
|
if limit <= 0 {
|
||||||
// Set default limit if not provided
|
// Set default limit if not provided
|
||||||
limit = 10
|
limit = 10
|
||||||
|
|
@ -84,14 +84,16 @@ func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, curs
|
||||||
|
|
||||||
// Convert Go map to MongoDB filter
|
// Convert Go map to MongoDB filter
|
||||||
mongoFilter := bson.M{
|
mongoFilter := bson.M{
|
||||||
"versiondetail.islatest": true,
|
"version_detail.is_latest": true,
|
||||||
}
|
}
|
||||||
// Map common filter keys to MongoDB document paths
|
// Map common filter keys to MongoDB document paths
|
||||||
for k, v := range filter {
|
for k, v := range filter {
|
||||||
// Handle nested fields with dot notation
|
// Handle nested fields with dot notation
|
||||||
switch k {
|
switch k {
|
||||||
case "publisher.trusted":
|
case "version":
|
||||||
mongoFilter["publisher.trusted"] = v
|
mongoFilter["version_detail.version"] = v
|
||||||
|
case "name":
|
||||||
|
mongoFilter["name"] = v
|
||||||
default:
|
default:
|
||||||
mongoFilter[k] = v
|
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
|
// 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)
|
err := db.collection.FindOne(ctx, bson.M{"id": cursor}).Decode(&cursorDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == mongo.ErrNoDocuments {
|
if err == mongo.ErrNoDocuments {
|
||||||
|
|
@ -138,7 +140,7 @@ func (db *MongoDB) List(ctx context.Context, filter map[string]interface{}, curs
|
||||||
defer mongoCursor.Close(ctx)
|
defer mongoCursor.Close(ctx)
|
||||||
|
|
||||||
// Decode results
|
// Decode results
|
||||||
var results []*model.Entry
|
var results []*model.Server
|
||||||
if err = mongoCursor.All(ctx, &results); err != nil {
|
if err = mongoCursor.All(ctx, &results); err != nil {
|
||||||
return nil, "", err
|
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
|
// find a server detail with the same name and check that the current version is greater than the existing one
|
||||||
filter := bson.M{
|
filter := bson.M{
|
||||||
"name": serverDetail.Name,
|
"name": serverDetail.Name,
|
||||||
"versiondetail.islatest": true,
|
"version_detail.is_latest": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingEntry model.ServerDetail
|
var existingEntry model.ServerDetail
|
||||||
|
|
|
||||||
|
|
@ -24,88 +24,105 @@ type PublishRequest struct {
|
||||||
AuthStatusToken string `json:"-"` // Used internally for device flows
|
AuthStatusToken string `json:"-"` // Used internally for device flows
|
||||||
}
|
}
|
||||||
|
|
||||||
type Entry struct {
|
// Repository represents a source code repository as defined in the spec
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
ID string `json:"id,omitempty"`
|
URL string `json:"url" bson:"url"`
|
||||||
URL string `json:"url,omitempty"`
|
Source string `json:"source" bson:"source"`
|
||||||
Source string `json:"source,omitempty"`
|
ID string `json:"id" bson:"id"`
|
||||||
SubFolder string `json:"subfolder,omitempty"`
|
|
||||||
Branch string `json:"branch,omitempty"`
|
|
||||||
Commit string `json:"commit,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VersionDetail struct {
|
// ServerList represents the response for listing servers as defined in the spec
|
||||||
Version string `json:"version,omitempty"`
|
type ServerList struct {
|
||||||
ReleaseDate string `json:"release_date,omitempty"` //RFC 3339 date format
|
Servers []Server `json:"servers" bson:"servers"`
|
||||||
IsLatest bool `json:"is_latest,omitempty"`
|
Next string `json:"next,omitempty" bson:"next,omitempty"`
|
||||||
|
TotalCount int `json:"total_count" bson:"total_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerDetail struct {
|
// create an enum for Format
|
||||||
ID string `json:"id,omitempty"`
|
type Format string
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Description string `json:"description,omitempty"`
|
const (
|
||||||
VersionDetail VersionDetail `json:"version_detail,omitempty"`
|
FormatString Format = "string"
|
||||||
Repository Repository `json:"repository,omitempty"`
|
FormatNumber Format = "number"
|
||||||
PackageCanonical string `json:"package_canonical,omitempty"`
|
FormatBoolean Format = "boolean"
|
||||||
Registries []Registries `json:"registries,omitempty"`
|
FormatFilePath Format = "file_path"
|
||||||
Remotes []Remotes `json:"remotes,omitempty"`
|
)
|
||||||
|
|
||||||
|
// 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 {
|
type InputWithVariables struct {
|
||||||
Name string `json:"name,omitempty"`
|
Input `json:",inline" bson:",inline"`
|
||||||
PackageName string `json:"package_name,omitempty"`
|
Variables map[string]Input `json:"variables,omitempty" bson:"variables,omitempty"`
|
||||||
License string `json:"license,omitempty"`
|
|
||||||
CommandArguments Command `json:"command_arguments,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Remotes struct {
|
type KeyValueInput struct {
|
||||||
TransportType string `json:"transport_type,omitempty"`
|
InputWithVariables `json:",inline" bson:",inline"`
|
||||||
Url string `json:"url,omitempty"`
|
Name string `json:"name" bson:"name"`
|
||||||
}
|
}
|
||||||
|
type ArgumentType string
|
||||||
|
|
||||||
type Command struct {
|
const (
|
||||||
SubCommands []SubCommand `json:"sub_commands,omitempty"`
|
ArgumentTypePositional ArgumentType = "positional"
|
||||||
PositionalArguments []PositionalArgument `json:"positional_arguments,omitempty"`
|
ArgumentTypeNamed ArgumentType = "named"
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// RuntimeArgument defines a type that can be either a PositionalArgument or a NamedArgument
|
||||||
type Argument struct {
|
type Argument struct {
|
||||||
Name string `json:"name,omitempty"`
|
InputWithVariables `json:",inline" bson:",inline"`
|
||||||
Description string `json:"description,omitempty"`
|
Type ArgumentType `json:"type" bson:"type"`
|
||||||
DefaultValue string `json:"default_value,omitempty"`
|
Name string `json:"name,omitempty" bson:"name,omitempty"`
|
||||||
IsRequired bool `json:"is_required,omitempty"`
|
IsRepeated bool `json:"is_repeated,omitempty" bson:"is_repeated,omitempty"`
|
||||||
IsEditable bool `json:"is_editable,omitempty"`
|
ValueHint string `json:"value_hint,omitempty" bson:"value_hint,omitempty"`
|
||||||
IsRepeatable bool `json:"is_repeatable,omitempty"`
|
|
||||||
Choices []string `json:"choices,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PositionalArgument struct {
|
type Package struct {
|
||||||
Position int `json:"position,omitempty"`
|
RegistryName string `json:"registry_name" bson:"registry_name"`
|
||||||
Argument Argument `json:"argument,omitempty"`
|
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 {
|
// Remote represents a remote connection endpoint
|
||||||
Name string `json:"name,omitempty"`
|
type Remote struct {
|
||||||
Description string `json:"description,omitempty"`
|
TransportType string `json:"transport_type" bson:"transport_type"`
|
||||||
NamedArguments []NamedArguments `json:"named_arguments,omitempty"`
|
URL string `json:"url" bson:"url"`
|
||||||
|
Headers []Input `json:"headers,omitempty" bson:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
type NamedArguments struct {
|
|
||||||
ShortFlag string `json:"short_flag,omitempty"`
|
// VersionDetail represents the version details of a server
|
||||||
LongFlag string `json:"long_flag,omitempty"`
|
type VersionDetail struct {
|
||||||
RequiresValue bool `json:"requires_value,omitempty"`
|
Version string `json:"version" bson:"version"`
|
||||||
Argument Argument `json:"argument,omitempty"`
|
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
|
// NewFakeRegistryService creates a new fake registry service with pre-populated data
|
||||||
func NewFakeRegistryService() RegistryService {
|
func NewFakeRegistryService() RegistryService {
|
||||||
// Sample registry entries with updated model structure
|
// Sample registry entries with updated model structure
|
||||||
registries := []*model.Entry{
|
registries := []*model.Server{
|
||||||
{
|
{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Name: "bluegreen/mcp-server",
|
Name: "bluegreen/mcp-server",
|
||||||
Description: "A dummy MCP registry for testing",
|
Description: "A dummy MCP registry for testing",
|
||||||
Repository: model.Repository{
|
Repository: model.Repository{
|
||||||
URL: "https://github.com/example/mcp-1",
|
URL: "https://github.com/example/mcp-1",
|
||||||
SubFolder: "server",
|
Source: "github",
|
||||||
Branch: "main",
|
ID: "example/mcp-1",
|
||||||
Commit: "abc123def456",
|
|
||||||
},
|
},
|
||||||
VersionDetail: model.VersionDetail{
|
VersionDetail: model.VersionDetail{
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
|
|
@ -39,10 +38,9 @@ func NewFakeRegistryService() RegistryService {
|
||||||
Name: "orangepurple/mcp-server",
|
Name: "orangepurple/mcp-server",
|
||||||
Description: "Another dummy MCP registry for testing",
|
Description: "Another dummy MCP registry for testing",
|
||||||
Repository: model.Repository{
|
Repository: model.Repository{
|
||||||
URL: "https://github.com/example/mcp-2",
|
URL: "https://github.com/example/mcp-2",
|
||||||
SubFolder: "mcp",
|
Source: "github",
|
||||||
Branch: "develop",
|
ID: "example/mcp-2",
|
||||||
Commit: "def456ghi789",
|
|
||||||
},
|
},
|
||||||
VersionDetail: model.VersionDetail{
|
VersionDetail: model.VersionDetail{
|
||||||
Version: "0.9.0",
|
Version: "0.9.0",
|
||||||
|
|
@ -55,10 +53,9 @@ func NewFakeRegistryService() RegistryService {
|
||||||
Name: "greenyellow/mcp-server",
|
Name: "greenyellow/mcp-server",
|
||||||
Description: "Yet another dummy MCP registry for testing",
|
Description: "Yet another dummy MCP registry for testing",
|
||||||
Repository: model.Repository{
|
Repository: model.Repository{
|
||||||
URL: "https://github.com/example/mcp-3",
|
URL: "https://github.com/example/mcp-3",
|
||||||
SubFolder: "",
|
Source: "github",
|
||||||
Branch: "feature/mcp-server",
|
ID: "example/mcp-3",
|
||||||
Commit: "789jkl012mno",
|
|
||||||
},
|
},
|
||||||
VersionDetail: model.VersionDetail{
|
VersionDetail: model.VersionDetail{
|
||||||
Version: "0.9.5",
|
Version: "0.9.5",
|
||||||
|
|
@ -69,7 +66,7 @@ func NewFakeRegistryService() RegistryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new in-memory database
|
// Create a new in-memory database
|
||||||
registryMap := make(map[string]*model.Entry)
|
registryMap := make(map[string]*model.Server)
|
||||||
for _, entry := range registries {
|
for _, entry := range registries {
|
||||||
registryMap[entry.ID] = entry
|
registryMap[entry.ID] = entry
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +77,7 @@ func NewFakeRegistryService() RegistryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves MCPRegistry entries with optional filtering and pagination
|
// 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
|
// Create a timeout context for the database operation
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -90,9 +87,8 @@ func (s *fakeRegistryService) List(cursor string, limit int) ([]model.Entry, str
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
// Convert from []*model.Server to []model.Server
|
||||||
// Convert from []*model.Entry to []model.Entry
|
result := make([]model.Server, len(entries))
|
||||||
result := make([]model.Entry, len(entries))
|
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
result[i] = *entry
|
result[i] = *entry
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func NewRegistryServiceWithDB(db database.Database) RegistryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns all registry entries
|
// 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
|
// Create a timeout context for the database operation
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -32,8 +32,8 @@ func (s *registryServiceImpl) GetAll() ([]model.Entry, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert from []*model.Entry to []model.Entry
|
// Convert from []*model.Server to []model.Server
|
||||||
result := make([]model.Entry, len(entries))
|
result := make([]model.Server, len(entries))
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
result[i] = *entry
|
result[i] = *entry
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ func (s *registryServiceImpl) GetAll() ([]model.Entry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns registry entries with cursor-based pagination
|
// 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
|
// Create a timeout context for the database operation
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -58,8 +58,8 @@ func (s *registryServiceImpl) List(cursor string, limit int) ([]model.Entry, str
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert from []*model.MCPRegistry to []model.MCPRegistry
|
// Convert from []*model.Server to []model.Server
|
||||||
result := make([]model.Entry, len(entries))
|
result := make([]model.Server, len(entries))
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
result[i] = *entry
|
result[i] = *entry
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import "github.com/modelcontextprotocol/registry/internal/model"
|
||||||
|
|
||||||
// RegistryService defines the interface for registry operations
|
// RegistryService defines the interface for registry operations
|
||||||
type RegistryService interface {
|
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)
|
GetByID(id string) (*model.ServerDetail, error)
|
||||||
Publish(serverDetail *model.ServerDetail) error
|
Publish(serverDetail *model.ServerDetail) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,58 @@
|
||||||
{
|
{
|
||||||
"name": "io.github.<username>/<repository>",
|
"description": "<your description here>",
|
||||||
"description": "<description>",
|
"name": "io.github.<owner>/<server-name>",
|
||||||
"version_detail": {
|
"package_canonical": "<package_registry",
|
||||||
"version": "1.0.2"
|
"packages": [
|
||||||
},
|
{
|
||||||
"registries": [
|
"registry_name": "npm",
|
||||||
{
|
"name": "@<owner>/<server-name>",
|
||||||
"name": "npm",
|
"version": "0.2.23",
|
||||||
"package_name": "<npm_package_name>",
|
"package_arguments": [
|
||||||
"license": "MIT",
|
{
|
||||||
"command_arguments": {
|
"description": "Specify services and permissions.",
|
||||||
"sub_commands": [
|
"is_required": true,
|
||||||
{
|
"format": "string",
|
||||||
"name": "start",
|
"value": "-s",
|
||||||
"description": "Start the server",
|
"default": "-s",
|
||||||
"named_arguments": null
|
"type": "positional",
|
||||||
}
|
"value_hint": "-s"
|
||||||
],
|
}
|
||||||
"positional_arguments": null,
|
],
|
||||||
"environment_variables": [
|
"environment_variables": [
|
||||||
{
|
{
|
||||||
"name": "PORT",
|
"description": "API Key to access the server",
|
||||||
"description": "Port to run the server on",
|
"name": "API_KEY"
|
||||||
"required": false
|
}
|
||||||
}
|
]
|
||||||
],
|
},{
|
||||||
"named_arguments": null
|
"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