diff --git a/internal/api/router/router.go b/internal/api/router/router.go index 65f2c19..c592c52 100644 --- a/internal/api/router/router.go +++ b/internal/api/router/router.go @@ -2,6 +2,8 @@ package router import ( + "encoding/json" + "fmt" "net/http" "strings" "time" @@ -91,6 +93,40 @@ func WithSkipPaths(paths ...string) MiddlewareOption { } } +// handle404 returns a helpful 404 error with suggestions for common mistakes +func handle404(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/problem+json") + w.WriteHeader(http.StatusNotFound) + + path := r.URL.Path + detail := "Endpoint not found. See /docs for the API documentation." + + // Provide suggestions for common API endpoint mistakes + if !strings.HasPrefix(path, "/v0/") { + detail = fmt.Sprintf( + "Endpoint not found. Did you mean '%s'? See /docs for the API documentation.", + "/v0"+path, + ) + } + + errorBody := map[string]interface{}{ + "title": "Not Found", + "status": 404, + "detail": detail, + } + + // Use JSON marshal to ensure consistent formatting + jsonData, err := json.Marshal(errorBody) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + _, err = w.Write(jsonData) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} + // NewHumaAPI creates a new Huma API with all routes registered func NewHumaAPI(cfg *config.Config, registry service.RegistryService, mux *http.ServeMux, metrics *telemetry.Metrics) huma.API { // Create Huma API configuration @@ -113,11 +149,15 @@ func NewHumaAPI(cfg *config.Config, registry service.RegistryService, mux *http. // Add /metrics for Prometheus metrics using promhttp mux.Handle("/metrics", metrics.PrometheusHandler()) - // Add redirect from / to docs + // Add redirect from / to docs and 404 handler for all other routes mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.Redirect(w, r, "https://github.com/modelcontextprotocol/registry/tree/main/docs", http.StatusTemporaryRedirect) + return } + + // Handle 404 for all other routes + handle404(w, r) }) return api