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
6 changed files with 1702 additions and 2 deletions
15
internal/api/handlers/v0/health.go
Normal file
15
internal/api/handlers/v0/health.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Package v0 contains API handlers for version 0 of the API
|
||||
package v0
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HealthHandler returns a handler for health check endpoint
|
||||
func HealthHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
}
|
||||
27
internal/api/handlers/v0/ping.go
Normal file
27
internal/api/handlers/v0/ping.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Package v0 contains API handlers for version 0 of the API
|
||||
package v0
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
)
|
||||
|
||||
// PingHandler returns a handler for the ping endpoint that returns build version
|
||||
func PingHandler(cfg *config.Config) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]string{
|
||||
"status": "ok",
|
||||
"version": cfg.Version,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
125
internal/api/handlers/v0/servers.go
Normal file
125
internal/api/handlers/v0/servers.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Package v0 contains API handlers for version 0 of the API
|
||||
package v0
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/modelcontextprotocol/registry/internal/model"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
)
|
||||
|
||||
// Response is a paginated API response
|
||||
type PaginatedResponse struct {
|
||||
Data []model.Entry `json:"servers"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Metadata contains pagination metadata
|
||||
type Metadata struct {
|
||||
NextCursor string `json:"next_cursor,omitempty"`
|
||||
}
|
||||
|
||||
// ServersHandler returns a handler for listing registry items
|
||||
func ServersHandler(registry service.RegistryService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse cursor and limit from query parameters
|
||||
cursor := r.URL.Query().Get("cursor")
|
||||
if cursor != "" {
|
||||
_, err := uuid.Parse(cursor)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid cursor parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
|
||||
// Default limit if not specified
|
||||
limit := 30
|
||||
|
||||
// Try to parse limit from query param
|
||||
if limitStr != "" {
|
||||
parsedLimit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid limit parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if limit is within reasonable bounds
|
||||
if parsedLimit <= 0 {
|
||||
http.Error(w, "Limit must be greater than 0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if parsedLimit > 100 {
|
||||
// Cap maximum limit to prevent excessive queries
|
||||
limit = 100
|
||||
} else {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
// Use the GetAll method to get paginated results
|
||||
registries, nextCursor, err := registry.List(cursor, limit)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Create paginated response
|
||||
response := PaginatedResponse{
|
||||
Data: registries,
|
||||
}
|
||||
|
||||
// Add metadata if there's a next cursor
|
||||
if nextCursor != "" {
|
||||
response.Metadata = Metadata{
|
||||
NextCursor: nextCursor,
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
|
||||
// ServersDetailHandler returns a handler for getting details of a specific server by ID
|
||||
func ServersDetailHandler(registry service.RegistryService) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the server ID from the URL path
|
||||
id := r.PathValue("id")
|
||||
|
||||
// Validate that the ID is a valid UUID
|
||||
_, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid server ID format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the server details from the registry service
|
||||
serverDetail, err := registry.GetByID(id)
|
||||
if err != nil {
|
||||
if err.Error() == "record not found" {
|
||||
http.Error(w, "Server not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Error retrieving server details", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(serverDetail)
|
||||
}
|
||||
}
|
||||
19
internal/api/router/router.go
Normal file
19
internal/api/router/router.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Package router contains API routing logic
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
)
|
||||
|
||||
// New creates a new router with all API versions registered
|
||||
func New(cfg *config.Config, registry service.RegistryService) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Register routes for all API versions
|
||||
RegisterV0Routes(mux, cfg, registry)
|
||||
|
||||
return mux
|
||||
}
|
||||
19
internal/api/router/v0.go
Normal file
19
internal/api/router/v0.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Package router contains API routing logic
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
)
|
||||
|
||||
// RegisterV0Routes registers all v0 API routes to the provided router
|
||||
func RegisterV0Routes(mux *http.ServeMux, cfg *config.Config, registry service.RegistryService) {
|
||||
// Register v0 endpoints
|
||||
mux.HandleFunc("/v0/health", v0.HealthHandler())
|
||||
mux.HandleFunc("/v0/servers", v0.ServersHandler(registry))
|
||||
mux.HandleFunc("/v0/servers/{id}", v0.ServersDetailHandler(registry))
|
||||
mux.HandleFunc("/v0/ping", v0.PingHandler(cfg))
|
||||
}
|
||||
48
internal/api/server.go
Normal file
48
internal/api/server.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/internal/api/router"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
)
|
||||
|
||||
// Server represents the HTTP server
|
||||
type Server struct {
|
||||
config *config.Config
|
||||
registry service.RegistryService
|
||||
router *http.ServeMux
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// NewServer creates a new HTTP server
|
||||
func NewServer(cfg *config.Config, registryService service.RegistryService) *Server {
|
||||
// Create router with all API versions registered
|
||||
mux := router.New(cfg, registryService)
|
||||
|
||||
server := &Server{
|
||||
config: cfg,
|
||||
registry: registryService,
|
||||
router: mux,
|
||||
server: &http.Server{
|
||||
Addr: cfg.ServerAddress,
|
||||
Handler: mux,
|
||||
},
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// Start begins listening for incoming HTTP requests
|
||||
func (s *Server) Start() error {
|
||||
log.Printf("HTTP server starting on %s", s.config.ServerAddress)
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
Loading…
Reference in a new issue