Просмотр исходного кода

feat: mcp cache support (#204)

* feat: mcp cache support

* fix: ci lint
zijiren 7 месяцев назад
Родитель
Сommit
fba4ea7f36

+ 18 - 2
core/controller/groupmcp-server.go

@@ -60,7 +60,7 @@ func GroupMCPSseServer(c *gin.Context) {
 
 	group := middleware.GetGroup(c)
 
-	groupMcp, err := model.GetEnabledGroupMCPByID(id, group.ID)
+	groupMcp, err := model.CacheGetGroupMCP(group.ID, id)
 	if err != nil {
 		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
@@ -69,6 +69,14 @@ func GroupMCPSseServer(c *gin.Context) {
 		))
 		return
 	}
+	if groupMcp.Status != model.GroupMCPStatusEnabled {
+		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
+			mcp.NewRequestId(nil),
+			mcp.INVALID_REQUEST,
+			"mcp is not enabled",
+		))
+		return
+	}
 
 	switch groupMcp.Type {
 	case model.GroupMCPTypeProxySSE:
@@ -222,7 +230,7 @@ func GroupMCPStreamable(c *gin.Context) {
 
 	group := middleware.GetGroup(c)
 
-	groupMcp, err := model.GetEnabledGroupMCPByID(id, group.ID)
+	groupMcp, err := model.CacheGetGroupMCP(group.ID, id)
 	if err != nil {
 		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
@@ -231,6 +239,14 @@ func GroupMCPStreamable(c *gin.Context) {
 		))
 		return
 	}
+	if groupMcp.Status != model.GroupMCPStatusEnabled {
+		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
+			mcp.NewRequestId(nil),
+			mcp.INVALID_REQUEST,
+			"mcp is not enabled",
+		))
+		return
+	}
 
 	switch groupMcp.Type {
 	case model.GroupMCPTypeProxyStreamable:

+ 21 - 5
core/controller/publicmcp-server.go

@@ -123,7 +123,7 @@ func (r *redisStoreManager) Delete(session string) {
 func PublicMCPSseServer(c *gin.Context) {
 	mcpID := c.Param("id")
 
-	publicMcp, err := model.GetEnabledPublicMCPByID(mcpID)
+	publicMcp, err := model.CacheGetPublicMCP(mcpID)
 	if err != nil {
 		c.JSON(http.StatusBadRequest, CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
@@ -132,6 +132,14 @@ func PublicMCPSseServer(c *gin.Context) {
 		))
 		return
 	}
+	if publicMcp.Status != model.PublicMCPStatusEnabled {
+		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
+			mcp.NewRequestId(nil),
+			mcp.INVALID_REQUEST,
+			"mcp is not enabled",
+		))
+		return
+	}
 
 	switch publicMcp.Type {
 	case model.PublicMCPTypeProxySSE:
@@ -163,7 +171,7 @@ func handlePublicEmbedMCP(c *gin.Context, mcpID string, config *model.MCPEmbeddi
 	var reusingConfig map[string]string
 	if len(config.Reusing) != 0 {
 		group := middleware.GetGroup(c)
-		param, err := model.GetGroupPublicMCPReusingParam(mcpID, group.ID)
+		param, err := model.CacheGetPublicMCPReusingParam(mcpID, group.ID)
 		if err != nil {
 			c.JSON(http.StatusBadRequest, CreateMCPErrorResponse(
 				mcp.NewRequestId(nil),
@@ -349,7 +357,7 @@ func processReusingParams(reusingParams map[string]model.ReusingParam, mcpID str
 		return nil
 	}
 
-	param, err := model.GetGroupPublicMCPReusingParam(mcpID, groupID)
+	param, err := model.CacheGetPublicMCPReusingParam(mcpID, groupID)
 	if err != nil {
 		return err
 	}
@@ -467,7 +475,7 @@ func sendMCPSSEMessage(c *gin.Context, mcpType, sessionID string) {
 // TODO: batch and sse support
 func PublicMCPStreamable(c *gin.Context) {
 	mcpID := c.Param("id")
-	publicMcp, err := model.GetEnabledPublicMCPByID(mcpID)
+	publicMcp, err := model.CacheGetPublicMCP(mcpID)
 	if err != nil {
 		c.JSON(http.StatusBadRequest, CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
@@ -476,6 +484,14 @@ func PublicMCPStreamable(c *gin.Context) {
 		))
 		return
 	}
+	if publicMcp.Status != model.PublicMCPStatusEnabled {
+		c.JSON(http.StatusNotFound, CreateMCPErrorResponse(
+			mcp.NewRequestId(nil),
+			mcp.INVALID_REQUEST,
+			"mcp is not enabled",
+		))
+		return
+	}
 
 	switch publicMcp.Type {
 	case model.PublicMCPTypeProxyStreamable:
@@ -506,7 +522,7 @@ func handlePublicEmbedStreamable(c *gin.Context, mcpID string, config *model.MCP
 	var reusingConfig map[string]string
 	if len(config.Reusing) != 0 {
 		group := middleware.GetGroup(c)
-		param, err := model.GetGroupPublicMCPReusingParam(mcpID, group.ID)
+		param, err := model.CacheGetPublicMCPReusingParam(mcpID, group.ID)
 		if err != nil {
 			c.JSON(http.StatusBadRequest, CreateMCPErrorResponse(
 				mcp.NewRequestId(nil),

+ 2 - 2
core/controller/publicmcp.go

@@ -215,7 +215,7 @@ func GetGroupPublicMCPReusingParam(c *gin.Context) {
 		return
 	}
 
-	param, err := model.GetGroupPublicMCPReusingParam(mcpID, groupID)
+	param, err := model.GetPublicMCPReusingParam(mcpID, groupID)
 	if err != nil {
 		middleware.ErrorResponse(c, http.StatusNotFound, err.Error())
 		return
@@ -255,7 +255,7 @@ func SaveGroupPublicMCPReusingParam(c *gin.Context) {
 	param.MCPID = mcpID
 	param.GroupID = groupID
 
-	if err := model.SaveGroupPublicMCPReusingParam(&param); err != nil {
+	if err := model.SavePublicMCPReusingParam(&param); err != nil {
 		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
 		return
 	}

+ 266 - 7
core/model/cache.go

@@ -242,21 +242,25 @@ func CacheUpdateTokenStatus(key string, status int) error {
 	return updateTokenStatusScript.Run(context.Background(), common.RDB, []string{fmt.Sprintf(TokenCacheKey, key)}, status).Err()
 }
 
-type redisGroupModelConfigMap map[string]GroupModelConfig
+type redisMap[K comparable, V any] map[K]V
 
 var (
-	_ redis.Scanner            = (*redisGroupModelConfigMap)(nil)
-	_ encoding.BinaryMarshaler = (*redisGroupModelConfigMap)(nil)
+	_ redis.Scanner            = (*redisMap[string, any])(nil)
+	_ encoding.BinaryMarshaler = (*redisMap[string, any])(nil)
 )
 
-func (r *redisGroupModelConfigMap) ScanRedis(value string) error {
+func (r *redisMap[K, V]) ScanRedis(value string) error {
 	return sonic.UnmarshalString(value, r)
 }
 
-func (r redisGroupModelConfigMap) MarshalBinary() ([]byte, error) {
+func (r redisMap[K, V]) MarshalBinary() ([]byte, error) {
 	return sonic.Marshal(r)
 }
 
+type (
+	redisGroupModelConfigMap redisMap[string, GroupModelConfig]
+)
+
 type GroupCache struct {
 	ID            string                   `json:"-"              redis:"-"`
 	Status        int                      `json:"status"         redis:"st"`
@@ -278,8 +282,8 @@ func (g *GroupCache) GetAvailableSets() []string {
 }
 
 func (g *Group) ToGroupCache() *GroupCache {
-	modelConfigs := make(redisGroupModelConfigMap, len(g.GroupModelConfigs))
-	for _, modelConfig := range g.GroupModelConfigs {
+	modelConfigs := make(redisGroupModelConfigMap, len(g.ModelConfigs))
+	for _, modelConfig := range g.ModelConfigs {
 		modelConfigs[modelConfig.Model] = modelConfig
 	}
 	return &GroupCache{
@@ -411,6 +415,261 @@ func CacheUpdateGroupUsedAmountOnlyIncrease(id string, amount float64) error {
 	return updateGroupUsedAmountOnlyIncreaseScript.Run(context.Background(), common.RDB, []string{fmt.Sprintf(GroupCacheKey, id)}, amount).Err()
 }
 
+type GroupMCPCache struct {
+	ID            string               `json:"id"             redis:"i"`
+	GroupID       string               `json:"group_id"       redis:"g"`
+	Status        GroupMCPStatus       `json:"status"         redis:"s"`
+	Type          GroupMCPType         `json:"type"           redis:"t"`
+	ProxyConfig   *GroupMCPProxyConfig `json:"proxy_config"   redis:"pc"`
+	OpenAPIConfig *MCPOpenAPIConfig    `json:"openapi_config" redis:"oc"`
+}
+
+func (g *GroupMCP) ToGroupMCPCache() *GroupMCPCache {
+	return &GroupMCPCache{
+		ID:            g.ID,
+		GroupID:       g.GroupID,
+		Status:        g.Status,
+		Type:          g.Type,
+		ProxyConfig:   g.ProxyConfig,
+		OpenAPIConfig: g.OpenAPIConfig,
+	}
+}
+
+const (
+	GroupMCPCacheKey = "group_mcp:%s:%s" // group_id:mcp_id
+)
+
+func CacheDeleteGroupMCP(groupID, mcpID string) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	return common.RedisDel(fmt.Sprintf(GroupMCPCacheKey, groupID, mcpID))
+}
+
+//nolint:gosec
+func CacheSetGroupMCP(groupMCP *GroupMCPCache) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	key := fmt.Sprintf(GroupMCPCacheKey, groupMCP.GroupID, groupMCP.ID)
+	pipe := common.RDB.Pipeline()
+	pipe.HSet(context.Background(), key, groupMCP)
+	expireTime := SyncFrequency + time.Duration(rand.Int64N(60)-30)*time.Second
+	pipe.Expire(context.Background(), key, expireTime)
+	_, err := pipe.Exec(context.Background())
+	return err
+}
+
+func CacheGetGroupMCP(groupID, mcpID string) (*GroupMCPCache, error) {
+	if !common.RedisEnabled {
+		groupMCP, err := GetGroupMCPByID(mcpID, groupID)
+		if err != nil {
+			return nil, err
+		}
+		return groupMCP.ToGroupMCPCache(), nil
+	}
+
+	cacheKey := fmt.Sprintf(GroupMCPCacheKey, groupID, mcpID)
+	groupMCPCache := &GroupMCPCache{}
+	err := common.RDB.HGetAll(context.Background(), cacheKey).Scan(groupMCPCache)
+	if err == nil && groupMCPCache.ID != "" {
+		return groupMCPCache, nil
+	} else if err != nil && !errors.Is(err, redis.Nil) {
+		log.Errorf("get group mcp (%s:%s) from redis error: %s", groupID, mcpID, err.Error())
+	}
+
+	groupMCP, err := GetGroupMCPByID(mcpID, groupID)
+	if err != nil {
+		return nil, err
+	}
+
+	gmc := groupMCP.ToGroupMCPCache()
+
+	if err := CacheSetGroupMCP(gmc); err != nil {
+		log.Error("redis set group mcp error: " + err.Error())
+	}
+
+	return gmc, nil
+}
+
+var updateGroupMCPStatusScript = redis.NewScript(`
+	if redis.call("HExists", KEYS[1], "s") then
+		redis.call("HSet", KEYS[1], "s", ARGV[1])
+	end
+	return redis.status_reply("ok")
+`)
+
+func CacheUpdateGroupMCPStatus(groupID, mcpID string, status GroupMCPStatus) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	return updateGroupMCPStatusScript.Run(context.Background(), common.RDB, []string{fmt.Sprintf(GroupMCPCacheKey, groupID, mcpID)}, status).Err()
+}
+
+type PublicMCPCache struct {
+	ID            string                `json:"id"             redis:"i"`
+	Status        PublicMCPStatus       `json:"status"         redis:"s"`
+	Type          PublicMCPType         `json:"type"           redis:"t"`
+	Price         MCPPrice              `json:"price"          redis:"p"`
+	ProxyConfig   *PublicMCPProxyConfig `json:"proxy_config"   redis:"pc"`
+	OpenAPIConfig *MCPOpenAPIConfig     `json:"openapi_config" redis:"oc"`
+	EmbedConfig   *MCPEmbeddingConfig   `json:"embed_config"   redis:"ec"`
+}
+
+func (p *PublicMCP) ToPublicMCPCache() *PublicMCPCache {
+	return &PublicMCPCache{
+		ID:            p.ID,
+		Status:        p.Status,
+		Type:          p.Type,
+		Price:         p.Price,
+		ProxyConfig:   p.ProxyConfig,
+		OpenAPIConfig: p.OpenAPIConfig,
+		EmbedConfig:   p.EmbedConfig,
+	}
+}
+
+const (
+	PublicMCPCacheKey = "public_mcp:%s" // mcp_id
+)
+
+func CacheDeletePublicMCP(mcpID string) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	return common.RedisDel(fmt.Sprintf(PublicMCPCacheKey, mcpID))
+}
+
+//nolint:gosec
+func CacheSetPublicMCP(publicMCP *PublicMCPCache) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	key := fmt.Sprintf(PublicMCPCacheKey, publicMCP.ID)
+	pipe := common.RDB.Pipeline()
+	pipe.HSet(context.Background(), key, publicMCP)
+	expireTime := SyncFrequency + time.Duration(rand.Int64N(60)-30)*time.Second
+	pipe.Expire(context.Background(), key, expireTime)
+	_, err := pipe.Exec(context.Background())
+	return err
+}
+
+func CacheGetPublicMCP(mcpID string) (*PublicMCPCache, error) {
+	if !common.RedisEnabled {
+		publicMCP, err := GetPublicMCPByID(mcpID)
+		if err != nil {
+			return nil, err
+		}
+		return publicMCP.ToPublicMCPCache(), nil
+	}
+
+	cacheKey := fmt.Sprintf(PublicMCPCacheKey, mcpID)
+	publicMCPCache := &PublicMCPCache{}
+	err := common.RDB.HGetAll(context.Background(), cacheKey).Scan(publicMCPCache)
+	if err == nil && publicMCPCache.ID != "" {
+		return publicMCPCache, nil
+	} else if err != nil && !errors.Is(err, redis.Nil) {
+		log.Errorf("get public mcp (%s) from redis error: %s", mcpID, err.Error())
+	}
+
+	publicMCP, err := GetPublicMCPByID(mcpID)
+	if err != nil {
+		return nil, err
+	}
+
+	pmc := publicMCP.ToPublicMCPCache()
+
+	if err := CacheSetPublicMCP(pmc); err != nil {
+		log.Error("redis set public mcp error: " + err.Error())
+	}
+
+	return pmc, nil
+}
+
+var updatePublicMCPStatusScript = redis.NewScript(`
+	if redis.call("HExists", KEYS[1], "s") then
+		redis.call("HSet", KEYS[1], "s", ARGV[1])
+	end
+	return redis.status_reply("ok")
+`)
+
+func CacheUpdatePublicMCPStatus(mcpID string, status PublicMCPStatus) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	return updatePublicMCPStatusScript.Run(context.Background(), common.RDB, []string{fmt.Sprintf(PublicMCPCacheKey, mcpID)}, status).Err()
+}
+
+const (
+	PublicMCPReusingParamCacheKey = "public_mcp_reusing_param:%s:%s" // mcp_id:group_id
+)
+
+type PublicMCPReusingParamCache struct {
+	MCPID         string            `json:"mcp_id"         redis:"m"`
+	GroupID       string            `json:"group_id"       redis:"g"`
+	ReusingParams map[string]string `json:"reusing_params" redis:"rp"`
+}
+
+func (p *PublicMCPReusingParam) ToPublicMCPReusingParamCache() *PublicMCPReusingParamCache {
+	return &PublicMCPReusingParamCache{
+		MCPID:         p.MCPID,
+		GroupID:       p.GroupID,
+		ReusingParams: p.ReusingParams,
+	}
+}
+
+func CacheDeletePublicMCPReusingParam(mcpID, groupID string) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	return common.RedisDel(fmt.Sprintf(PublicMCPReusingParamCacheKey, mcpID, groupID))
+}
+
+//nolint:gosec
+func CacheSetPublicMCPReusingParam(param *PublicMCPReusingParamCache) error {
+	if !common.RedisEnabled {
+		return nil
+	}
+	key := fmt.Sprintf(PublicMCPReusingParamCacheKey, param.MCPID, param.GroupID)
+	pipe := common.RDB.Pipeline()
+	pipe.HSet(context.Background(), key, param)
+	expireTime := SyncFrequency + time.Duration(rand.Int64N(60)-30)*time.Second
+	pipe.Expire(context.Background(), key, expireTime)
+	_, err := pipe.Exec(context.Background())
+	return err
+}
+
+func CacheGetPublicMCPReusingParam(mcpID, groupID string) (*PublicMCPReusingParamCache, error) {
+	if !common.RedisEnabled {
+		param, err := GetPublicMCPReusingParam(mcpID, groupID)
+		if err != nil {
+			return nil, err
+		}
+		return param.ToPublicMCPReusingParamCache(), nil
+	}
+
+	cacheKey := fmt.Sprintf(PublicMCPReusingParamCacheKey, mcpID, groupID)
+	paramCache := &PublicMCPReusingParamCache{}
+	err := common.RDB.HGetAll(context.Background(), cacheKey).Scan(paramCache)
+	if err == nil && paramCache.MCPID != "" {
+		return paramCache, nil
+	} else if err != nil && !errors.Is(err, redis.Nil) {
+		log.Errorf("get public mcp reusing param (%s:%s) from redis error: %s", mcpID, groupID, err.Error())
+	}
+
+	param, err := GetPublicMCPReusingParam(mcpID, groupID)
+	if err != nil {
+		return nil, err
+	}
+
+	prc := param.ToPublicMCPReusingParamCache()
+
+	if err := CacheSetPublicMCPReusingParam(prc); err != nil {
+		log.Error("redis set public mcp reusing param error: " + err.Error())
+	}
+
+	return prc, nil
+}
+
 //nolint:revive
 type ModelConfigCache interface {
 	GetModelConfig(model string) (*ModelConfig, bool)

+ 2 - 2
core/model/group.go

@@ -25,9 +25,9 @@ type Group struct {
 	CreatedAt              time.Time               `json:"created_at"`
 	ID                     string                  `gorm:"primaryKey"                    json:"id"`
 	Tokens                 []Token                 `gorm:"foreignKey:GroupID"            json:"-"`
-	GroupModelConfigs      []GroupModelConfig      `gorm:"foreignKey:GroupID"            json:"-"`
+	ModelConfigs           []GroupModelConfig      `gorm:"foreignKey:GroupID"            json:"-"`
 	PublicMCPReusingParams []PublicMCPReusingParam `gorm:"foreignKey:GroupID"            json:"-"`
-	GroupMCPs              []GroupMCP              `gorm:"foreignKey:GroupID"            json:"-"`
+	MCPs                   []GroupMCP              `gorm:"foreignKey:GroupID"            json:"-"`
 	Status                 int                     `gorm:"default:1;index"               json:"status"`
 	RPMRatio               float64                 `gorm:"index"                         json:"rpm_ratio,omitempty"`
 	TPMRatio               float64                 `gorm:"index"                         json:"tpm_ratio,omitempty"`

+ 29 - 14
core/model/groupmcp.go

@@ -6,6 +6,7 @@ import (
 
 	"github.com/bytedance/sonic"
 	"github.com/labring/aiproxy/core/common"
+	log "github.com/sirupsen/logrus"
 	"gorm.io/gorm"
 )
 
@@ -102,10 +103,18 @@ func CreateGroupMCP(mcp *GroupMCP) error {
 }
 
 // UpdateGroupMCP updates an existing GroupMCP
-func UpdateGroupMCP(mcp *GroupMCP) error {
+func UpdateGroupMCP(mcp *GroupMCP) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeleteGroupMCP(mcp.GroupID, mcp.ID); err != nil {
+				log.Error("cache delete group mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	selects := []string{
 		"name",
-		"proxy_sse_config",
+		"proxy_config",
 		"openapi_config",
 	}
 	if mcp.Type != "" {
@@ -121,13 +130,29 @@ func UpdateGroupMCP(mcp *GroupMCP) error {
 	return HandleUpdateResult(result, ErrGroupMCPNotFound)
 }
 
-func UpdateGroupMCPStatus(id string, groupID string, status GroupMCPStatus) error {
+func UpdateGroupMCPStatus(id string, groupID string, status GroupMCPStatus) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeleteGroupMCP(groupID, id); err != nil {
+				log.Error("cache delete group mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	result := DB.Model(&GroupMCP{}).Where("id = ? AND group_id = ?", id, groupID).Update("status", status)
 	return HandleUpdateResult(result, ErrGroupMCPNotFound)
 }
 
 // DeleteGroupMCP deletes a GroupMCP by ID and GroupID
-func DeleteGroupMCP(id string, groupID string) error {
+func DeleteGroupMCP(id string, groupID string) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeleteGroupMCP(groupID, id); err != nil {
+				log.Error("cache delete group mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	if id == "" || groupID == "" {
 		return errors.New("group mcp id or group id is empty")
 	}
@@ -145,16 +170,6 @@ func GetGroupMCPByID(id string, groupID string) (*GroupMCP, error) {
 	return &mcp, HandleNotFound(err, ErrGroupMCPNotFound)
 }
 
-// GetEnabledGroupMCPByID retrieves a GroupMCP by ID and GroupID
-func GetEnabledGroupMCPByID(id string, groupID string) (*GroupMCP, error) {
-	if id == "" || groupID == "" {
-		return nil, errors.New("group mcp id or group id is empty")
-	}
-	var mcp GroupMCP
-	err := DB.Where("id = ? AND group_id = ? AND status = ?", id, groupID, GroupMCPStatusEnabled).First(&mcp).Error
-	return &mcp, HandleNotFound(err, ErrGroupMCPNotFound)
-}
-
 // GetGroupMCPs retrieves GroupMCPs with pagination and filtering
 func GetGroupMCPs(groupID string, page int, perPage int, mcpType PublicMCPType, keyword string, status GroupMCPStatus) (mcps []*GroupMCP, total int64, err error) {
 	if groupID == "" {

+ 77 - 29
core/model/publicmcp.go

@@ -7,6 +7,7 @@ import (
 
 	"github.com/bytedance/sonic"
 	"github.com/labring/aiproxy/core/common"
+	log "github.com/sirupsen/logrus"
 	"gorm.io/gorm"
 )
 
@@ -67,26 +68,26 @@ type PublicMCPReusingParam struct {
 	ReusingParams map[string]string `gorm:"serializer:fastjson;type:text" json:"reusing_params"`
 }
 
-func (l *PublicMCPReusingParam) BeforeCreate(_ *gorm.DB) (err error) {
-	if l.MCPID == "" {
+func (p *PublicMCPReusingParam) BeforeCreate(_ *gorm.DB) (err error) {
+	if p.MCPID == "" {
 		return errors.New("mcp id is empty")
 	}
-	if l.GroupID == "" {
+	if p.GroupID == "" {
 		return errors.New("group is empty")
 	}
 	return
 }
 
-func (l *PublicMCPReusingParam) MarshalJSON() ([]byte, error) {
+func (p *PublicMCPReusingParam) MarshalJSON() ([]byte, error) {
 	type Alias PublicMCPReusingParam
 	a := &struct {
 		*Alias
 		CreatedAt int64 `json:"created_at"`
 		UpdateAt  int64 `json:"update_at"`
 	}{
-		Alias:     (*Alias)(l),
-		CreatedAt: l.CreatedAt.UnixMilli(),
-		UpdateAt:  l.UpdateAt.UnixMilli(),
+		Alias:     (*Alias)(p),
+		CreatedAt: p.CreatedAt.UnixMilli(),
+		UpdateAt:  p.UpdateAt.UnixMilli(),
 	}
 	return sonic.Marshal(a)
 }
@@ -197,19 +198,36 @@ func CreatePublicMCP(mcp *PublicMCP) error {
 	return err
 }
 
-func SavePublicMCP(mcp *PublicMCP) error {
+func SavePublicMCP(mcp *PublicMCP) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCP(mcp.ID); err != nil {
+				log.Error("cache delete public mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	return DB.Save(mcp).Error
 }
 
 // UpdatePublicMCP updates an existing MCP
-func UpdatePublicMCP(mcp *PublicMCP) error {
+func UpdatePublicMCP(mcp *PublicMCP) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCP(mcp.ID); err != nil {
+				log.Error("cache delete public mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	selects := []string{
 		"repo_url",
 		"readme",
+		"readme_url",
 		"tags",
 		"author",
 		"logo_url",
-		"proxy_sse_config",
+		"proxy_config",
 		"openapi_config",
 		"embed_config",
 	}
@@ -233,13 +251,29 @@ func UpdatePublicMCP(mcp *PublicMCP) error {
 	return HandleUpdateResult(result, ErrPublicMCPNotFound)
 }
 
-func UpdatePublicMCPStatus(id string, status PublicMCPStatus) error {
+func UpdatePublicMCPStatus(id string, status PublicMCPStatus) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheUpdatePublicMCPStatus(id, status); err != nil {
+				log.Error("cache update public mcp status error: " + err.Error())
+			}
+		}
+	}()
+
 	result := DB.Model(&PublicMCP{}).Where("id = ?", id).Update("status", status)
 	return HandleUpdateResult(result, ErrPublicMCPNotFound)
 }
 
 // DeletePublicMCP deletes an MCP by ID
-func DeletePublicMCP(id string) error {
+func DeletePublicMCP(id string) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCP(id); err != nil {
+				log.Error("cache delete public mcp error: " + err.Error())
+			}
+		}
+	}()
+
 	if id == "" {
 		return errors.New("MCP id is empty")
 	}
@@ -257,16 +291,6 @@ func GetPublicMCPByID(id string) (*PublicMCP, error) {
 	return &mcp, HandleNotFound(err, ErrPublicMCPNotFound)
 }
 
-// GetEnabledPublicMCPByID retrieves an MCP by ID
-func GetEnabledPublicMCPByID(id string) (*PublicMCP, error) {
-	if id == "" {
-		return nil, errors.New("MCP id is empty")
-	}
-	var mcp PublicMCP
-	err := DB.Where("id = ? AND status = ?", id, PublicMCPStatusEnabled).First(&mcp).Error
-	return &mcp, HandleNotFound(err, ErrPublicMCPNotFound)
-}
-
 // GetPublicMCPs retrieves MCPs with pagination and filtering
 func GetPublicMCPs(page int, perPage int, mcpType PublicMCPType, keyword string, status PublicMCPStatus) (mcps []*PublicMCP, total int64, err error) {
 	tx := DB.Model(&PublicMCP{})
@@ -320,12 +344,28 @@ func GetPublicMCPsEnabled(ids []string) ([]string, error) {
 	return mcpIDs, nil
 }
 
-func SaveGroupPublicMCPReusingParam(param *PublicMCPReusingParam) (err error) {
+func SavePublicMCPReusingParam(param *PublicMCPReusingParam) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCPReusingParam(param.MCPID, param.GroupID); err != nil {
+				log.Error("cache delete public mcp reusing param error: " + err.Error())
+			}
+		}
+	}()
+
 	return DB.Save(param).Error
 }
 
-// UpdateGroupPublicMCPReusingParam updates an existing GroupMCPReusingParam
-func UpdateGroupPublicMCPReusingParam(param *PublicMCPReusingParam) error {
+// UpdatePublicMCPReusingParam updates an existing GroupMCPReusingParam
+func UpdatePublicMCPReusingParam(param *PublicMCPReusingParam) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCPReusingParam(param.MCPID, param.GroupID); err != nil {
+				log.Error("cache delete public mcp reusing param error: " + err.Error())
+			}
+		}
+	}()
+
 	result := DB.
 		Select([]string{
 			"reusing_params",
@@ -335,8 +375,16 @@ func UpdateGroupPublicMCPReusingParam(param *PublicMCPReusingParam) error {
 	return HandleUpdateResult(result, ErrMCPReusingParamNotFound)
 }
 
-// DeleteGroupPublicMCPReusingParam deletes a GroupMCPReusingParam
-func DeleteGroupPublicMCPReusingParam(mcpID string, groupID string) error {
+// DeletePublicMCPReusingParam deletes a GroupMCPReusingParam
+func DeletePublicMCPReusingParam(mcpID string, groupID string) (err error) {
+	defer func() {
+		if err == nil {
+			if err := CacheDeletePublicMCPReusingParam(mcpID, groupID); err != nil {
+				log.Error("cache delete public mcp reusing param error: " + err.Error())
+			}
+		}
+	}()
+
 	if mcpID == "" || groupID == "" {
 		return errors.New("MCP ID or Group ID is empty")
 	}
@@ -346,8 +394,8 @@ func DeleteGroupPublicMCPReusingParam(mcpID string, groupID string) error {
 	return HandleUpdateResult(result, ErrMCPReusingParamNotFound)
 }
 
-// GetGroupPublicMCPReusingParam retrieves a GroupMCPReusingParam by MCP ID and Group ID
-func GetGroupPublicMCPReusingParam(mcpID string, groupID string) (*PublicMCPReusingParam, error) {
+// GetPublicMCPReusingParam retrieves a GroupMCPReusingParam by MCP ID and Group ID
+func GetPublicMCPReusingParam(mcpID string, groupID string) (*PublicMCPReusingParam, error) {
 	if mcpID == "" || groupID == "" {
 		return nil, errors.New("MCP ID or Group ID is empty")
 	}