Jelajahi Sumber

feat: add group's public mcp reusing config (#253)

* feat: add group's public mcp reusing config

* fix: ci lint
zijiren 6 bulan lalu
induk
melakukan
ca3969aae7

+ 4 - 7
core/controller/mcp/embedmcp.go

@@ -141,7 +141,7 @@ func GetEmbedConfig(
 	ct mcpservers.ConfigTemplates,
 	initConfig map[string]string,
 ) (*model.MCPEmbeddingConfig, error) {
-	reusingConfig := make(map[string]model.MCPEmbeddingReusingConfig)
+	reusingConfig := make(map[string]model.ReusingParam)
 	embedConfig := &model.MCPEmbeddingConfig{
 		Init: initConfig,
 	}
@@ -155,19 +155,16 @@ func GetEmbedConfig(
 			if _, ok := initConfig[key]; ok {
 				return nil, fmt.Errorf("config %s is provided, but it is not allowed", key)
 			}
-			reusingConfig[key] = model.MCPEmbeddingReusingConfig{
+			reusingConfig[key] = model.ReusingParam{
 				Name:        value.Name,
 				Description: value.Description,
 				Required:    true,
 			}
 		case mcpservers.ConfigRequiredTypeInitOrReusingOnly:
-			if v, ok := initConfig[key]; ok {
-				if v == "" {
-					return nil, fmt.Errorf("config %s is required", key)
-				}
+			if v, ok := initConfig[key]; ok && v != "" {
 				continue
 			}
-			reusingConfig[key] = model.MCPEmbeddingReusingConfig{
+			reusingConfig[key] = model.ReusingParam{
 				Name:        value.Name,
 				Description: value.Description,
 				Required:    true,

+ 1 - 1
core/controller/mcp/mcp.go

@@ -94,7 +94,7 @@ func handleEmbedSSEMCP(
 			))
 			return
 		}
-		reusingConfig = param.ReusingParams
+		reusingConfig = param.Params
 	}
 	server, err := mcpservers.GetMCPServer(mcpID, config.Init, reusingConfig)
 	if err != nil {

+ 133 - 0
core/controller/mcp/publicmcp-group.go

@@ -0,0 +1,133 @@
+package controller
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/bytedance/sonic"
+	"github.com/gin-gonic/gin"
+	"github.com/labring/aiproxy/core/controller/utils"
+	"github.com/labring/aiproxy/core/middleware"
+	"github.com/labring/aiproxy/core/model"
+	"gorm.io/gorm"
+)
+
+type GroupPublicMCPResponse struct {
+	model.PublicMCP
+	Endpoints MCPEndpoint                   `json:"endpoints"`
+	Reusing   map[string]model.ReusingParam `json:"reusing"`
+	Params    map[string]string             `json:"params"`
+}
+
+func (r *GroupPublicMCPResponse) MarshalJSON() ([]byte, error) {
+	type Alias GroupPublicMCPResponse
+	a := &struct {
+		*Alias
+		CreatedAt int64 `json:"created_at"`
+		UpdateAt  int64 `json:"update_at"`
+	}{
+		Alias: (*Alias)(r),
+	}
+	if !r.CreatedAt.IsZero() {
+		a.CreatedAt = r.CreatedAt.UnixMilli()
+	}
+	if !r.UpdateAt.IsZero() {
+		a.UpdateAt = r.UpdateAt.UnixMilli()
+	}
+	return sonic.Marshal(a)
+}
+
+func NewGroupPublicMCPResponse(
+	host string,
+	mcp model.PublicMCP,
+	groupID string,
+) (GroupPublicMCPResponse, error) {
+	r := GroupPublicMCPResponse{
+		PublicMCP: mcp,
+		Endpoints: NewPublicMCPEndpoint(host, mcp),
+	}
+	r.ProxyConfig = nil
+	r.EmbedConfig = nil
+	r.OpenAPIConfig = nil
+
+	switch mcp.Type {
+	case model.PublicMCPTypeProxySSE, model.PublicMCPTypeProxyStreamable:
+		for _, v := range mcp.ProxyConfig.Reusing {
+			r.Reusing[v.Name] = v.ReusingParam
+		}
+	case model.PublicMCPTypeEmbed:
+		r.Reusing = mcp.EmbedConfig.Reusing
+	}
+
+	reusingParams, err := model.GetPublicMCPReusingParam(mcp.ID, groupID)
+	if err != nil {
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return r, err
+		}
+	}
+	r.Params = reusingParams.Params
+
+	return r, nil
+}
+
+func NewGroupPublicMCPResponses(
+	host string,
+	mcps []model.PublicMCP,
+	groupID string,
+) ([]GroupPublicMCPResponse, error) {
+	responses := make([]GroupPublicMCPResponse, len(mcps))
+	for i, mcp := range mcps {
+		response, err := NewGroupPublicMCPResponse(host, mcp, groupID)
+		if err != nil {
+			return nil, err
+		}
+		responses[i] = response
+	}
+	return responses, nil
+}
+
+// GetGroupPublicMCPs godoc
+//
+//	@Summary		Get MCPs by group
+//	@Description	Get MCPs by group
+//	@Tags			mcp
+//	@Tags			group
+//	@Produce		json
+//	@Security		ApiKeyAuth
+//	@Param			group		path		string	true	"Group ID"
+//	@Param			page		query		int		false	"Page"
+//	@Param			per_page	query		int		false	"Per Page"
+//	@Param			type		query		string	false	"Type"
+//	@Param			keyword		query		string	false	"Keyword"
+//	@Success		200			{object}	middleware.APIResponse{data=[]GroupPublicMCPResponse}
+//	@Router			/api/group/{group}/mcp [get]
+func GetGroupPublicMCPs(c *gin.Context) {
+	groupID := c.Param("group")
+
+	page, perPage := utils.ParsePageParams(c)
+	mcpType := model.PublicMCPType(c.Query("type"))
+	keyword := c.Query("keyword")
+
+	mcps, total, err := model.GetPublicMCPs(
+		page,
+		perPage,
+		mcpType,
+		keyword,
+		model.PublicMCPStatusEnabled,
+	)
+	if err != nil {
+		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	responses, err := NewGroupPublicMCPResponses(c.Request.Host, mcps, groupID)
+	if err != nil {
+		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	middleware.SuccessResponse(c, gin.H{
+		"mcps":  responses,
+		"total": total,
+	})
+}

+ 4 - 4
core/controller/mcp/publicmcp-server.go

@@ -142,7 +142,7 @@ func handlePublicSSEMCP(
 
 // processReusingParams handles the reusing parameters for MCP proxy
 func processReusingParams(
-	reusingParams map[string]model.ReusingParam,
+	reusingParams map[string]model.PublicMCPProxyReusingParam,
 	mcpID, groupID string,
 	headers map[string]string,
 	backendQuery *url.Values,
@@ -157,7 +157,7 @@ func processReusingParams(
 	}
 
 	for k, v := range reusingParams {
-		paramValue, ok := param.ReusingParams[k]
+		paramValue, ok := param.Params[k]
 		if !ok {
 			if v.Required {
 				return fmt.Errorf("%s required", k)
@@ -309,7 +309,7 @@ func handlePublicEmbedStreamable(c *gin.Context, mcpID string, config *model.MCP
 			))
 			return
 		}
-		reusingConfig = param.ReusingParams
+		reusingConfig = param.Params
 	}
 	server, err := mcpservers.GetMCPServer(mcpID, config.Init, reusingConfig)
 	if err != nil {
@@ -349,7 +349,7 @@ func handlePublicProxyStreamable(c *gin.Context, mcpID string, config *model.Pub
 	group := middleware.GetGroup(c)
 
 	// Process reusing parameters if any
-	if err := processReusingParams(config.ReusingParams, mcpID, group.ID, headers, &backendQuery); err != nil {
+	if err := processReusingParams(config.Reusing, mcpID, group.ID, headers, &backendQuery); err != nil {
 		c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
 			mcp.INVALID_REQUEST,

+ 15 - 16
core/controller/mcp/publicmcp.go

@@ -31,14 +31,18 @@ func (mcp *PublicMCPResponse) MarshalJSON() ([]byte, error) {
 		CreatedAt int64 `json:"created_at"`
 		UpdateAt  int64 `json:"update_at"`
 	}{
-		Alias:     (*Alias)(mcp),
-		CreatedAt: mcp.CreatedAt.UnixMilli(),
-		UpdateAt:  mcp.UpdateAt.UnixMilli(),
+		Alias: (*Alias)(mcp),
+	}
+	if !mcp.CreatedAt.IsZero() {
+		a.CreatedAt = mcp.CreatedAt.UnixMilli()
+	}
+	if !mcp.UpdateAt.IsZero() {
+		a.UpdateAt = mcp.UpdateAt.UnixMilli()
 	}
 	return sonic.Marshal(a)
 }
 
-func NewPublicMCPResponse(host string, mcp model.PublicMCP) PublicMCPResponse {
+func NewPublicMCPEndpoint(host string, mcp model.PublicMCP) MCPEndpoint {
 	ep := MCPEndpoint{}
 	switch mcp.Type {
 	case model.PublicMCPTypeProxySSE,
@@ -57,9 +61,13 @@ func NewPublicMCPResponse(host string, mcp model.PublicMCP) PublicMCPResponse {
 		}
 	case model.PublicMCPTypeDocs:
 	}
+	return ep
+}
+
+func NewPublicMCPResponse(host string, mcp model.PublicMCP) PublicMCPResponse {
 	return PublicMCPResponse{
 		PublicMCP: mcp,
-		Endpoints: ep,
+		Endpoints: NewPublicMCPEndpoint(host, mcp),
 	}
 }
 
@@ -91,10 +99,6 @@ func GetPublicMCPs(c *gin.Context) {
 	keyword := c.Query("keyword")
 	status, _ := strconv.Atoi(c.Query("status"))
 
-	if status == 0 {
-		status = int(model.PublicMCPStatusEnabled)
-	}
-
 	mcps, total, err := model.GetPublicMCPs(
 		page,
 		perPage,
@@ -125,11 +129,6 @@ func GetPublicMCPs(c *gin.Context) {
 //	@Router			/api/mcp/public/all [get]
 func GetAllPublicMCPs(c *gin.Context) {
 	status, _ := strconv.Atoi(c.Query("status"))
-
-	if status == 0 {
-		status = int(model.PublicMCPStatusEnabled)
-	}
-
 	mcps, err := model.GetAllPublicMCPs(model.PublicMCPStatus(status))
 	if err != nil {
 		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
@@ -138,7 +137,7 @@ func GetAllPublicMCPs(c *gin.Context) {
 	middleware.SuccessResponse(c, NewPublicMCPResponses(c.Request.Host, mcps))
 }
 
-// GetPublicMCPByIDHandler godoc
+// GetPublicMCPByID godoc
 //
 //	@Summary		Get MCP by ID
 //	@Description	Get a specific MCP by its ID
@@ -148,7 +147,7 @@ func GetAllPublicMCPs(c *gin.Context) {
 //	@Param			id	path		string	true	"MCP ID"
 //	@Success		200	{object}	middleware.APIResponse{data=PublicMCPResponse}
 //	@Router			/api/mcp/public/{id} [get]
-func GetPublicMCPByIDHandler(c *gin.Context) {
+func GetPublicMCPByID(c *gin.Context) {
 	id := c.Param("id")
 	if id == "" {
 		middleware.ErrorResponse(c, http.StatusBadRequest, "MCP ID is required")

+ 160 - 21
core/docs/docs.go

@@ -1533,6 +1533,80 @@ const docTemplate = `{
                 }
             }
         },
+        "/api/group/{group}/mcp": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "Get MCPs by group",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "mcp",
+                    "group"
+                ],
+                "summary": "Get MCPs by group",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Group ID",
+                        "name": "group",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Page",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Per Page",
+                        "name": "per_page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Type",
+                        "name": "type",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Keyword",
+                        "name": "keyword",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/middleware.APIResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/controller.GroupPublicMCPResponse"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/api/group/{group}/model_config/{model}": {
             "get": {
                 "security": [
@@ -8238,6 +8312,71 @@ const docTemplate = `{
                 }
             }
         },
+        "controller.GroupPublicMCPResponse": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "type": "string"
+                },
+                "embed_config": {
+                    "$ref": "#/definitions/model.MCPEmbeddingConfig"
+                },
+                "github_url": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "string"
+                },
+                "logo_url": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "openapi_config": {
+                    "$ref": "#/definitions/model.MCPOpenAPIConfig"
+                },
+                "params": {
+                    "type": "object",
+                    "additionalProperties": {
+                        "type": "string"
+                    }
+                },
+                "price": {
+                    "$ref": "#/definitions/model.MCPPrice"
+                },
+                "proxy_config": {
+                    "$ref": "#/definitions/model.PublicMCPProxyConfig"
+                },
+                "readme": {
+                    "type": "string"
+                },
+                "readme_url": {
+                    "type": "string"
+                },
+                "reusing": {
+                    "type": "object",
+                    "additionalProperties": {
+                        "$ref": "#/definitions/model.ReusingParam"
+                    }
+                },
+                "status": {
+                    "$ref": "#/definitions/model.PublicMCPStatus"
+                },
+                "tags": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "type": {
+                    "$ref": "#/definitions/model.PublicMCPType"
+                },
+                "update_at": {
+                    "type": "string"
+                }
+            }
+        },
         "controller.GroupResponse": {
             "type": "object",
             "properties": {
@@ -9845,25 +9984,11 @@ const docTemplate = `{
                 "reusing": {
                     "type": "object",
                     "additionalProperties": {
-                        "$ref": "#/definitions/model.MCPEmbeddingReusingConfig"
+                        "$ref": "#/definitions/model.ReusingParam"
                     }
                 }
             }
         },
-        "model.MCPEmbeddingReusingConfig": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "required": {
-                    "type": "boolean"
-                }
-            }
-        },
         "model.MCPOpenAPIConfig": {
             "type": "object",
             "properties": {
@@ -10283,10 +10408,10 @@ const docTemplate = `{
                         "type": "string"
                     }
                 },
-                "reusing_params": {
+                "reusing": {
                     "type": "object",
                     "additionalProperties": {
-                        "$ref": "#/definitions/model.ReusingParam"
+                        "$ref": "#/definitions/model.PublicMCPProxyReusingParam"
                     }
                 },
                 "url": {
@@ -10294,6 +10419,23 @@ const docTemplate = `{
                 }
             }
         },
+        "model.PublicMCPProxyReusingParam": {
+            "type": "object",
+            "properties": {
+                "description": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "required": {
+                    "type": "boolean"
+                },
+                "type": {
+                    "$ref": "#/definitions/model.ParamType"
+                }
+            }
+        },
         "model.PublicMCPReusingParam": {
             "type": "object",
             "properties": {
@@ -10306,7 +10448,7 @@ const docTemplate = `{
                 "mcp_id": {
                     "type": "string"
                 },
-                "reusing_params": {
+                "params": {
                     "type": "object",
                     "additionalProperties": {
                         "type": "string"
@@ -10475,9 +10617,6 @@ const docTemplate = `{
                 },
                 "required": {
                     "type": "boolean"
-                },
-                "type": {
-                    "$ref": "#/definitions/model.ParamType"
                 }
             }
         },

+ 160 - 21
core/docs/swagger.json

@@ -1524,6 +1524,80 @@
                 }
             }
         },
+        "/api/group/{group}/mcp": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "Get MCPs by group",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "mcp",
+                    "group"
+                ],
+                "summary": "Get MCPs by group",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Group ID",
+                        "name": "group",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Page",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Per Page",
+                        "name": "per_page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Type",
+                        "name": "type",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Keyword",
+                        "name": "keyword",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/middleware.APIResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/controller.GroupPublicMCPResponse"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/api/group/{group}/model_config/{model}": {
             "get": {
                 "security": [
@@ -8229,6 +8303,71 @@
                 }
             }
         },
+        "controller.GroupPublicMCPResponse": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "type": "string"
+                },
+                "embed_config": {
+                    "$ref": "#/definitions/model.MCPEmbeddingConfig"
+                },
+                "github_url": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "string"
+                },
+                "logo_url": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "openapi_config": {
+                    "$ref": "#/definitions/model.MCPOpenAPIConfig"
+                },
+                "params": {
+                    "type": "object",
+                    "additionalProperties": {
+                        "type": "string"
+                    }
+                },
+                "price": {
+                    "$ref": "#/definitions/model.MCPPrice"
+                },
+                "proxy_config": {
+                    "$ref": "#/definitions/model.PublicMCPProxyConfig"
+                },
+                "readme": {
+                    "type": "string"
+                },
+                "readme_url": {
+                    "type": "string"
+                },
+                "reusing": {
+                    "type": "object",
+                    "additionalProperties": {
+                        "$ref": "#/definitions/model.ReusingParam"
+                    }
+                },
+                "status": {
+                    "$ref": "#/definitions/model.PublicMCPStatus"
+                },
+                "tags": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "type": {
+                    "$ref": "#/definitions/model.PublicMCPType"
+                },
+                "update_at": {
+                    "type": "string"
+                }
+            }
+        },
         "controller.GroupResponse": {
             "type": "object",
             "properties": {
@@ -9836,25 +9975,11 @@
                 "reusing": {
                     "type": "object",
                     "additionalProperties": {
-                        "$ref": "#/definitions/model.MCPEmbeddingReusingConfig"
+                        "$ref": "#/definitions/model.ReusingParam"
                     }
                 }
             }
         },
-        "model.MCPEmbeddingReusingConfig": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "required": {
-                    "type": "boolean"
-                }
-            }
-        },
         "model.MCPOpenAPIConfig": {
             "type": "object",
             "properties": {
@@ -10274,10 +10399,10 @@
                         "type": "string"
                     }
                 },
-                "reusing_params": {
+                "reusing": {
                     "type": "object",
                     "additionalProperties": {
-                        "$ref": "#/definitions/model.ReusingParam"
+                        "$ref": "#/definitions/model.PublicMCPProxyReusingParam"
                     }
                 },
                 "url": {
@@ -10285,6 +10410,23 @@
                 }
             }
         },
+        "model.PublicMCPProxyReusingParam": {
+            "type": "object",
+            "properties": {
+                "description": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "required": {
+                    "type": "boolean"
+                },
+                "type": {
+                    "$ref": "#/definitions/model.ParamType"
+                }
+            }
+        },
         "model.PublicMCPReusingParam": {
             "type": "object",
             "properties": {
@@ -10297,7 +10439,7 @@
                 "mcp_id": {
                     "type": "string"
                 },
-                "reusing_params": {
+                "params": {
                     "type": "object",
                     "additionalProperties": {
                         "type": "string"
@@ -10466,9 +10608,6 @@
                 },
                 "required": {
                     "type": "boolean"
-                },
-                "type": {
-                    "$ref": "#/definitions/model.ParamType"
                 }
             }
         },

+ 103 - 15
core/docs/swagger.yaml

@@ -228,6 +228,49 @@ definitions:
       update_at:
         type: string
     type: object
+  controller.GroupPublicMCPResponse:
+    properties:
+      created_at:
+        type: string
+      embed_config:
+        $ref: '#/definitions/model.MCPEmbeddingConfig'
+      github_url:
+        type: string
+      id:
+        type: string
+      logo_url:
+        type: string
+      name:
+        type: string
+      openapi_config:
+        $ref: '#/definitions/model.MCPOpenAPIConfig'
+      params:
+        additionalProperties:
+          type: string
+        type: object
+      price:
+        $ref: '#/definitions/model.MCPPrice'
+      proxy_config:
+        $ref: '#/definitions/model.PublicMCPProxyConfig'
+      readme:
+        type: string
+      readme_url:
+        type: string
+      reusing:
+        additionalProperties:
+          $ref: '#/definitions/model.ReusingParam'
+        type: object
+      status:
+        $ref: '#/definitions/model.PublicMCPStatus'
+      tags:
+        items:
+          type: string
+        type: array
+      type:
+        $ref: '#/definitions/model.PublicMCPType'
+      update_at:
+        type: string
+    type: object
   controller.GroupResponse:
     properties:
       accessed_at:
@@ -1329,18 +1372,9 @@ definitions:
         type: object
       reusing:
         additionalProperties:
-          $ref: '#/definitions/model.MCPEmbeddingReusingConfig'
+          $ref: '#/definitions/model.ReusingParam'
         type: object
     type: object
-  model.MCPEmbeddingReusingConfig:
-    properties:
-      description:
-        type: string
-      name:
-        type: string
-      required:
-        type: boolean
-    type: object
   model.MCPOpenAPIConfig:
     properties:
       authorization:
@@ -1642,13 +1676,24 @@ definitions:
         additionalProperties:
           type: string
         type: object
-      reusing_params:
+      reusing:
         additionalProperties:
-          $ref: '#/definitions/model.ReusingParam'
+          $ref: '#/definitions/model.PublicMCPProxyReusingParam'
         type: object
       url:
         type: string
     type: object
+  model.PublicMCPProxyReusingParam:
+    properties:
+      description:
+        type: string
+      name:
+        type: string
+      required:
+        type: boolean
+      type:
+        $ref: '#/definitions/model.ParamType'
+    type: object
   model.PublicMCPReusingParam:
     properties:
       created_at:
@@ -1657,7 +1702,7 @@ definitions:
         type: string
       mcp_id:
         type: string
-      reusing_params:
+      params:
         additionalProperties:
           type: string
         type: object
@@ -1771,8 +1816,6 @@ definitions:
         type: string
       required:
         type: boolean
-      type:
-        $ref: '#/definitions/model.ParamType'
     type: object
   model.StreamOptions:
     properties:
@@ -2860,6 +2903,51 @@ paths:
       summary: Update a group
       tags:
       - group
+  /api/group/{group}/mcp:
+    get:
+      description: Get MCPs by group
+      parameters:
+      - description: Group ID
+        in: path
+        name: group
+        required: true
+        type: string
+      - description: Page
+        in: query
+        name: page
+        type: integer
+      - description: Per Page
+        in: query
+        name: per_page
+        type: integer
+      - description: Type
+        in: query
+        name: type
+        type: string
+      - description: Keyword
+        in: query
+        name: keyword
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            allOf:
+            - $ref: '#/definitions/middleware.APIResponse'
+            - properties:
+                data:
+                  items:
+                    $ref: '#/definitions/controller.GroupPublicMCPResponse'
+                  type: array
+              type: object
+      security:
+      - ApiKeyAuth: []
+      summary: Get MCPs by group
+      tags:
+      - mcp
+      - group
   /api/group/{group}/model_config/{model}:
     delete:
       description: Delete group model config

+ 7 - 7
core/model/cache.go

@@ -605,20 +605,20 @@ func CacheUpdatePublicMCPStatus(mcpID string, status PublicMCPStatus) error {
 }
 
 const (
-	PublicMCPReusingParamCacheKey = "public_mcp_reusing_param:%s:%s" // mcp_id:group_id
+	PublicMCPReusingParamCacheKey = "public_mcp_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"`
+	MCPID   string            `json:"mcp_id"   redis:"m"`
+	GroupID string            `json:"group_id" redis:"g"`
+	Params  map[string]string `json:"params"   redis:"p"`
 }
 
 func (p *PublicMCPReusingParam) ToPublicMCPReusingParamCache() *PublicMCPReusingParamCache {
 	return &PublicMCPReusingParamCache{
-		MCPID:         p.MCPID,
-		GroupID:       p.GroupID,
-		ReusingParams: p.ReusingParams,
+		MCPID:   p.MCPID,
+		GroupID: p.GroupID,
+		Params:  p.Params,
 	}
 }
 

+ 21 - 23
core/model/publicmcp.go

@@ -42,10 +42,9 @@ const (
 )
 
 type ReusingParam struct {
-	Name        string    `json:"name"`
-	Description string    `json:"description"`
-	Type        ParamType `json:"type"`
-	Required    bool      `json:"required"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Required    bool   `json:"required"`
 }
 
 type MCPPrice struct {
@@ -53,20 +52,25 @@ type MCPPrice struct {
 	ToolsCallPrices       map[string]float64 `json:"tools_call_prices"        gorm:"serializer:fastjson;type:text"`
 }
 
+type PublicMCPProxyReusingParam struct {
+	ReusingParam
+	Type ParamType `json:"type"`
+}
+
 type PublicMCPProxyConfig struct {
-	URL           string                  `json:"url"`
-	Querys        map[string]string       `json:"querys"`
-	Headers       map[string]string       `json:"headers"`
-	ReusingParams map[string]ReusingParam `json:"reusing_params"`
+	URL     string                                `json:"url"`
+	Querys  map[string]string                     `json:"querys"`
+	Headers map[string]string                     `json:"headers"`
+	Reusing map[string]PublicMCPProxyReusingParam `json:"reusing"`
 }
 
 type PublicMCPReusingParam struct {
-	MCPID         string            `gorm:"primaryKey"                    json:"mcp_id"`
-	GroupID       string            `gorm:"primaryKey"                    json:"group_id"`
-	CreatedAt     time.Time         `gorm:"index"                         json:"created_at"`
-	UpdateAt      time.Time         `gorm:"index"                         json:"update_at"`
-	Group         *Group            `gorm:"foreignKey:GroupID"            json:"-"`
-	ReusingParams map[string]string `gorm:"serializer:fastjson;type:text" json:"reusing_params"`
+	MCPID     string            `gorm:"primaryKey"                    json:"mcp_id"`
+	GroupID   string            `gorm:"primaryKey"                    json:"group_id"`
+	CreatedAt time.Time         `gorm:"index"                         json:"created_at"`
+	UpdateAt  time.Time         `gorm:"index"                         json:"update_at"`
+	Group     *Group            `gorm:"foreignKey:GroupID"            json:"-"`
+	Params    map[string]string `gorm:"serializer:fastjson;type:text" json:"params"`
 }
 
 func (p *PublicMCPReusingParam) BeforeCreate(_ *gorm.DB) (err error) {
@@ -101,15 +105,9 @@ type MCPOpenAPIConfig struct {
 	Authorization  string `json:"authorization,omitempty"`
 }
 
-type MCPEmbeddingReusingConfig struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Required    bool   `json:"required"`
-}
-
 type MCPEmbeddingConfig struct {
-	Init    map[string]string                    `json:"init"`
-	Reusing map[string]MCPEmbeddingReusingConfig `json:"reusing"`
+	Init    map[string]string       `json:"init"`
+	Reusing map[string]ReusingParam `json:"reusing"`
 }
 
 var validateMCPIDRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
@@ -413,7 +411,7 @@ func UpdatePublicMCPReusingParam(param *PublicMCPReusingParam) (err error) {
 
 	result := DB.
 		Select([]string{
-			"reusing_params",
+			"params",
 		}).
 		Where("mcp_id = ? AND group_id = ?", param.MCPID, param.GroupID).
 		Updates(param)

+ 6 - 1
core/router/api.go

@@ -75,6 +75,11 @@ func SetAPIRouter(router *gin.Engine) {
 				groupModelConfigRoute.DELETE("/:model", controller.DeleteGroupModelConfig)
 				groupModelConfigRoute.GET("/:model", controller.GetGroupModelConfig)
 			}
+
+			groupMcpRoute := groupRoute.Group("/:group/mcp")
+			{
+				groupMcpRoute.GET("/", mcp.GetGroupPublicMCPs)
+			}
 		}
 
 		optionRoute := apiRouter.Group("/option")
@@ -184,7 +189,7 @@ func SetAPIRouter(router *gin.Engine) {
 		{
 			publicMcpRoute.GET("/", mcp.GetPublicMCPs)
 			publicMcpRoute.GET("/all", mcp.GetAllPublicMCPs)
-			publicMcpRoute.GET("/:id", mcp.GetPublicMCPByIDHandler)
+			publicMcpRoute.GET("/:id", mcp.GetPublicMCPByID)
 			publicMcpRoute.POST("/", mcp.CreatePublicMCP)
 			publicMcpRoute.PUT("/:id", mcp.UpdatePublicMCP)
 			publicMcpRoute.DELETE("/:id", mcp.DeletePublicMCP)

+ 9 - 11
web/src/api/mcp.ts

@@ -1,18 +1,22 @@
 import { get, post, put, del } from './index'
 
-// Types
+type ParamType = 'header' | 'query'
+
+export interface PublicMCPProxyReusingParam extends ReusingParam {
+  type: ParamType
+}
+
 export interface ReusingParam {
   name: string
   description: string
   required: boolean
-  type: 'header' | 'query'
 }
 
 export interface PublicMCPProxyConfig {
   url: string
   querys: Record<string, string>
   headers: Record<string, string>
-  reusing_params: Record<string, ReusingParam>
+  reusing: Record<string, PublicMCPProxyReusingParam>
 }
 
 export interface MCPOpenAPIConfig {
@@ -23,15 +27,9 @@ export interface MCPOpenAPIConfig {
   authorization?: string
 }
 
-export interface MCPEmbeddingReusingConfig {
-  name: string
-  description: string
-  required: boolean
-}
-
 export interface MCPEmbeddingConfig {
   init: Record<string, string>
-  reusing: Record<string, MCPEmbeddingReusingConfig>
+  reusing: Record<string, ReusingParam>
 }
 
 export interface PublicMCP {
@@ -84,7 +82,7 @@ export interface SaveEmbedMCPRequest {
 export interface PublicMCPReusingParam {
   mcp_id: string
   group_id: string
-  reusing_params: Record<string, string>
+  params: Record<string, string>
 }
 
 // API functions

+ 1 - 1
web/src/pages/mcp/components/MCPConfig.tsx

@@ -361,7 +361,7 @@ const MCPConfig = () => {
             url: "",
             headers: {},
             querys: {},
-            reusing_params: {},
+            reusing: {},
           };
         }
       } else if (type === "mcp_openapi") {

+ 9 - 9
web/src/pages/mcp/components/config/ProxyConfig.tsx

@@ -1,5 +1,5 @@
 import { useState } from 'react'
-import { PublicMCPProxyConfig, ReusingParam } from '@/api/mcp'
+import { PublicMCPProxyConfig, PublicMCPProxyReusingParam } from '@/api/mcp'
 import { Label } from '@/components/ui/label'
 import { Input } from '@/components/ui/input'
 import { Button } from '@/components/ui/button'
@@ -20,7 +20,7 @@ const ProxyConfig = ({ config, onChange }: ProxyConfigProps) => {
       url: '',
       headers: {},
       querys: {},
-      reusing_params: {}
+      reusing: {}
     }
   )
 
@@ -30,7 +30,7 @@ const ProxyConfig = ({ config, onChange }: ProxyConfigProps) => {
   const [newQueryKey, setNewQueryKey] = useState('')
   const [newQueryValue, setNewQueryValue] = useState('')
   const [newReusingKey, setNewReusingKey] = useState('')
-  const [newReusingParam, setNewReusingParam] = useState<ReusingParam>({
+  const [newReusingParam, setNewReusingParam] = useState<PublicMCPProxyReusingParam>({
     name: '',
     description: '',
     required: false,
@@ -111,13 +111,13 @@ const ProxyConfig = ({ config, onChange }: ProxyConfigProps) => {
     if (!newReusingKey.trim() || !newReusingParam.name.trim()) return
     
     const newReusingParams = { 
-      ...proxyConfig.reusing_params, 
+      ...proxyConfig.reusing, 
       [newReusingKey]: { ...newReusingParam } 
     }
     
     const newConfig = { 
       ...proxyConfig, 
-      reusing_params: newReusingParams 
+      reusing: newReusingParams 
     }
     
     setProxyConfig(newConfig)
@@ -132,12 +132,12 @@ const ProxyConfig = ({ config, onChange }: ProxyConfigProps) => {
   }
 
   const removeReusingParam = (key: string) => {
-    const newReusingParams = { ...proxyConfig.reusing_params }
+    const newReusingParams = { ...proxyConfig.reusing }
     delete newReusingParams[key]
     
     const newConfig = { 
       ...proxyConfig, 
-      reusing_params: newReusingParams 
+      reusing: newReusingParams 
     }
     
     setProxyConfig(newConfig)
@@ -302,13 +302,13 @@ const ProxyConfig = ({ config, onChange }: ProxyConfigProps) => {
             </Button>
           </div>
 
-          {Object.keys(proxyConfig.reusing_params).length === 0 ? (
+          {Object.keys(proxyConfig.reusing).length === 0 ? (
             <div className="text-center text-muted-foreground py-4">
               No reusing parameters configured
             </div>
           ) : (
             <div className="space-y-2">
-              {Object.entries(proxyConfig.reusing_params).map(([key, param]) => (
+              {Object.entries(proxyConfig.reusing).map(([key, param]) => (
                 <Card key={key}>
                   <CardHeader>
                     <CardTitle className="text-base flex justify-between">