package http import ( "encoding/json" "log/slog" "net/http" "github.com/google/uuid" "github.com/nextlevelbuilder/goclaw/internal/i18n" "github.com/nextlevelbuilder/goclaw/internal/store" ) func (h *MCPHandler) handleGrantAgent(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) serverID, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "server")}) return } var req struct { AgentID string `json:"agent_id"` ToolAllow json.RawMessage `json:"tool_allow,omitempty"` ToolDeny json.RawMessage `json:"tool_deny,omitempty"` } if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)}) return } agentID, err := uuid.Parse(req.AgentID) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")}) return } grant := store.MCPAgentGrant{ ServerID: serverID, AgentID: agentID, Enabled: true, ToolAllow: req.ToolAllow, ToolDeny: req.ToolDeny, GrantedBy: store.UserIDFromContext(r.Context()), } if err := h.store.GrantToAgent(r.Context(), &grant); err != nil { slog.Error("mcp.grant_agent", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } h.emitCacheInvalidate() emitAudit(h.msgBus, r, "mcp_server.agent_granted", "mcp_server", serverID.String()) writeJSON(w, http.StatusCreated, map[string]string{"status": "granted"}) } func (h *MCPHandler) handleRevokeAgent(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) serverID, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "server")}) return } agentID, err := uuid.Parse(r.PathValue("agentID")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")}) return } if err := h.store.RevokeFromAgent(r.Context(), serverID, agentID); err != nil { slog.Error("mcp.revoke_agent", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } h.emitCacheInvalidate() emitAudit(h.msgBus, r, "mcp_server.agent_revoked", "mcp_server", serverID.String()) writeJSON(w, http.StatusOK, map[string]string{"status": "revoked"}) } func (h *MCPHandler) handleListAgentGrants(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) agentID, err := uuid.Parse(r.PathValue("agentID")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")}) return } grants, err := h.store.ListAgentGrants(r.Context(), agentID) if err != nil { slog.Error("mcp.list_agent_grants", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"grants": grants}) } func (h *MCPHandler) handleListServerGrants(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) serverID, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "server")}) return } grants, err := h.store.ListServerGrants(r.Context(), serverID) if err != nil { slog.Error("mcp.list_server_grants", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "grants")}) return } writeJSON(w, http.StatusOK, map[string]any{"grants": grants}) } func (h *MCPHandler) handleGrantUser(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) serverID, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "server")}) return } var req struct { UserID string `json:"user_id"` ToolAllow json.RawMessage `json:"tool_allow,omitempty"` ToolDeny json.RawMessage `json:"tool_deny,omitempty"` } if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)}) return } if req.UserID == "" { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "user_id")}) return } if err := store.ValidateUserID(req.UserID); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } grant := store.MCPUserGrant{ ServerID: serverID, UserID: req.UserID, Enabled: true, ToolAllow: req.ToolAllow, ToolDeny: req.ToolDeny, GrantedBy: store.UserIDFromContext(r.Context()), } if err := h.store.GrantToUser(r.Context(), &grant); err != nil { slog.Error("mcp.grant_user", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } h.emitCacheInvalidate() emitAudit(h.msgBus, r, "mcp_server.user_granted", "mcp_server", serverID.String()) writeJSON(w, http.StatusCreated, map[string]string{"status": "granted"}) } func (h *MCPHandler) handleRevokeUser(w http.ResponseWriter, r *http.Request) { locale := store.LocaleFromContext(r.Context()) serverID, err := uuid.Parse(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "server")}) return } targetUserID := r.PathValue("userID") if err := store.ValidateUserID(targetUserID); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } if err := h.store.RevokeFromUser(r.Context(), serverID, targetUserID); err != nil { slog.Error("mcp.revoke_user", "error", err) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } h.emitCacheInvalidate() emitAudit(h.msgBus, r, "mcp_server.user_revoked", "mcp_server", serverID.String()) writeJSON(w, http.StatusOK, map[string]string{"status": "revoked"}) }