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

feat: add group mcp detail and endpoint (#266)

* feat: add group mcp detail and endpoint

* fix: ci lint

* feat: add is hosted filed

* chore: readme omit empty
zijiren 6 месяцев назад
Родитель
Сommit
bf9a7e28b8

+ 53 - 9
core/controller/mcp/embedmcp.go

@@ -60,6 +60,7 @@ type EmbedMCP struct {
 	ID              string                    `json:"id"`
 	ID              string                    `json:"id"`
 	Enabled         bool                      `json:"enabled"`
 	Enabled         bool                      `json:"enabled"`
 	Name            string                    `json:"name"`
 	Name            string                    `json:"name"`
+	NameCN          string                    `json:"name_cn"`
 	Readme          string                    `json:"readme"`
 	Readme          string                    `json:"readme"`
 	ReadmeURL       string                    `json:"readme_url"`
 	ReadmeURL       string                    `json:"readme_url"`
 	ReadmeCN        string                    `json:"readme_cn"`
 	ReadmeCN        string                    `json:"readme_cn"`
@@ -79,6 +80,7 @@ func newEmbedMCP(
 		ID:          mcp.ID,
 		ID:          mcp.ID,
 		Enabled:     enabled,
 		Enabled:     enabled,
 		Name:        mcp.Name,
 		Name:        mcp.Name,
+		NameCN:      mcp.NameCN,
 		Readme:      mcp.Readme,
 		Readme:      mcp.Readme,
 		ReadmeURL:   mcp.ReadmeURL,
 		ReadmeURL:   mcp.ReadmeURL,
 		ReadmeCN:    mcp.ReadmeCN,
 		ReadmeCN:    mcp.ReadmeCN,
@@ -220,22 +222,48 @@ func GetProxyConfig(
 			value = param.Default
 			value = param.Default
 		}
 		}
 
 
-		switch param.Type {
-		case model.ParamTypeURL:
+		switch param.Required {
+		case mcpservers.ConfigRequiredTypeInitOnly:
+			// 必须在初始化时提供
 			if value == "" {
 			if value == "" {
-				return nil, fmt.Errorf("url parameter %s is required", key)
+				return nil, fmt.Errorf("parameter %s is required", key)
 			}
 			}
-			config.URL = value
-		case model.ParamTypeHeader:
+			applyParamToConfig(config, key, value, param.Type)
+		case mcpservers.ConfigRequiredTypeReusingOnly:
+			// 只能通过 reusing 提供,不能在初始化时提供
 			if value != "" {
 			if value != "" {
-				config.Headers[key] = value
+				return nil, fmt.Errorf(
+					"parameter %s should not be provided in init config, it should be provided via reusing",
+					key,
+				)
+			}
+			config.Reusing[key] = model.PublicMCPProxyReusingParam{
+				ReusingParam: model.ReusingParam{
+					Name:        param.Name,
+					Description: param.Description,
+					Required:    true,
+				},
+				Type: param.Type,
 			}
 			}
-		case model.ParamTypeQuery:
+		case mcpservers.ConfigRequiredTypeInitOrReusingOnly:
+			// 可以在初始化时提供,也可以通过 reusing 提供
 			if value != "" {
 			if value != "" {
-				config.Querys[key] = value
+				applyParamToConfig(config, key, value, param.Type)
+			} else {
+				config.Reusing[key] = model.PublicMCPProxyReusingParam{
+					ReusingParam: model.ReusingParam{
+						Name:        param.Name,
+						Description: param.Description,
+						Required:    true,
+					},
+					Type: param.Type,
+				}
 			}
 			}
 		default:
 		default:
-			return nil, fmt.Errorf("unsupported proxy param type: %s", param.Type)
+			// 可选参数
+			if value != "" {
+				applyParamToConfig(config, key, value, param.Type)
+			}
 		}
 		}
 	}
 	}
 
 
@@ -246,6 +274,22 @@ func GetProxyConfig(
 	return config, nil
 	return config, nil
 }
 }
 
 
+// 辅助函数:将参数应用到配置中
+func applyParamToConfig(
+	config *model.PublicMCPProxyConfig,
+	key, value string,
+	paramType model.ProxyParamType,
+) {
+	switch paramType {
+	case model.ParamTypeURL:
+		config.URL = value
+	case model.ParamTypeHeader:
+		config.Headers[key] = value
+	case model.ParamTypeQuery:
+		config.Querys[key] = value
+	}
+}
+
 func ToPublicMCP(
 func ToPublicMCP(
 	e mcpservers.McpServer,
 	e mcpservers.McpServer,
 	initConfig map[string]string,
 	initConfig map[string]string,

+ 23 - 18
core/controller/mcp/mcp.go

@@ -82,32 +82,37 @@ func handleEmbedSSEMCP(
 	config *model.MCPEmbeddingConfig,
 	config *model.MCPEmbeddingConfig,
 	endpoint EndpointProvider,
 	endpoint EndpointProvider,
 ) {
 ) {
-	var reusingConfig map[string]string
-	if len(config.Reusing) != 0 {
-		group := middleware.GetGroup(c)
-		param, err := model.CacheGetPublicMCPReusingParam(mcpID, group.ID)
-		if err != nil {
-			c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-				mcp.NewRequestId(nil),
-				mcp.INVALID_REQUEST,
-				err.Error(),
-			))
-			return
-		}
-		reusingConfig = param.Params
+	reusingConfig, err := prepareEmbedReusingConfig(c, mcpID, config.Reusing)
+	if err != nil {
+		http.Error(c.Writer, err.Error(), http.StatusBadRequest)
+		return
 	}
 	}
+
 	server, err := mcpservers.GetMCPServer(mcpID, config.Init, reusingConfig)
 	server, err := mcpservers.GetMCPServer(mcpID, config.Init, reusingConfig)
 	if err != nil {
 	if err != nil {
-		c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-			mcp.NewRequestId(nil),
-			mcp.INVALID_REQUEST,
-			err.Error(),
-		))
+		http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 		return
 		return
 	}
 	}
+
 	handleSSEMCPServer(c, server, string(model.PublicMCPTypeEmbed), endpoint)
 	handleSSEMCPServer(c, server, string(model.PublicMCPTypeEmbed), endpoint)
 }
 }
 
 
