Init world. Basic registry API server
Work in progress . This commit adds the core registry service implementation with methods to: - Retrieve all registry entries - List entries with cursor-based pagination - Fetch specific server details by ID
This commit is contained in:
parent
2e8c761edf
commit
4fb5bc477e
1 changed files with 1702 additions and 2 deletions
189
internal/database/mongo.go
Normal file
189
internal/database/mongo.go
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/modelcontextprotocol/registry/internal/model"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// MongoDB is an implementation of the Database interface using MongoDB
|
||||
type MongoDB struct {
|
||||
client *mongo.Client
|
||||
database *mongo.Database
|
||||
collection *mongo.Collection
|
||||
}
|
||||
|
||||
// NewMongoDB creates a new instance of the MongoDB database
|
||||
func NewMongoDB(ctx context.Context, connectionURI, databaseName, collectionName string) (*MongoDB, error) {
|
||||
// Set client options and connect to MongoDB
|
||||
clientOptions := options.Client().ApplyURI(connectionURI)
|
||||
client, err := mongo.Connect(ctx, clientOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ping the MongoDB server to verify the connection
|
||||
if err = client.Ping(ctx, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get database and collection
|
||||
database := client.Database(databaseName)
|
||||
collection := database.Collection(collectionName)
|
||||
|
||||
// Create indexes for better query performance
|
||||
models := []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{{Key: "name", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "id", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = collection.Indexes().CreateMany(ctx, models)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MongoDB{
|
||||
client: client,
|
||||
database: database,
|
||||
collection: collection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if limit <= 0 {
|
||||
// Set default limit if not provided
|
||||
limit = 10
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return nil, "", ctx.Err()
|
||||
}
|
||||
|
||||
// Convert Go map to MongoDB filter
|
||||
mongoFilter := bson.M{}
|
||||
// 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
|
||||
default:
|
||||
mongoFilter[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Setup pagination options
|
||||
findOptions := options.Find()
|
||||
|
||||
// If cursor is provided, add condition to filter to only get records after the cursor
|
||||
if cursor != "" {
|
||||
// Validate that the cursor is a valid UUID
|
||||
if _, err := uuid.Parse(cursor); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid cursor format: %w", err)
|
||||
}
|
||||
|
||||
// Fetch the document at the cursor to get its sort values
|
||||
var cursorDoc model.Entry
|
||||
err := db.collection.FindOne(ctx, bson.M{"id": cursor}).Decode(&cursorDoc)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
// If cursor document not found, start from beginning
|
||||
} else {
|
||||
return nil, "", err
|
||||
}
|
||||
} else {
|
||||
// Use the cursor document's ID to paginate (records with ID > cursor's ID)
|
||||
mongoFilter["id"] = bson.M{"$gt": cursor}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sort order by ID (for consistent pagination)
|
||||
findOptions.SetSort(bson.M{"id": 1})
|
||||
|
||||
// Set limit if provided and valid
|
||||
if limit > 0 {
|
||||
findOptions.SetLimit(int64(limit))
|
||||
}
|
||||
|
||||
// Execute find operation with options
|
||||
mongoCursor, err := db.collection.Find(ctx, mongoFilter, findOptions)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer mongoCursor.Close(ctx)
|
||||
|
||||
// Decode results
|
||||
var results []*model.Entry
|
||||
if err = mongoCursor.All(ctx, &results); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Determine the next cursor
|
||||
nextCursor := ""
|
||||
if len(results) > 0 && limit > 0 && len(results) >= limit {
|
||||
// Use the last item's ID as the next cursor
|
||||
nextCursor = results[len(results)-1].ID
|
||||
}
|
||||
|
||||
return results, nextCursor, nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a single ServerDetail by its ID
|
||||
func (db *MongoDB) GetByID(ctx context.Context, id string) (*model.ServerDetail, error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
// Create a filter for the ID
|
||||
filter := bson.M{"id": id}
|
||||
|
||||
// Find the entry in the database
|
||||
var entry model.ServerDetail
|
||||
err := db.collection.FindOne(ctx, filter).Decode(&entry)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("error retrieving entry: %w", err)
|
||||
}
|
||||
|
||||
// Create and return a ServerDetail from the entry data
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (db *MongoDB) Close() error {
|
||||
return db.client.Disconnect(context.Background())
|
||||
}
|
||||
|
||||
// Connection returns information about the database connection
|
||||
func (db *MongoDB) Connection() *ConnectionInfo {
|
||||
isConnected := false
|
||||
// Check if the client is connected
|
||||
if db.client != nil {
|
||||
// A quick ping with 1 second timeout to verify connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
err := db.client.Ping(ctx, nil)
|
||||
isConnected = (err == nil)
|
||||
}
|
||||
|
||||
return &ConnectionInfo{
|
||||
Type: ConnectionTypeMongoDB,
|
||||
IsConnected: isConnected,
|
||||
Raw: db.client,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue