// Package api implements the REST HTTP endpoints served alongside the MCP server. package api import ( "encoding/json" "io/fs" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/local-mcp/local-mcp-go/internal/events" "github.com/local-mcp/local-mcp-go/internal/store" ) // Stores groups all database stores that the API handlers need. type Stores struct { Instructions *store.InstructionStore Settings *store.SettingsStore Agents *store.AgentStore } // NewRouter builds and returns the main chi router. // staticFS must serve the embedded static directory; pass nil to skip. func NewRouter(stores Stores, broker *events.Broker, apiToken string, staticFS fs.FS) http.Handler { r := chi.NewRouter() r.Use(middleware.RealIP) r.Use(middleware.Recoverer) // Auth-check endpoint — always public r.Get("/auth-check", func(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "auth_required": apiToken != "", }) }) // Health — always public r.Get("/healthz", handleHealth()) // Static files — always public if staticFS != nil { r.Get("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, staticFS, "index.html") }) r.Handle("/static/*", http.StripPrefix("/static/", http.FileServerFS(staticFS))) } // All /api/* routes are protected when apiToken is set r.Group(func(r chi.Router) { if apiToken != "" { r.Use(bearerAuthMiddleware(apiToken)) } r.Get("/api/status", handleStatus(stores)) r.Get("/api/instructions", handleListInstructions(stores)) r.Post("/api/instructions", handleCreateInstruction(stores, broker)) r.Patch("/api/instructions/{id}", handleUpdateInstruction(stores, broker)) r.Delete("/api/instructions/consumed", handleClearConsumed(stores, broker)) r.Delete("/api/instructions/{id}", handleDeleteInstruction(stores, broker)) r.Get("/api/config", handleGetConfig(stores)) r.Patch("/api/config", handleUpdateConfig(stores, broker)) r.Get("/api/events", handleSSE(broker)) }) return r } // writeJSON serialises v as JSON with the given status code. func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // writeError writes a JSON {"detail": msg} error response. func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, map[string]string{"detail": msg}) } // serverStartTime records when this process started, used by /api/status. var serverStartTime = time.Now().UTC()