+// prepareEmbedReusingConfig 准备嵌入MCP的reusing配置
+func prepareEmbedReusingConfig(
+	c *gin.Context,
+	mcpID string,
+	reusingParams map[string]model.ReusingParam,
+) (map[string]string, error) {
+	if len(reusingParams) == 0 {
+		return nil, nil
+	}
+
+	group := middleware.GetGroup(c)
+	processor := NewReusingParamProcessor(mcpID, group.ID)
+
+	return processor.ProcessEmbedReusingParams(reusingParams)
+}
+
 func sendMCPSSEMessage(c *gin.Context, mcpType, sessionID string) {
 func sendMCPSSEMessage(c *gin.Context, mcpType, sessionID string) {
 	backend, ok := getStore().Get(sessionID)
 	backend, ok := getStore().Get(sessionID)
 	if !ok || backend != mcpType {
 	if !ok || backend != mcpType {

+ 123 - 31
core/controller/mcp/publicmcp-group.go

@@ -9,22 +9,80 @@ import (
 	"github.com/labring/aiproxy/core/controller/utils"
 	"github.com/labring/aiproxy/core/controller/utils"
 	"github.com/labring/aiproxy/core/middleware"
 	"github.com/labring/aiproxy/core/middleware"
 	"github.com/labring/aiproxy/core/model"
 	"github.com/labring/aiproxy/core/model"
+	"github.com/mark3labs/mcp-go/mcp"
 	"gorm.io/gorm"
 	"gorm.io/gorm"
 )
 )
 
 
+func IsHostedMCP(t model.PublicMCPType) bool {
+	return t == model.PublicMCPTypeEmbed ||
+		t == model.PublicMCPTypeOpenAPI ||
+		t == model.PublicMCPTypeProxySSE ||
+		t == model.PublicMCPTypeProxyStreamable
+}
+
 type GroupPublicMCPResponse struct {
 type GroupPublicMCPResponse struct {
 	model.PublicMCP
 	model.PublicMCP
+	Hosted bool `json:"hosted"`
+}
+
+func (r *GroupPublicMCPResponse) MarshalJSON() ([]byte, error) {
+	type Alias GroupPublicMCPResponse
+	a := &struct {
+		*Alias
+		CreatedAt int64 `json:"created_at,omitempty"`
+		UpdateAt  int64 `json:"update_at,omitempty"`
+	}{
+		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(mcp model.PublicMCP) GroupPublicMCPResponse {
+	r := GroupPublicMCPResponse{
+		PublicMCP: mcp,
+		Hosted:    IsHostedMCP(mcp.Type),
+	}
+	r.Type = ""
+	r.Readme = ""
+	r.ReadmeCN = ""
+	r.ReadmeURL = ""
+	r.ReadmeCNURL = ""
+	r.ProxyConfig = nil
+	r.EmbedConfig = nil
+	r.OpenAPIConfig = nil
+	r.TestConfig = nil
+	return r
+}
+
+func NewGroupPublicMCPResponses(mcps []model.PublicMCP) []GroupPublicMCPResponse {
+	responses := make([]GroupPublicMCPResponse, len(mcps))
+	for i, mcp := range mcps {
+		responses[i] = NewGroupPublicMCPResponse(mcp)
+	}
+	return responses
+}
+
+type GroupPublicMCPDetailResponse struct {
+	model.PublicMCP
+	Hosted    bool                          `json:"hosted"`
 	Endpoints MCPEndpoint                   `json:"endpoints"`
 	Endpoints MCPEndpoint                   `json:"endpoints"`
 	Reusing   map[string]model.ReusingParam `json:"reusing"`
 	Reusing   map[string]model.ReusingParam `json:"reusing"`
 	Params    map[string]string             `json:"params"`
 	Params    map[string]string             `json:"params"`
+	Tools     []mcp.Tool                    `json:"tools"`
 }
 }
 
 
-func (r *GroupPublicMCPResponse) MarshalJSON() ([]byte, error) {
-	type Alias GroupPublicMCPResponse
+func (r *GroupPublicMCPDetailResponse) MarshalJSON() ([]byte, error) {
+	type Alias GroupPublicMCPDetailResponse
 	a := &struct {
 	a := &struct {
 		*Alias
 		*Alias
-		CreatedAt int64 `json:"created_at"`
-		UpdateAt  int64 `json:"update_at"`
+		CreatedAt int64 `json:"created_at,omitempty"`
+		UpdateAt  int64 `json:"update_at,omitempty"`
 	}{
 	}{
 		Alias: (*Alias)(r),
 		Alias: (*Alias)(r),
 	}
 	}
@@ -37,18 +95,33 @@ func (r *GroupPublicMCPResponse) MarshalJSON() ([]byte, error) {
 	return sonic.Marshal(a)
 	return sonic.Marshal(a)
 }
 }
 
 
-func NewGroupPublicMCPResponse(
+func checkParamsIsFull(params model.Params, reusing map[string]model.ReusingParam) bool {
+	for _, r := range reusing {
+		if !r.Required {
+			continue
+		}
+		if v, ok := params[r.Name]; !ok || v == "" {
+			return false
+		}
+	}
+	return true
+}
+
+func NewGroupPublicMCPDetailResponse(
 	host string,
 	host string,
 	mcp model.PublicMCP,
 	mcp model.PublicMCP,
 	groupID string,
 	groupID string,
-) (GroupPublicMCPResponse, error) {
-	r := GroupPublicMCPResponse{
+) (GroupPublicMCPDetailResponse, error) {
+	r := GroupPublicMCPDetailResponse{
 		PublicMCP: mcp,
 		PublicMCP: mcp,
-		Endpoints: NewPublicMCPEndpoint(host, mcp),
+		Hosted:    IsHostedMCP(mcp.Type),
 	}
 	}
+
+	r.Type = ""
 	r.ProxyConfig = nil
 	r.ProxyConfig = nil
 	r.EmbedConfig = nil
 	r.EmbedConfig = nil
 	r.OpenAPIConfig = nil
 	r.OpenAPIConfig = nil
+	r.TestConfig = nil
 
 
 	switch mcp.Type {
 	switch mcp.Type {
 	case model.PublicMCPTypeProxySSE, model.PublicMCPTypeProxyStreamable:
 	case model.PublicMCPTypeProxySSE, model.PublicMCPTypeProxyStreamable:
@@ -57,6 +130,8 @@ func NewGroupPublicMCPResponse(
 		}
 		}
 	case model.PublicMCPTypeEmbed:
 	case model.PublicMCPTypeEmbed:
 		r.Reusing = mcp.EmbedConfig.Reusing
 		r.Reusing = mcp.EmbedConfig.Reusing
+	default:
+		return r, nil
 	}
 	}
 
 
 	reusingParams, err := model.GetPublicMCPReusingParam(mcp.ID, groupID)
 	reusingParams, err := model.GetPublicMCPReusingParam(mcp.ID, groupID)
@@ -67,23 +142,11 @@ func NewGroupPublicMCPResponse(
 	}
 	}
 	r.Params = reusingParams.Params
 	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
+	if checkParamsIsFull(r.Params, r.Reusing) {
+		r.Endpoints = NewPublicMCPEndpoint(host, mcp)
 	}
 	}
-	return responses, nil
+
+	return r, nil
 }
 }
 
 
 // GetGroupPublicMCPs godoc
 // GetGroupPublicMCPs godoc
@@ -102,8 +165,6 @@ func NewGroupPublicMCPResponses(
 //	@Success		200			{object}	middleware.APIResponse{data=[]GroupPublicMCPResponse}
 //	@Success		200			{object}	middleware.APIResponse{data=[]GroupPublicMCPResponse}
 //	@Router			/api/group/{group}/mcp [get]
 //	@Router			/api/group/{group}/mcp [get]
 func GetGroupPublicMCPs(c *gin.Context) {
 func GetGroupPublicMCPs(c *gin.Context) {
-	groupID := c.Param("group")
-
 	page, perPage := utils.ParsePageParams(c)
 	page, perPage := utils.ParsePageParams(c)
 	mcpType := model.PublicMCPType(c.Query("type"))
 	mcpType := model.PublicMCPType(c.Query("type"))
 	keyword := c.Query("keyword")
 	keyword := c.Query("keyword")
@@ -120,14 +181,45 @@ func GetGroupPublicMCPs(c *gin.Context) {
 		return
 		return
 	}
 	}
 
 
-	responses, err := NewGroupPublicMCPResponses(c.Request.Host, mcps, groupID)
-	if err != nil {
-		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
-		return
-	}
+	responses := NewGroupPublicMCPResponses(mcps)
 
 
 	middleware.SuccessResponse(c, gin.H{
 	middleware.SuccessResponse(c, gin.H{
 		"mcps":  responses,
 		"mcps":  responses,
 		"total": total,
 		"total": total,
 	})
 	})
 }
 }
+
+// GetGroupPublicMCPByID godoc
+//
+//	@Summary		Get MCP by ID
+//	@Description	Get a specific MCP by its ID
+//	@Tags			mcp
+//	@Tags			group
+//	@Produce		json
+//	@Security		ApiKeyAuth
+//	@Param			group	path		string	true	"Group ID"
+//	@Param			id		path		string	true	"MCP ID"
+//	@Success		200		{object}	middleware.APIResponse{data=GroupPublicMCPDetailResponse}
+//	@Router			/api/group/{group}/mcp/{id} [get]
+func GetGroupPublicMCPByID(c *gin.Context) {
+	groupID := c.Param("group")
+	id := c.Param("id")
+	if id == "" {
+		middleware.ErrorResponse(c, http.StatusBadRequest, "MCP ID is required")
+		return
+	}
+
+	mcp, err := model.GetPublicMCPByID(id)
+	if err != nil {
+		middleware.ErrorResponse(c, http.StatusNotFound, err.Error())
+		return
+	}
+
+	response, err := NewGroupPublicMCPDetailResponse(c.Request.Host, mcp, groupID)
+	if err != nil {
+		middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	middleware.SuccessResponse(c, response)
+}

+ 136 - 69
core/controller/mcp/publicmcp-server.go

@@ -1,7 +1,6 @@
 package controller
 package controller
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
@@ -76,72 +75,153 @@ func handlePublicSSEMCP(
 ) {
 ) {
 	switch publicMcp.Type {
 	switch publicMcp.Type {
 	case model.PublicMCPTypeProxySSE:
 	case model.PublicMCPTypeProxySSE:
-		client, err := transport.NewSSE(
-			publicMcp.ProxyConfig.URL,
-			transport.WithHeaders(publicMcp.ProxyConfig.Headers),
-		)
-		if err != nil {
+		if err := handlePublicProxySSE(c, publicMcp, endpoint); err != nil {
 			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			return
 			return
 		}
 		}
-		err = client.Start(c.Request.Context())
-		if err != nil {
-			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
-			return
-		}
-		defer client.Close()
-		handleSSEMCPServer(
-			c,
-			mcpservers.WrapMCPClient2Server(client),
-			string(model.PublicMCPTypeProxySSE),
-			endpoint,
-		)
 	case model.PublicMCPTypeProxyStreamable:
 	case model.PublicMCPTypeProxyStreamable:
-		client, err := transport.NewStreamableHTTP(
-			publicMcp.ProxyConfig.URL,
-			transport.WithHTTPHeaders(publicMcp.ProxyConfig.Headers),
-		)
-		if err != nil {
+		if err := handlePublicProxyStreamableSSE(c, publicMcp, endpoint); err != nil {
 			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			return
 			return
 		}
 		}
-		err = client.Start(c.Request.Context())
-		if err != nil {
-			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
-			return
-		}
-		defer client.Close()
-		handleSSEMCPServer(
-			c,
-			mcpservers.WrapMCPClient2Server(client),
-			string(model.PublicMCPTypeProxyStreamable),
-			endpoint,
-		)
 	case model.PublicMCPTypeOpenAPI:
 	case model.PublicMCPTypeOpenAPI:
 		server, err := newOpenAPIMCPServer(publicMcp.OpenAPIConfig)
 		server, err := newOpenAPIMCPServer(publicMcp.OpenAPIConfig)
 		if err != nil {
 		if err != nil {
-			c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-				mcp.NewRequestId(nil),
-				mcp.INVALID_REQUEST,
-				err.Error(),
-			))
+			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			return
 			return
 		}
 		}
 		handleSSEMCPServer(c, server, string(model.PublicMCPTypeOpenAPI), endpoint)
 		handleSSEMCPServer(c, server, string(model.PublicMCPTypeOpenAPI), endpoint)
 	case model.PublicMCPTypeEmbed:
 	case model.PublicMCPTypeEmbed:
 		handleEmbedSSEMCP(c, publicMcp.ID, publicMcp.EmbedConfig, endpoint)
 		handleEmbedSSEMCP(c, publicMcp.ID, publicMcp.EmbedConfig, endpoint)
 	default:
 	default:
-		c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-			mcp.NewRequestId(nil),
-			mcp.INVALID_REQUEST,
-			"unknown mcp type",
-		))
-		return
+		http.Error(c.Writer, "unknown mcp type", http.StatusBadRequest)
 	}
 	}
 }
 }
 
 
-// processReusingParams handles the reusing parameters for MCP proxy
-func processReusingParams(
+// handlePublicProxySSE 处理公共代理SSE
+func handlePublicProxySSE(
+	c *gin.Context,
+	publicMcp *model.PublicMCPCache,
+	endpoint EndpointProvider,
+) error {
+	client, err := createProxySSEClient(c, publicMcp)
+	if err != nil {
+		return err
+	}
+	defer client.Close()
+
+	handleSSEMCPServer(
+		c,
+		mcpservers.WrapMCPClient2Server(client),
+		string(model.PublicMCPTypeProxySSE),
+		endpoint,
+	)
+	return nil
+}
+
+// handlePublicProxyStreamableSSE 处理公共代理Streamable SSE
+func handlePublicProxyStreamableSSE(
+	c *gin.Context,
+	publicMcp *model.PublicMCPCache,
+	endpoint EndpointProvider,
+) error {
+	client, err := createProxyStreamableClient(c, publicMcp)
+	if err != nil {
+		return err
+	}
+	defer client.Close()
+
+	handleSSEMCPServer(
+		c,
+		mcpservers.WrapMCPClient2Server(client),
+		string(model.PublicMCPTypeProxyStreamable),
+		endpoint,
+	)
+	return nil
+}
+
+// createProxySSEClient 创建代理SSE客户端
+func createProxySSEClient(
+	c *gin.Context,
+	publicMcp *model.PublicMCPCache,
+) (transport.Interface, error) {
+	url, headers, err := prepareProxyConfig(c, publicMcp)
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := transport.NewSSE(url, transport.WithHeaders(headers))
+	if err != nil {
+		return nil, err
+	}
+
+	if err := client.Start(c.Request.Context()); err != nil {
+		return nil, err
+	}
+
+	return client, nil
+}
+
+// createProxyStreamableClient 创建代理Streamable客户端
+func createProxyStreamableClient(
+	c *gin.Context,
+	publicMcp *model.PublicMCPCache,
+) (transport.Interface, error) {
+	url, headers, err := prepareProxyConfig(c, publicMcp)
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := transport.NewStreamableHTTP(url, transport.WithHTTPHeaders(headers))
+	if err != nil {
+		return nil, err
+	}
+
+	if err := client.Start(c.Request.Context()); err != nil {
+		return nil, err
+	}
+
+	return client, nil
+}
+
+// prepareProxyConfig 准备代理配置
+func prepareProxyConfig(
+	c *gin.Context,
+	publicMcp *model.PublicMCPCache,
+) (string, map[string]string, error) {
+	url, err := url.Parse(publicMcp.ProxyConfig.URL)
+	if err != nil {
+		return "", nil, fmt.Errorf("invalid proxy URL: %w", err)
+	}
+
+	headers := make(map[string]string)
+	backendQuery := url.Query()
+
+	// 复制静态配置
+	for k, v := range publicMcp.ProxyConfig.Headers {
+		headers[k] = v
+	}
+
+	// 处理reusing参数
+	if len(publicMcp.ProxyConfig.Reusing) > 0 {
+		group := middleware.GetGroup(c)
+		processor := NewReusingParamProcessor(publicMcp.ID, group.ID)
+
+		if err := processor.ProcessProxyReusingParams(
+			publicMcp.ProxyConfig.Reusing,
+			headers,
+			&backendQuery,
+		); err != nil {
+			return "", nil, err
+		}
+	}
+
+	url.RawQuery = backendQuery.Encode()
+	return url.String(), headers, nil
+}
+
+// processProxyReusingParams handles the reusing parameters for MCP proxy
+func processProxyReusingParams(
 	reusingParams map[string]model.PublicMCPProxyReusingParam,
 	reusingParams map[string]model.PublicMCPProxyReusingParam,
 	mcpID, groupID string,
 	mcpID, groupID string,
 	headers map[string]string,
 	headers map[string]string,
@@ -160,7 +240,7 @@ func processReusingParams(
 		paramValue, ok := param.Params[k]
 		paramValue, ok := param.Params[k]
 		if !ok {
 		if !ok {
 			if v.Required {
 			if v.Required {
-				return fmt.Errorf("%s required", k)
+				return fmt.Errorf("required reusing parameter %s is missing", k)
 			}
 			}
 			continue
 			continue
 		}
 		}
@@ -170,8 +250,10 @@ func processReusingParams(
 			headers[k] = paramValue
 			headers[k] = paramValue
 		case model.ParamTypeQuery:
 		case model.ParamTypeQuery:
 			backendQuery.Set(k, paramValue)
 			backendQuery.Set(k, paramValue)
+		case model.ParamTypeURL:
+			return fmt.Errorf("URL parameter %s cannot be set via reusing", k)
 		default:
 		default:
-			return errors.New("unknow param type")
+			return fmt.Errorf("unknown param type: %s", v.Type)
 		}
 		}
 	}
 	}
 
 
@@ -247,28 +329,13 @@ func PublicMCPStreamable(c *gin.Context) {
 func handlePublicStreamable(c *gin.Context, publicMcp *model.PublicMCPCache) {
 func handlePublicStreamable(c *gin.Context, publicMcp *model.PublicMCPCache) {
 	switch publicMcp.Type {
 	switch publicMcp.Type {
 	case model.PublicMCPTypeProxySSE:
 	case model.PublicMCPTypeProxySSE:
-		client, err := transport.NewSSE(
-			publicMcp.ProxyConfig.URL,
-			transport.WithHeaders(publicMcp.ProxyConfig.Headers),
-		)
-		if err != nil {
-			c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-				mcp.NewRequestId(nil),
-				mcp.INVALID_REQUEST,
-				err.Error(),
-			))
-			return
-		}
-		err = client.Start(c.Request.Context())
+		client, err := createProxySSEClient(c, publicMcp)
 		if err != nil {
 		if err != nil {
-			c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
-				mcp.NewRequestId(nil),
-				mcp.INVALID_REQUEST,
-				err.Error(),
-			))
+			http.Error(c.Writer, err.Error(), http.StatusBadRequest)
 			return
 			return
 		}
 		}
 		defer client.Close()
 		defer client.Close()
+
 		mcpproxy.NewStatelessStreamableHTTPServer(
 		mcpproxy.NewStatelessStreamableHTTPServer(
 			mcpservers.WrapMCPClient2Server(client),
 			mcpservers.WrapMCPClient2Server(client),
 		).ServeHTTP(c.Writer, c.Request)
 		).ServeHTTP(c.Writer, c.Request)
@@ -349,7 +416,7 @@ func handlePublicProxyStreamable(c *gin.Context, mcpID string, config *model.Pub
 	group := middleware.GetGroup(c)
 	group := middleware.GetGroup(c)
 
 
 	// Process reusing parameters if any
 	// Process reusing parameters if any
-	if err := processReusingParams(config.Reusing, mcpID, group.ID, headers, &backendQuery); err != nil {
+	if err := processProxyReusingParams(config.Reusing, mcpID, group.ID, headers, &backendQuery); err != nil {
 		c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
 		c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
 			mcp.NewRequestId(nil),
 			mcp.NewRequestId(nil),
 			mcp.INVALID_REQUEST,
 			mcp.INVALID_REQUEST,

+ 101 - 0
core/controller/mcp/reusing.go

@@ -0,0 +1,101 @@
+package controller
+
+import (
+	"fmt"
+	"net/url"
+
+	"github.com/labring/aiproxy/core/model"
+)
+
+// ReusingParamProcessor 统一处理reusing参数
+type ReusingParamProcessor struct {
+	mcpID   string
+	groupID string
+}
+
+func NewReusingParamProcessor(mcpID, groupID string) *ReusingParamProcessor {
+	return &ReusingParamProcessor{
+		mcpID:   mcpID,
+		groupID: groupID,
+	}
+}
+
+// ProcessProxyReusingParams 处理代理类型的reusing参数
+func (p *ReusingParamProcessor) ProcessProxyReusingParams(
+	reusingParams map[string]model.PublicMCPProxyReusingParam,
+	headers map[string]string,
+	backendQuery *url.Values,
+) error {
+	if len(reusingParams) == 0 {
+		return nil
+	}
+
+	param, err := model.CacheGetPublicMCPReusingParam(p.mcpID, p.groupID)
+	if err != nil {
+		return fmt.Errorf("failed to get reusing params: %w", err)
+	}
+
+	for key, config := range reusingParams {
+		value, exists := param.Params[key]
+		if !exists {
+			if config.Required {
+				return fmt.Errorf("required reusing parameter %s is missing", key)
+			}
+			continue
+		}
+
+		if err := p.applyProxyParam(key, value, config.Type, headers, backendQuery); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// ProcessEmbedReusingParams 处理嵌入类型的reusing参数
+func (p *ReusingParamProcessor) ProcessEmbedReusingParams(
+	reusingParams map[string]model.ReusingParam,
+) (map[string]string, error) {
+	if len(reusingParams) == 0 {
+		return nil, nil
+	}
+
+	param, err := model.CacheGetPublicMCPReusingParam(p.mcpID, p.groupID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get reusing params: %w", err)
+	}
+
+	reusingConfig := make(map[string]string)
+	for key, config := range reusingParams {
+		value, exists := param.Params[key]
+		if !exists {
+			if config.Required {
+				return nil, fmt.Errorf("required reusing parameter %s is missing", key)
+			}
+			continue
+		}
+		reusingConfig[key] = value
+	}
+
+	return reusingConfig, nil
+}
+
+// applyProxyParam 应用代理参数到相应位置
+func (p *ReusingParamProcessor) applyProxyParam(
+	key, value string,
+	paramType model.ProxyParamType,
+	headers map[string]string,
+	backendQuery *url.Values,
+) error {
+	switch paramType {
+	case model.ParamTypeHeader:
+		headers[key] = value
+	case model.ParamTypeQuery:
+		backendQuery.Set(key, value)
+	case model.ParamTypeURL:
+		return fmt.Errorf("URL parameter %s cannot be set via reusing", key)
+	default:
+		return fmt.Errorf("unknown param type: %s", paramType)
+	}
+	return nil
+}

+ 243 - 5
core/docs/docs.go

@@ -1619,6 +1619,60 @@ const docTemplate = `{
                 }
                 }
             }
             }
         },
         },
+        "/api/group/{group}/mcp/{id}": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "Get a specific MCP by its ID",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "mcp",
+                    "group"
+                ],
+                "summary": "Get MCP by ID",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Group ID",
+                        "name": "group",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "MCP ID",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/middleware.APIResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "$ref": "#/definitions/controller.GroupPublicMCPDetailResponse"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/api/group/{group}/model_config/{model}": {
         "/api/group/{group}/model_config/{model}": {
             "get": {
             "get": {
                 "security": [
                 "security": [
@@ -8230,6 +8284,9 @@ const docTemplate = `{
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "readme": {
                 "readme": {
                     "type": "string"
                     "type": "string"
                 },
                 },
@@ -8333,7 +8390,7 @@ const docTemplate = `{
                 }
                 }
             }
             }
         },
         },
-        "controller.GroupPublicMCPResponse": {
+        "controller.GroupPublicMCPDetailResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
                 "created_at": {
                 "created_at": {
@@ -8363,6 +8420,9 @@ const docTemplate = `{
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -8405,6 +8465,86 @@ const docTemplate = `{
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
+                "tools": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/mcp.Tool"
+                    }
+                },
+                "type": {
+                    "$ref": "#/definitions/model.PublicMCPType"
+                },
+                "update_at": {
+                    "type": "string"
+                }
+            }
+        },
+        "controller.GroupPublicMCPResponse": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "type": "string"
+                },
+                "description": {
+                    "type": "string"
+                },
+                "description_cn": {
+                    "type": "string"
+                },
+                "embed_config": {
+                    "$ref": "#/definitions/model.MCPEmbeddingConfig"
+                },
+                "github_url": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "string"
+                },
+                "logo_url": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "name_cn": {
+                    "type": "string"
+                },
+                "openapi_config": {
+                    "$ref": "#/definitions/model.MCPOpenAPIConfig"
+                },
+                "price": {
+                    "$ref": "#/definitions/model.MCPPrice"
+                },
+                "proxy_config": {
+                    "$ref": "#/definitions/model.PublicMCPProxyConfig"
+                },
+                "readme": {
+                    "type": "string"
+                },
+                "readme_cn": {
+                    "type": "string"
+                },
+                "readme_cn_url": {
+                    "type": "string"
+                },
+                "readme_url": {
+                    "type": "string"
+                },
+                "status": {
+                    "$ref": "#/definitions/model.PublicMCPStatus"
+                },
+                "tags": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -8576,6 +8716,9 @@ const docTemplate = `{
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -8606,6 +8749,9 @@ const docTemplate = `{
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -8948,6 +9094,78 @@ const docTemplate = `{
                 }
                 }
             }
             }
         },
         },
+        "mcp.Tool": {
+            "type": "object",
+            "properties": {
+                "annotations": {
+                    "description": "Optional properties describing tool behavior",
+                    "allOf": [
+                        {
+                            "$ref": "#/definitions/mcp.ToolAnnotation"
+                        }
+                    ]
+                },
+                "description": {
+                    "description": "A human-readable description of the tool.",
+                    "type": "string"
+                },
+                "inputSchema": {
+                    "description": "A JSON Schema object defining the expected parameters for the tool.",
+                    "allOf": [
+                        {
+                            "$ref": "#/definitions/mcp.ToolInputSchema"
+                        }
+                    ]
+                },
+                "name": {
+                    "description": "The name of the tool.",
+                    "type": "string"
+                }
+            }
+        },
+        "mcp.ToolAnnotation": {
+            "type": "object",
+            "properties": {
+                "destructiveHint": {
+                    "description": "If true, the tool may perform destructive updates",
+                    "type": "boolean"
+                },
+                "idempotentHint": {
+                    "description": "If true, repeated calls with same args have no additional effect",
+                    "type": "boolean"
+                },
+                "openWorldHint": {
+                    "description": "If true, tool interacts with external entities",
+                    "type": "boolean"
+                },
+                "readOnlyHint": {
+                    "description": "If true, the tool does not modify its environment",
+                    "type": "boolean"
+                },
+                "title": {
+                    "description": "Human-readable title for the tool",
+                    "type": "string"
+                }
+            }
+        },
+        "mcp.ToolInputSchema": {
+            "type": "object",
+            "properties": {
+                "properties": {
+                    "type": "object",
+                    "additionalProperties": {}
+                },
+                "required": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "type": {
+                    "type": "string"
+                }
+            }
+        },
         "middleware.APIResponse": {
         "middleware.APIResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
@@ -10251,6 +10469,12 @@ const docTemplate = `{
                 }
                 }
             }
             }
         },
         },
+        "model.Params": {
+            "type": "object",
+            "additionalProperties": {
+                "type": "string"
+            }
+        },
         "model.ParsePdfResponse": {
         "model.ParsePdfResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
@@ -10367,6 +10591,9 @@ const docTemplate = `{
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -10397,6 +10624,9 @@ const docTemplate = `{
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -10461,10 +10691,7 @@ const docTemplate = `{
                     "type": "string"
                     "type": "string"
                 },
                 },
                 "params": {
                 "params": {
-                    "type": "object",
-                    "additionalProperties": {
-                        "type": "string"
-                    }
+                    "$ref": "#/definitions/model.Params"
                 },
                 },
                 "update_at": {
                 "update_at": {
                     "type": "string"
                     "type": "string"
@@ -10701,6 +10928,17 @@ const docTemplate = `{
                 }
                 }
             }
             }
         },
         },
+        "model.TestConfig": {
+            "type": "object",
+            "properties": {
+                "enabled": {
+                    "type": "boolean"
+                },
+                "params": {
+                    "$ref": "#/definitions/model.Params"
+                }
+            }
+        },
         "model.TextResponse": {
         "model.TextResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {

+ 243 - 5
core/docs/swagger.json

@@ -1610,6 +1610,60 @@
                 }
                 }
             }
             }
         },
         },
+        "/api/group/{group}/mcp/{id}": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "description": "Get a specific MCP by its ID",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "mcp",
+                    "group"
+                ],
+                "summary": "Get MCP by ID",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Group ID",
+                        "name": "group",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "MCP ID",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/middleware.APIResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "$ref": "#/definitions/controller.GroupPublicMCPDetailResponse"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "/api/group/{group}/model_config/{model}": {
         "/api/group/{group}/model_config/{model}": {
             "get": {
             "get": {
                 "security": [
                 "security": [
@@ -8221,6 +8275,9 @@
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "readme": {
                 "readme": {
                     "type": "string"
                     "type": "string"
                 },
                 },
@@ -8324,7 +8381,7 @@
                 }
                 }
             }
             }
         },
         },
-        "controller.GroupPublicMCPResponse": {
+        "controller.GroupPublicMCPDetailResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
                 "created_at": {
                 "created_at": {
@@ -8354,6 +8411,9 @@
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -8396,6 +8456,86 @@
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
+                "tools": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/mcp.Tool"
+                    }
+                },
+                "type": {
+                    "$ref": "#/definitions/model.PublicMCPType"
+                },
+                "update_at": {
+                    "type": "string"
+                }
+            }
+        },
+        "controller.GroupPublicMCPResponse": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "type": "string"
+                },
+                "description": {
+                    "type": "string"
+                },
+                "description_cn": {
+                    "type": "string"
+                },
+                "embed_config": {
+                    "$ref": "#/definitions/model.MCPEmbeddingConfig"
+                },
+                "github_url": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "string"
+                },
+                "logo_url": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "name_cn": {
+                    "type": "string"
+                },
+                "openapi_config": {
+                    "$ref": "#/definitions/model.MCPOpenAPIConfig"
+                },
+                "price": {
+                    "$ref": "#/definitions/model.MCPPrice"
+                },
+                "proxy_config": {
+                    "$ref": "#/definitions/model.PublicMCPProxyConfig"
+                },
+                "readme": {
+                    "type": "string"
+                },
+                "readme_cn": {
+                    "type": "string"
+                },
+                "readme_cn_url": {
+                    "type": "string"
+                },
+                "readme_url": {
+                    "type": "string"
+                },
+                "status": {
+                    "$ref": "#/definitions/model.PublicMCPStatus"
+                },
+                "tags": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -8567,6 +8707,9 @@
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -8597,6 +8740,9 @@
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -8939,6 +9085,78 @@
                 }
                 }
             }
             }
         },
         },
+        "mcp.Tool": {
+            "type": "object",
+            "properties": {
+                "annotations": {
+                    "description": "Optional properties describing tool behavior",
+                    "allOf": [
+                        {
+                            "$ref": "#/definitions/mcp.ToolAnnotation"
+                        }
+                    ]
+                },
+                "description": {
+                    "description": "A human-readable description of the tool.",
+                    "type": "string"
+                },
+                "inputSchema": {
+                    "description": "A JSON Schema object defining the expected parameters for the tool.",
+                    "allOf": [
+                        {
+                            "$ref": "#/definitions/mcp.ToolInputSchema"
+                        }
+                    ]
+                },
+                "name": {
+                    "description": "The name of the tool.",
+                    "type": "string"
+                }
+            }
+        },
+        "mcp.ToolAnnotation": {
+            "type": "object",
+            "properties": {
+                "destructiveHint": {
+                    "description": "If true, the tool may perform destructive updates",
+                    "type": "boolean"
+                },
+                "idempotentHint": {
+                    "description": "If true, repeated calls with same args have no additional effect",
+                    "type": "boolean"
+                },
+                "openWorldHint": {
+                    "description": "If true, tool interacts with external entities",
+                    "type": "boolean"
+                },
+                "readOnlyHint": {
+                    "description": "If true, the tool does not modify its environment",
+                    "type": "boolean"
+                },
+                "title": {
+                    "description": "Human-readable title for the tool",
+                    "type": "string"
+                }
+            }
+        },
+        "mcp.ToolInputSchema": {
+            "type": "object",
+            "properties": {
+                "properties": {
+                    "type": "object",
+                    "additionalProperties": {}
+                },
+                "required": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "type": {
+                    "type": "string"
+                }
+            }
+        },
         "middleware.APIResponse": {
         "middleware.APIResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
@@ -10242,6 +10460,12 @@
                 }
                 }
             }
             }
         },
         },
+        "model.Params": {
+            "type": "object",
+            "additionalProperties": {
+                "type": "string"
+            }
+        },
         "model.ParsePdfResponse": {
         "model.ParsePdfResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {
@@ -10358,6 +10582,9 @@
                 "name": {
                 "name": {
                     "type": "string"
                     "type": "string"
                 },
                 },
+                "name_cn": {
+                    "type": "string"
+                },
                 "openapi_config": {
                 "openapi_config": {
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                     "$ref": "#/definitions/model.MCPOpenAPIConfig"
                 },
                 },
@@ -10388,6 +10615,9 @@
                         "type": "string"
                         "type": "string"
                     }
                     }
                 },
                 },
+                "test_config": {
+                    "$ref": "#/definitions/model.TestConfig"
+                },
                 "type": {
                 "type": {
                     "$ref": "#/definitions/model.PublicMCPType"
                     "$ref": "#/definitions/model.PublicMCPType"
                 },
                 },
@@ -10452,10 +10682,7 @@
                     "type": "string"
                     "type": "string"
                 },
                 },
                 "params": {
                 "params": {
-                    "type": "object",
-                    "additionalProperties": {
-                        "type": "string"
-                    }
+                    "$ref": "#/definitions/model.Params"
                 },
                 },
                 "update_at": {
                 "update_at": {
                     "type": "string"
                     "type": "string"
@@ -10692,6 +10919,17 @@
                 }
                 }
             }
             }
         },
         },
+        "model.TestConfig": {
+            "type": "object",
+            "properties": {
+                "enabled": {
+                    "type": "boolean"
+                },
+                "params": {
+                    "$ref": "#/definitions/model.Params"
+                }
+            }
+        },
         "model.TextResponse": {
         "model.TextResponse": {
             "type": "object",
             "type": "object",
             "properties": {
             "properties": {

+ 158 - 4
core/docs/swagger.yaml

@@ -167,6 +167,8 @@ definitions:
         type: string
         type: string
       name:
       name:
         type: string
         type: string
+      name_cn:
+        type: string
       readme:
       readme:
         type: string
         type: string
       readme_cn:
       readme_cn:
@@ -234,7 +236,7 @@ definitions:
       update_at:
       update_at:
         type: string
         type: string
     type: object
     type: object
-  controller.GroupPublicMCPResponse:
+  controller.GroupPublicMCPDetailResponse:
     properties:
     properties:
       created_at:
       created_at:
         type: string
         type: string
@@ -254,6 +256,8 @@ definitions:
         type: string
         type: string
       name:
       name:
         type: string
         type: string
+      name_cn:
+        type: string
       openapi_config:
       openapi_config:
         $ref: '#/definitions/model.MCPOpenAPIConfig'
         $ref: '#/definitions/model.MCPOpenAPIConfig'
       params:
       params:
@@ -282,6 +286,59 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      test_config:
+        $ref: '#/definitions/model.TestConfig'
+      tools:
+        items:
+          $ref: '#/definitions/mcp.Tool'
+        type: array
+      type:
+        $ref: '#/definitions/model.PublicMCPType'
+      update_at:
+        type: string
+    type: object
+  controller.GroupPublicMCPResponse:
+    properties:
+      created_at:
+        type: string
+      description:
+        type: string
+      description_cn:
+        type: string
+      embed_config:
+        $ref: '#/definitions/model.MCPEmbeddingConfig'
+      github_url:
+        type: string
+      id:
+        type: string
+      logo_url:
+        type: string
+      name:
+        type: string
+      name_cn:
+        type: string
+      openapi_config:
+        $ref: '#/definitions/model.MCPOpenAPIConfig'
+      price:
+        $ref: '#/definitions/model.MCPPrice'
+      proxy_config:
+        $ref: '#/definitions/model.PublicMCPProxyConfig'
+      readme:
+        type: string
+      readme_cn:
+        type: string
+      readme_cn_url:
+        type: string
+      readme_url:
+        type: string
+      status:
+        $ref: '#/definitions/model.PublicMCPStatus'
+      tags:
+        items:
+          type: string
+        type: array
+      test_config:
+        $ref: '#/definitions/model.TestConfig'
       type:
       type:
         $ref: '#/definitions/model.PublicMCPType'
         $ref: '#/definitions/model.PublicMCPType'
       update_at:
       update_at:
@@ -394,6 +451,8 @@ definitions:
         type: string
         type: string
       name:
       name:
         type: string
         type: string
+      name_cn:
+        type: string
       openapi_config:
       openapi_config:
         $ref: '#/definitions/model.MCPOpenAPIConfig'
         $ref: '#/definitions/model.MCPOpenAPIConfig'
       price:
       price:
@@ -414,6 +473,8 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      test_config:
+        $ref: '#/definitions/model.TestConfig'
       type:
       type:
         $ref: '#/definitions/model.PublicMCPType'
         $ref: '#/definitions/model.PublicMCPType'
       update_at:
       update_at:
@@ -637,6 +698,54 @@ definitions:
       web_search_count:
       web_search_count:
         type: integer
         type: integer
     type: object
     type: object
+  mcp.Tool:
+    properties:
+      annotations:
+        allOf:
+        - $ref: '#/definitions/mcp.ToolAnnotation'
+        description: Optional properties describing tool behavior
+      description:
+        description: A human-readable description of the tool.
+        type: string
+      inputSchema:
+        allOf:
+        - $ref: '#/definitions/mcp.ToolInputSchema'
+        description: A JSON Schema object defining the expected parameters for the
+          tool.
+      name:
+        description: The name of the tool.
+        type: string
+    type: object
+  mcp.ToolAnnotation:
+    properties:
+      destructiveHint:
+        description: If true, the tool may perform destructive updates
+        type: boolean
+      idempotentHint:
+        description: If true, repeated calls with same args have no additional effect
+        type: boolean
+      openWorldHint:
+        description: If true, tool interacts with external entities
+        type: boolean
+      readOnlyHint:
+        description: If true, the tool does not modify its environment
+        type: boolean
+      title:
+        description: Human-readable title for the tool
+        type: string
+    type: object
+  mcp.ToolInputSchema:
+    properties:
+      properties:
+        additionalProperties: {}
+        type: object
+      required:
+        items:
+          type: string
+        type: array
+      type:
+        type: string
+    type: object
   middleware.APIResponse:
   middleware.APIResponse:
     properties:
     properties:
       data: {}
       data: {}
@@ -1562,6 +1671,10 @@ definitions:
       value:
       value:
         type: string
         type: string
     type: object
     type: object
+  model.Params:
+    additionalProperties:
+      type: string
+    type: object
   model.ParsePdfResponse:
   model.ParsePdfResponse:
     properties:
     properties:
       markdown:
       markdown:
@@ -1642,6 +1755,8 @@ definitions:
         type: string
         type: string
       name:
       name:
         type: string
         type: string
+      name_cn:
+        type: string
       openapi_config:
       openapi_config:
         $ref: '#/definitions/model.MCPOpenAPIConfig'
         $ref: '#/definitions/model.MCPOpenAPIConfig'
       price:
       price:
@@ -1662,6 +1777,8 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      test_config:
+        $ref: '#/definitions/model.TestConfig'
       type:
       type:
         $ref: '#/definitions/model.PublicMCPType'
         $ref: '#/definitions/model.PublicMCPType'
       update_at:
       update_at:
@@ -1704,9 +1821,7 @@ definitions:
       mcp_id:
       mcp_id:
         type: string
         type: string
       params:
       params:
-        additionalProperties:
-          type: string
-        type: object
+        $ref: '#/definitions/model.Params'
       update_at:
       update_at:
         type: string
         type: string
     type: object
     type: object
@@ -1863,6 +1978,13 @@ definitions:
       web_search_count:
       web_search_count:
         type: integer
         type: integer
     type: object
     type: object
+  model.TestConfig:
+    properties:
+      enabled:
+        type: boolean
+      params:
+        $ref: '#/definitions/model.Params'
+    type: object
   model.TextResponse:
   model.TextResponse:
     properties:
     properties:
       choices:
       choices:
@@ -2992,6 +3114,38 @@ paths:
       tags:
       tags:
       - mcp
       - mcp
       - group
       - group
+  /api/group/{group}/mcp/{id}:
+    get:
+      description: Get a specific MCP by its ID
+      parameters:
+      - description: Group ID
+        in: path
+        name: group
+        required: true
+        type: string
+      - description: MCP ID
+        in: path
+        name: id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            allOf:
+            - $ref: '#/definitions/middleware.APIResponse'
+            - properties:
+                data:
+                  $ref: '#/definitions/controller.GroupPublicMCPDetailResponse'
+              type: object
+      security:
+      - ApiKeyAuth: []
+      summary: Get MCP by ID
+      tags:
+      - mcp
+      - group
   /api/group/{group}/model_config/{model}:
   /api/group/{group}/model_config/{model}:
     delete:
     delete:
       description: Delete group model config
       description: Delete group model config

+ 7 - 0
core/model/cache.go

@@ -643,6 +643,13 @@ func CacheSetPublicMCPReusingParam(param *PublicMCPReusingParamCache) error {
 }
 }
 
 
 func CacheGetPublicMCPReusingParam(mcpID, groupID string) (*PublicMCPReusingParamCache, error) {
 func CacheGetPublicMCPReusingParam(mcpID, groupID string) (*PublicMCPReusingParamCache, error) {
+	if groupID == "" {
+		return &PublicMCPReusingParamCache{
+			MCPID:   mcpID,
+			GroupID: groupID,
+			Params:  make(map[string]string),
+		}, nil
+	}
 	if !common.RedisEnabled {
 	if !common.RedisEnabled {
 		param, err := GetPublicMCPReusingParam(mcpID, groupID)
 		param, err := GetPublicMCPReusingParam(mcpID, groupID)
 		if err != nil {
 		if err != nil {

+ 48 - 30
core/model/publicmcp.go

@@ -65,13 +65,15 @@ type PublicMCPProxyConfig struct {
 	Reusing map[string]PublicMCPProxyReusingParam `json:"reusing"`
 	Reusing map[string]PublicMCPProxyReusingParam `json:"reusing"`
 }
 }
 
 
+type Params = map[string]string
+
 type PublicMCPReusingParam struct {
 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:"-"`
-	Params    map[string]string `gorm:"serializer:fastjson;type:text" json:"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    Params    `gorm:"serializer:fastjson;type:text" json:"params"`
 }
 }
 
 
 func (p *PublicMCPReusingParam) BeforeCreate(_ *gorm.DB) (err error) {
 func (p *PublicMCPReusingParam) BeforeCreate(_ *gorm.DB) (err error) {
@@ -123,27 +125,36 @@ func validateMCPID(id string) error {
 	return nil
 	return nil
 }
 }
 
 
+type TestConfig struct {
+	Enabled bool   `json:"enabled"`
+	Params  Params `json:"params"`
+}
+
 type PublicMCP struct {
 type PublicMCP struct {
-	ID                     string                  `gorm:"primaryKey"                    json:"id"`
-	Status                 PublicMCPStatus         `gorm:"index;default:1"               json:"status"`
-	CreatedAt              time.Time               `gorm:"index,autoCreateTime"          json:"created_at"`
-	UpdateAt               time.Time               `gorm:"index,autoUpdateTime"          json:"update_at"`
-	PublicMCPReusingParams []PublicMCPReusingParam `gorm:"foreignKey:MCPID"              json:"-"`
-	Name                   string                  `                                     json:"name"`
-	Type                   PublicMCPType           `gorm:"index"                         json:"type"`
-	Description            string                  `                                     json:"description"`
-	DescriptionCN          string                  `                                     json:"description_cn"`
-	GitHubURL              string                  `                                     json:"github_url"`
-	Readme                 string                  `gorm:"type:text"                     json:"readme"`
-	ReadmeCN               string                  `gorm:"type:text"                     json:"readme_cn"`
-	ReadmeURL              string                  `                                     json:"readme_url"`
-	ReadmeCNURL            string                  `                                     json:"readme_cn_url"`
-	Tags                   []string                `gorm:"serializer:fastjson;type:text" json:"tags,omitempty"`
-	LogoURL                string                  `                                     json:"logo_url"`
-	Price                  MCPPrice                `gorm:"embedded"                      json:"price"`
-	ProxyConfig            *PublicMCPProxyConfig   `gorm:"serializer:fastjson;type:text" json:"proxy_config,omitempty"`
-	OpenAPIConfig          *MCPOpenAPIConfig       `gorm:"serializer:fastjson;type:text" json:"openapi_config,omitempty"`
-	EmbedConfig            *MCPEmbeddingConfig     `gorm:"serializer:fastjson;type:text" json:"embed_config,omitempty"`
+	ID                     string                  `gorm:"primaryKey"           json:"id"`
+	CreatedAt              time.Time               `gorm:"index,autoCreateTime" json:"created_at"`
+	UpdateAt               time.Time               `gorm:"index,autoUpdateTime" json:"update_at"`
+	PublicMCPReusingParams []PublicMCPReusingParam `gorm:"foreignKey:MCPID"     json:"-"`
+
+	Name          string          `json:"name"`
+	NameCN        string          `json:"name_cn,omitempty"`
+	Status        PublicMCPStatus `json:"status"                   gorm:"index;default:1"`
+	Type          PublicMCPType   `json:"type,omitempty"           gorm:"index"`
+	Description   string          `json:"description"`
+	DescriptionCN string          `json:"description_cn,omitempty"`
+	GitHubURL     string          `json:"github_url"`
+	Readme        string          `json:"readme,omitempty"         gorm:"type:text"`
+	ReadmeCN      string          `json:"readme_cn,omitempty"      gorm:"type:text"`
+	ReadmeURL     string          `json:"readme_url,omitempty"`
+	ReadmeCNURL   string          `json:"readme_cn_url,omitempty"`
+	Tags          []string        `json:"tags,omitempty"           gorm:"serializer:fastjson;type:text"`
+	LogoURL       string          `json:"logo_url,omitempty"`
+	Price         MCPPrice        `json:"price"                    gorm:"embedded"`
+
+	ProxyConfig   *PublicMCPProxyConfig `gorm:"serializer:fastjson;type:text" json:"proxy_config,omitempty"`
+	OpenAPIConfig *MCPOpenAPIConfig     `gorm:"serializer:fastjson;type:text" json:"openapi_config,omitempty"`
+	EmbedConfig   *MCPEmbeddingConfig   `gorm:"serializer:fastjson;type:text" json:"embed_config,omitempty"`
+	TestConfig    *TestConfig           `gorm:"serializer:fastjson;type:text" json:"test_config,omitempty"`
 }
 }
 
 
 func (p *PublicMCP) BeforeCreate(_ *gorm.DB) error {
 func (p *PublicMCP) BeforeCreate(_ *gorm.DB) error {
@@ -230,14 +241,18 @@ func UpdatePublicMCP(mcp *PublicMCP) (err error) {
 
 
 	selects := []string{
 	selects := []string{
 		"github_url",
 		"github_url",
+		"description",
+		"description_cn",
 		"readme",
 		"readme",
+		"readme_cn",
 		"readme_url",
 		"readme_url",
+		"readme_cn_url",
 		"tags",
 		"tags",
-		"author",
 		"logo_url",
 		"logo_url",
 		"proxy_config",
 		"proxy_config",
 		"openapi_config",
 		"openapi_config",
 		"embed_config",
 		"embed_config",
+		"test_config",
 	}
 	}
 	if mcp.Status != 0 {
 	if mcp.Status != 0 {
 		selects = append(selects, "status")
 		selects = append(selects, "status")
@@ -245,6 +260,9 @@ func UpdatePublicMCP(mcp *PublicMCP) (err error) {
 	if mcp.Name != "" {
 	if mcp.Name != "" {
 		selects = append(selects, "name")
 		selects = append(selects, "name")
 	}
 	}
+	if mcp.NameCN != "" {
+		selects = append(selects, "name_cn")
+	}
 	if mcp.Type != "" {
 	if mcp.Type != "" {
 		selects = append(selects, "type")
 		selects = append(selects, "type")
 	}
 	}
@@ -443,11 +461,11 @@ func DeletePublicMCPReusingParam(mcpID, groupID string) (err error) {
 }
 }
 
 
 // GetPublicMCPReusingParam retrieves a GroupMCPReusingParam by MCP ID and Group ID
 // GetPublicMCPReusingParam retrieves a GroupMCPReusingParam by MCP ID and Group ID
-func GetPublicMCPReusingParam(mcpID, groupID string) (*PublicMCPReusingParam, error) {
+func GetPublicMCPReusingParam(mcpID, groupID string) (PublicMCPReusingParam, error) {
 	if mcpID == "" || groupID == "" {
 	if mcpID == "" || groupID == "" {
-		return nil, errors.New("MCP ID or Group ID is empty")
+		return PublicMCPReusingParam{}, errors.New("MCP ID or Group ID is empty")
 	}
 	}
 	var param PublicMCPReusingParam
 	var param PublicMCPReusingParam
 	err := DB.Where("mcp_id = ? AND group_id = ?", mcpID, groupID).First(&param).Error
 	err := DB.Where("mcp_id = ? AND group_id = ?", mcpID, groupID).First(&param).Error
-	return &param, HandleNotFound(err, ErrMCPReusingParamNotFound)
+	return param, HandleNotFound(err, ErrMCPReusingParamNotFound)
 }
 }

+ 1 - 0
core/router/api.go

@@ -79,6 +79,7 @@ func SetAPIRouter(router *gin.Engine) {
 			groupMcpRoute := groupRoute.Group("/:group/mcp")
 			groupMcpRoute := groupRoute.Group("/:group/mcp")
 			{
 			{
 				groupMcpRoute.GET("/", mcp.GetGroupPublicMCPs)
 				groupMcpRoute.GET("/", mcp.GetGroupPublicMCPs)
+				groupMcpRoute.GET("/:id", mcp.GetGroupPublicMCPByID)
 			}
 			}
 		}
 		}
 
 

+ 1 - 0
mcp-servers/12306/init.go

@@ -20,6 +20,7 @@ func init() {
 			"12306",
 			"12306",
 			"12306 Train Ticket Query",
 			"12306 Train Ticket Query",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("12306 购票搜索"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/Joooook/12306-mcp",
 				"https://github.com/Joooook/12306-mcp",

+ 1 - 0
mcp-servers/alipay/init.go

@@ -17,6 +17,7 @@ func init() {
 			"alipay",
 			"alipay",
 			"Alipay",
 			"Alipay",
 			model.PublicMCPTypeDocs,
 			model.PublicMCPTypeDocs,
+			mcpservers.WithNameCN("支付宝"),
 			mcpservers.WithTags([]string{"pay"}),
 			mcpservers.WithTags([]string{"pay"}),
 			mcpservers.WithDescription(
 			mcpservers.WithDescription(
 				"支付宝 MCP Server,让你可以轻松将支付宝开放平台提供的交易创建、查询、退款等能力集成到你的 LLM 应用中,并进一步创建具备支付能力的智能工具。",
 				"支付宝 MCP Server,让你可以轻松将支付宝开放平台提供的交易创建、查询、退款等能力集成到你的 LLM 应用中,并进一步创建具备支付能力的智能工具。",

+ 1 - 0
mcp-servers/amap/init.go

@@ -12,6 +12,7 @@ func init() {
 			"amap",
 			"amap",
 			"AMAP",
 			"AMAP",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("高德地图"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithTags([]string{"map"}),
 			mcpservers.WithTags([]string{"map"}),

+ 1 - 0
mcp-servers/baidu-map/init.go

@@ -16,6 +16,7 @@ func init() {
 			"baidu-map",
 			"baidu-map",
 			"Baidu Map",
 			"Baidu Map",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("百度地图"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/baidu-maps/mcp",
 				"https://github.com/baidu-maps/mcp",

+ 1 - 0
mcp-servers/bingcn/init.go

@@ -16,6 +16,7 @@ func init() {
 			"bing-cn-search",
 			"bing-cn-search",
 			"Bing CN Search",
 			"Bing CN Search",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("必应中国搜索"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/yan5236/bing-cn-mcp-server",
 				"https://github.com/yan5236/bing-cn-mcp-server",

+ 1 - 0
mcp-servers/fetch/init.go

@@ -19,6 +19,7 @@ func init() {
 			"fetch",
 			"fetch",
 			"Fetch",
 			"Fetch",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("网页内容获取"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/fetch",
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/fetch",

+ 1 - 0
mcp-servers/gezhe/init.go

@@ -20,6 +20,7 @@ func init() {
 			"gezhe",
 			"gezhe",
 			"Gezhe",
 			"Gezhe",
 			model.PublicMCPTypeProxyStreamable,
 			model.PublicMCPTypeProxyStreamable,
+			mcpservers.WithNameCN("歌者"),
 			mcpservers.WithProxyConfigType(configTemplates),
 			mcpservers.WithProxyConfigType(configTemplates),
 			mcpservers.WithTags([]string{"map"}),
 			mcpservers.WithTags([]string{"map"}),
 			mcpservers.WithDescription(
 			mcpservers.WithDescription(

+ 1 - 0
mcp-servers/gpt-vis/init.go

@@ -19,6 +19,7 @@ func init() {
 			"gpt-vis",
 			"gpt-vis",
 			"GPT Vis",
 			"GPT Vis",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("可视化图表"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/antvis/mcp-server-chart",
 				"https://github.com/antvis/mcp-server-chart",

+ 1 - 0
mcp-servers/hefeng-weather/init.go

@@ -19,6 +19,7 @@ func init() {
 			"hefeng-weather",
 			"hefeng-weather",
 			"HeFeng Weather",
 			"HeFeng Weather",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("和风天气"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/shanggqm/hefeng-mcp-weather",
 				"https://github.com/shanggqm/hefeng-mcp-weather",

+ 1 - 0
mcp-servers/howtocook/init.go

@@ -19,6 +19,7 @@ func init() {
 			"howtocook",
 			"howtocook",
 			"HowToCook Recipe Server",
 			"HowToCook Recipe Server",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("程序员做饭指南"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithDescription(
 			mcpservers.WithDescription(
 				"A recipe recommendation server based on the HowToCook project. Provides intelligent meal planning, recipe search by category, and dish recommendations based on the number of people.",
 				"A recipe recommendation server based on the HowToCook project. Provides intelligent meal planning, recipe search by category, and dish recommendations based on the number of people.",

+ 1 - 0
mcp-servers/jina-tools/init.go

@@ -19,6 +19,7 @@ func init() {
 			"jina",
 			"jina",
 			"Jina AI Tools",
 			"Jina AI Tools",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("Jina AI 工具"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/PsychArch/jina-mcp-tools",
 				"https://github.com/PsychArch/jina-mcp-tools",

+ 7 - 1
mcp-servers/mcp.go

@@ -138,6 +138,12 @@ type McpServer struct {
 
 
 type McpConfig func(*McpServer)
 type McpConfig func(*McpServer)
 
 
+func WithNameCN(nameCN string) McpConfig {
+	return func(e *McpServer) {
+		e.NameCN = nameCN
+	}
+}
+
 func WithDescription(description string) McpConfig {
 func WithDescription(description string) McpConfig {
 	return func(e *McpServer) {
 	return func(e *McpServer) {
 		e.Description = description
 		e.Description = description
@@ -237,7 +243,7 @@ func NewMcp(id, name string, mcpType model.PublicMCPType, opts ...McpConfig) Mcp
 }
 }
 
 
 func (e *McpServer) NewServer(config, reusingConfig map[string]string) (Server, error) {
 func (e *McpServer) NewServer(config, reusingConfig map[string]string) (Server, error) {
-	if e.newServer != nil {
+	if e.newServer == nil {
 		return nil, errors.New("not impl new server")
 		return nil, errors.New("not impl new server")
 	}
 	}
 	if err := ValidateConfigTemplatesConfig(e.ConfigTemplates, config, reusingConfig); err != nil {
 	if err := ValidateConfigTemplatesConfig(e.ConfigTemplates, config, reusingConfig); err != nil {

+ 1 - 0
mcp-servers/tavily/init.go

@@ -19,6 +19,7 @@ func init() {
 			"tavily",
 			"tavily",
 			"Tavily AI Search",
 			"Tavily AI Search",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("Tavily AI 搜索"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithDescription(
 			mcpservers.WithDescription(
 				"A powerful web search MCP server powered by Tavily's AI search engine. Provides real-time web search, content extraction, web crawling, and site mapping capabilities with advanced filtering and customization options.",
 				"A powerful web search MCP server powered by Tavily's AI search engine. Provides real-time web search, content extraction, web crawling, and site mapping capabilities with advanced filtering and customization options.",

+ 1 - 0
mcp-servers/time/init.go

@@ -19,6 +19,7 @@ func init() {
 			"time",
 			"time",
 			"Time",
 			"Time",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("时间"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 			mcpservers.WithGitHubURL(
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/time",
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/time",

+ 1 - 0
mcp-servers/web-search/init.go

@@ -20,6 +20,7 @@ func init() {
 			"web-search",
 			"web-search",
 			"Web Search",
 			"Web Search",
 			model.PublicMCPTypeEmbed,
 			model.PublicMCPTypeEmbed,
+			mcpservers.WithNameCN("网络搜索"),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithTags([]string{"search", "web", "google", "bing", "arxiv", "searchxng"}),
 			mcpservers.WithTags([]string{"search", "web", "google", "bing", "arxiv", "searchxng"}),