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

feat: embed mcp type (#260)

* feat: embed mcp type

* fix: streamable proxy

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

+ 92 - 35
core/controller/mcp/embedmcp.go

@@ -2,6 +2,7 @@ package controller
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"maps"
 	"net/http"
@@ -45,6 +46,16 @@ func newEmbedMCPConfigTemplates(templates mcpservers.ConfigTemplates) EmbedMCPCo
 	return emcpTemplates
 }
 
+func newEmbedMCPProxyConfigTemplates(
+	templates mcpservers.ProxyConfigTemplates,
+) EmbedMCPConfigTemplates {
+	emcpTemplates := make(EmbedMCPConfigTemplates, len(templates))
+	for key, template := range templates {
+		emcpTemplates[key] = newEmbedMCPConfigTemplate(template.ConfigTemplate)
+	}
+	return emcpTemplates
+}
+
 type EmbedMCP struct {
 	ID              string                    `json:"id"`
 	Enabled         bool                      `json:"enabled"`
@@ -65,17 +76,25 @@ func newEmbedMCP(
 	embedConfig *model.MCPEmbeddingConfig,
 ) *EmbedMCP {
 	emcp := &EmbedMCP{
-		ID:              mcp.ID,
-		Enabled:         enabled,
-		Name:            mcp.Name,
-		Readme:          mcp.Readme,
-		ReadmeURL:       mcp.ReadmeURL,
-		ReadmeCN:        mcp.ReadmeCN,
-		ReadmeCNURL:     mcp.ReadmeCNURL,
-		GitHubURL:       mcp.GitHubURL,
-		Tags:            mcp.Tags,
-		ConfigTemplates: newEmbedMCPConfigTemplates(mcp.ConfigTemplates),
-		EmbedConfig:     embedConfig,
+		ID:          mcp.ID,
+		Enabled:     enabled,
+		Name:        mcp.Name,
+		Readme:      mcp.Readme,
+		ReadmeURL:   mcp.ReadmeURL,
+		ReadmeCN:    mcp.ReadmeCN,
+		ReadmeCNURL: mcp.ReadmeCNURL,
+		GitHubURL:   mcp.GitHubURL,
+		Tags:        mcp.Tags,
+		EmbedConfig: embedConfig,
+	}
+	if len(mcp.ConfigTemplates) != 0 {
+		emcp.ConfigTemplates = newEmbedMCPConfigTemplates(mcp.ConfigTemplates)
+	}
+	if len(mcp.ProxyConfigTemplates) != 0 {
+		emcp.ConfigTemplates = newEmbedMCPProxyConfigTemplates(mcp.ProxyConfigTemplates)
+	}
+	if emcp.ConfigTemplates == nil {
+		emcp.ConfigTemplates = make(EmbedMCPConfigTemplates)
 	}
 	return emcp
 }
@@ -181,41 +200,79 @@ func GetEmbedConfig(
 	return embedConfig, nil
 }
 
+func GetProxyConfig(
+	proxyConfigType mcpservers.ProxyConfigTemplates,
+	initConfig map[string]string,
+) (*model.PublicMCPProxyConfig, error) {
+	if len(proxyConfigType) == 0 {
+		return nil, errors.New("proxy config type is empty")
+	}
+
+	config := &model.PublicMCPProxyConfig{
+		Querys:  make(map[string]string),
+		Headers: make(map[string]string),
+		Reusing: make(map[string]model.PublicMCPProxyReusingParam),
+	}
+
+	for key, param := range proxyConfigType {
+		value := initConfig[key]
+		if value == "" {
+			value = param.Default
+		}
+
+		switch param.Type {
+		case model.ParamTypeURL:
+			if value == "" {
+				return nil, fmt.Errorf("url parameter %s is required", key)
+			}
+			config.URL = value
+		case model.ParamTypeHeader:
+			if value != "" {
+				config.Headers[key] = value
+			}
+		case model.ParamTypeQuery:
+			if value != "" {
+				config.Querys[key] = value
+			}
+		default:
+			return nil, fmt.Errorf("unsupported proxy param type: %s", param.Type)
+		}
+	}
+
+	if config.URL == "" {
+		return nil, errors.New("url is required in proxy config")
+	}
+
+	return config, nil
+}
+
 func ToPublicMCP(
 	e mcpservers.McpServer,
 	initConfig map[string]string,
 	enabled bool,
 ) (*model.PublicMCP, error) {
-	embedConfig, err := GetEmbedConfig(e.ConfigTemplates, initConfig)
-	if err != nil {
-		return nil, err
-	}
-	pmcp := &model.PublicMCP{
-		ID:            e.ID,
-		Name:          e.Name,
-		LogoURL:       e.LogoURL,
-		Description:   e.Description,
-		DescriptionCN: e.DescriptionCN,
-		Readme:        e.Readme,
-		ReadmeURL:     e.ReadmeURL,
-		ReadmeCN:      e.ReadmeCN,
-		ReadmeCNURL:   e.ReadmeCNURL,
-		GithubURL:     e.GitHubURL,
-		Tags:          e.Tags,
-		EmbedConfig:   embedConfig,
+	pmcp := e.PublicMCP
+	switch e.Type {
+	case model.PublicMCPTypeEmbed:
+		embedConfig, err := GetEmbedConfig(e.ConfigTemplates, initConfig)
+		if err != nil {
+			return nil, err
+		}
+		pmcp.EmbedConfig = embedConfig
+	case model.PublicMCPTypeProxySSE, model.PublicMCPTypeProxyStreamable:
+		proxyConfig, err := GetProxyConfig(e.ProxyConfigTemplates, initConfig)
+		if err != nil {
+			return nil, err
+		}
+		pmcp.ProxyConfig = proxyConfig
+	default:
 	}
 	if enabled {
 		pmcp.Status = model.PublicMCPStatusEnabled
 	} else {
 		pmcp.Status = model.PublicMCPStatusDisabled
 	}
-	switch e.Type {
-	case mcpservers.McpTypeEmbed:
-		pmcp.Type = model.PublicMCPTypeEmbed
-	case mcpservers.McpTypeDocs:
-		pmcp.Type = model.PublicMCPTypeDocs
-	}
-	return pmcp, nil
+	return &pmcp, nil
 }
 
 // SaveEmbedMCP godoc

+ 13 - 1
core/mcpproxy/stateless-streamable.go

@@ -83,9 +83,21 @@ func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request
 		return
 	}
 
+	jsonBody, err := sonic.Marshal(response)
+	if err != nil {
+		s.writeJSONRPCError(
+			w,
+			nil,
+			mcp.INTERNAL_ERROR,
+			fmt.Sprintf("marshal response body error: %v", err),
+		)
+		return
+	}
+
 	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Content-Length", strconv.Itoa(len(jsonBody)))
 	w.WriteHeader(http.StatusOK)
-	_ = sonic.ConfigDefault.NewEncoder(w).Encode(response)
+	_, _ = w.Write(jsonBody)
 }
 
 func (s *StreamableHTTPServer) handleGet(w http.ResponseWriter, _ *http.Request) {

+ 23 - 14
core/mcpproxy/streamable.go

@@ -92,7 +92,7 @@ func (p *StreamableProxy) handleGetRequest(w http.ResponseWriter, r *http.Reques
 	}
 
 	// Extract the real backend session ID from the stored URL
-	parts := strings.Split(backendInfo, "?sessionId=")
+	parts := strings.Split(backendInfo, "|sessionId=")
 	if len(parts) > 1 {
 		req.Header.Set(headerKeySessionID, parts[1])
 	}
@@ -102,6 +102,8 @@ func (p *StreamableProxy) handleGetRequest(w http.ResponseWriter, r *http.Reques
 		req.Header.Set(name, value)
 	}
 
+	req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
+
 	//nolint:bodyclose
 	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
@@ -175,24 +177,32 @@ func (p *StreamableProxy) handlePostRequest(w http.ResponseWriter, r *http.Reque
 		return
 	}
 
+	// Extract the real backend session ID from the stored URL
+	parts := strings.Split(backendInfo, "|sessionId=")
+	if len(parts) != 2 {
+		http.Error(w, "Invalid or expired session ID", http.StatusNotFound)
+		return
+	}
+	backend := parts[0]
+	sessionID := parts[1]
+
 	// Create a request to the backend
-	req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, backendInfo, r.Body)
+	req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, backend, r.Body)
 	if err != nil {
 		http.Error(w, "Failed to create backend request", http.StatusInternalServerError)
 		return
 	}
 
-	// Extract the real backend session ID from the stored URL
-	parts := strings.Split(backendInfo, "?sessionId=")
-	if len(parts) > 1 {
-		req.Header.Set(headerKeySessionID, parts[1])
-	}
-
 	// Add any additional headers
 	for name, value := range p.headers {
 		req.Header.Set(name, value)
 	}
 
+	req.Header.Set(headerKeySessionID, sessionID)
+
+	req.Header.Set("Accept", "application/json, text/event-stream")
+	req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
+
 	//nolint:bodyclose
 	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
@@ -274,7 +284,7 @@ func (p *StreamableProxy) handleDeleteRequest(w http.ResponseWriter, r *http.Req
 	}
 
 	// Extract the real backend session ID from the stored URL
-	parts := strings.Split(backendInfo, "?sessionId=")
+	parts := strings.Split(backendInfo, "|sessionId=")
 	if len(parts) > 1 {
 		req.Header.Set(headerKeySessionID, parts[1])
 	}
@@ -323,6 +333,9 @@ func (p *StreamableProxy) proxyInitialOrNoSessionRequest(w http.ResponseWriter,
 		req.Header.Set(name, value)
 	}
 
+	req.Header.Set("Accept", "application/json, text/event-stream")
+	req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
+
 	//nolint:bodyclose
 	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
@@ -340,11 +353,7 @@ func (p *StreamableProxy) proxyInitialOrNoSessionRequest(w http.ResponseWriter,
 		// Store the mapping between our proxy session ID and the backend endpoint with its session
 		// ID
 		backendURL := p.backend
-		if strings.Contains(backendURL, "?") {
-			backendURL += "&sessionId=" + backendSessionID
-		} else {
-			backendURL += "?sessionId=" + backendSessionID
-		}
+		backendURL += "|sessionId=" + backendSessionID
 		p.store.Set(proxySessionID, backendURL)
 
 		// Replace the backend session ID with our proxy session ID in the response

+ 6 - 5
core/model/publicmcp.go

@@ -34,11 +34,12 @@ const (
 	PublicMCPTypeEmbed           PublicMCPType = "mcp_embed"
 )
 
-type ParamType string
+type ProxyParamType string
 
 const (
-	ParamTypeHeader ParamType = "header"
-	ParamTypeQuery  ParamType = "query"
+	ParamTypeURL    ProxyParamType = "url"
+	ParamTypeHeader ProxyParamType = "header"
+	ParamTypeQuery  ProxyParamType = "query"
 )
 
 type ReusingParam struct {
@@ -54,7 +55,7 @@ type MCPPrice struct {
 
 type PublicMCPProxyReusingParam struct {
 	ReusingParam
-	Type ParamType `json:"type"`
+	Type ProxyParamType `json:"type"`
 }
 
 type PublicMCPProxyConfig struct {
@@ -132,7 +133,7 @@ type PublicMCP struct {
 	Type                   PublicMCPType           `gorm:"index"                         json:"type"`
 	Description            string                  `                                     json:"description"`
 	DescriptionCN          string                  `                                     json:"description_cn"`
-	GithubURL              string                  `                                     json:"github_url"`
+	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"`

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

@@ -4,6 +4,7 @@ package train12306
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -18,7 +19,7 @@ func init() {
 		mcpservers.NewMcp(
 			"12306",
 			"12306 Train Ticket Query",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/Joooook/12306-mcp",

+ 5 - 2
mcp-servers/aiproxy-openapi/init.go

@@ -1,6 +1,9 @@
 package aiproxyopenapi
 
-import mcpservers "github.com/labring/aiproxy/mcp-servers"
+import (
+	"github.com/labring/aiproxy/core/model"
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
 
 // need import in mcpregister/init.go
 func init() {
@@ -8,7 +11,7 @@ func init() {
 		mcpservers.NewMcp(
 			"aiproxy-openapi",
 			"AI Proxy OpenAPI",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithDescription(

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

@@ -3,6 +3,7 @@ package alipay
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -15,7 +16,7 @@ func init() {
 		mcpservers.NewMcp(
 			"alipay",
 			"Alipay",
-			mcpservers.McpTypeDocs,
+			model.PublicMCPTypeDocs,
 			mcpservers.WithTags([]string{"pay"}),
 			mcpservers.WithDescription(
 				"支付宝 MCP Server,让你可以轻松将支付宝开放平台提供的交易创建、查询、退款等能力集成到你的 LLM 应用中,并进一步创建具备支付能力的智能工具。",

+ 5 - 2
mcp-servers/amap/init.go

@@ -1,6 +1,9 @@
 package amap
 
-import mcpservers "github.com/labring/aiproxy/mcp-servers"
+import (
+	"github.com/labring/aiproxy/core/model"
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
 
 // need import in mcpregister/init.go
 func init() {
@@ -8,7 +11,7 @@ func init() {
 		mcpservers.NewMcp(
 			"amap",
 			"AMAP",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithTags([]string{"map"}),

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

@@ -3,6 +3,7 @@ package baidumap
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -14,7 +15,7 @@ func init() {
 		mcpservers.NewMcp(
 			"baidu-map",
 			"Baidu Map",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/baidu-maps/mcp",

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

@@ -3,6 +3,7 @@ package bingcn
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -14,7 +15,7 @@ func init() {
 		mcpservers.NewMcp(
 			"bing-cn-search",
 			"Bing CN Search",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/yan5236/bing-cn-mcp-server",

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

@@ -3,6 +3,7 @@ package fetch
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"fetch",
 			"Fetch",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/fetch",

+ 2 - 1
mcp-servers/firecrawl/init.go

@@ -3,6 +3,7 @@ package firecrawl
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"firecrawl",
 			"Firecrawl",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/mendableai/firecrawl-mcp-server",

+ 2 - 1
mcp-servers/flomo/init.go

@@ -3,6 +3,7 @@ package flomo
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"flomo",
 			"Flomo",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/chatmcp/mcp-server-flomo",

+ 17 - 0
mcp-servers/gezhe/README.cn.md

@@ -0,0 +1,17 @@
+# Gezhe-MCP-server
+
+## 简介
+
+歌者 PPT MCP server, 可以通过话题生成 PPT
+
+### Tools
+
+1. `generate_ppt_by_topic`
+   - 输入:
+     - `topic` (string): 话题名称
+   - 返回: 预览链接
+
+## 使用指引
+
+1. 访问并登录 <https://pro.gezhe.com/settings>
+2. 进入「设置-MCP 服务器」页面,复制页面中提供的 URL 地址

+ 17 - 0
mcp-servers/gezhe/README.md

@@ -0,0 +1,17 @@
+# Gezhe-MCP-server
+
+## Introduction
+
+Gezhe PPT MCP server, can generate PPTs based on topics.
+
+### Tools
+
+1. `generate_ppt_by_topic`
+   - Input:
+     - `topic` (string): Topic name
+   - Returns: Preview link
+
+## Usage Guide
+
+1. Visit and log in to <https://pro.gezhe.com/settings>
+2. Go to the "Settings - MCP Server" page and copy the URL provided on the page.

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

@@ -0,0 +1,32 @@
+package gezhe
+
+import (
+	_ "embed"
+
+	"github.com/labring/aiproxy/core/model"
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
+
+//go:embed README.md
+var readme string
+
+//go:embed README.cn.md
+var readmeCN string
+
+// need import in mcpregister/init.go
+func init() {
+	mcpservers.Register(
+		mcpservers.NewMcp(
+			"gezhe",
+			"Gezhe",
+			model.PublicMCPTypeProxyStreamable,
+			mcpservers.WithProxyConfigType(configTemplates),
+			mcpservers.WithTags([]string{"map"}),
+			mcpservers.WithDescription(
+				"可以通过话题生成 PPT",
+			),
+			mcpservers.WithReadme(readme),
+			mcpservers.WithReadmeCN(readmeCN),
+		),
+	)
+}

+ 30 - 0
mcp-servers/gezhe/main.go

@@ -0,0 +1,30 @@
+package gezhe
+
+import (
+	"github.com/labring/aiproxy/core/model"
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
+
+const defaultURL = "https://mcp.gezhe.com/mcp"
+
+var configTemplates = mcpservers.ProxyConfigTemplates{
+	"API_KEY": {
+		ConfigTemplate: mcpservers.ConfigTemplate{
+			Name:        "API_KEY",
+			Required:    mcpservers.ConfigRequiredTypeInitOrReusingOnly,
+			Example:     "bx7Qt1BLbxRq...",
+			Description: "The key of the Gezhe MCP server: https://pro.gezhe.com/settings",
+		},
+		Type: model.ParamTypeQuery,
+	},
+
+	"url": {
+		ConfigTemplate: mcpservers.ConfigTemplate{
+			Name:        "URL",
+			Required:    mcpservers.ConfigRequiredTypeInitOptional,
+			Example:     defaultURL,
+			Description: "The Streamable http URL of the gezhe MCP server",
+		},
+		Type: model.ParamTypeURL,
+	},
+}

+ 30 - 0
mcp-servers/go.mod

@@ -15,34 +15,64 @@ require (
 )
 
 require (
+	filippo.io/edwards25519 v1.1.0 // indirect
 	github.com/KyleBanks/depth v1.2.1 // indirect
 	github.com/andybalholm/cascadia v1.3.3 // indirect
 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
 	github.com/bytedance/sonic/loader v0.2.4 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/getkin/kin-openapi v0.132.0 // indirect
+	github.com/glebarez/go-sqlite v1.22.0 // indirect
+	github.com/glebarez/sqlite v1.11.0 // indirect
 	github.com/go-openapi/jsonpointer v0.21.1 // indirect
 	github.com/go-openapi/jsonreference v0.21.0 // indirect
 	github.com/go-openapi/spec v0.21.0 // indirect
 	github.com/go-openapi/swag v0.23.1 // indirect
 	github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
+	github.com/go-sql-driver/mysql v1.9.2 // indirect
 	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
 	github.com/google/uuid v1.6.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+	github.com/jackc/pgx/v5 v5.7.5 // indirect
+	github.com/jackc/puddle/v2 v2.2.2 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.10 // indirect
 	github.com/mailru/easyjson v0.9.0 // indirect
+	github.com/maruel/natural v1.1.1 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+	github.com/ncruces/go-strftime v0.1.9 // indirect
 	github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
 	github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
 	github.com/perimeterx/marshmallow v1.1.5 // indirect
+	github.com/redis/go-redis/v9 v9.8.0 // indirect
+	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+	github.com/shopspring/decimal v1.4.0 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/cast v1.8.0 // indirect
 	github.com/swaggo/swag v1.16.4 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.13 // indirect
 	github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
 	golang.org/x/arch v0.17.0 // indirect
+	golang.org/x/crypto v0.38.0 // indirect
+	golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
 	golang.org/x/net v0.40.0 // indirect
+	golang.org/x/sync v0.14.0 // indirect
 	golang.org/x/sys v0.33.0 // indirect
 	golang.org/x/tools v0.33.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/driver/mysql v1.5.7 // indirect
+	gorm.io/driver/postgres v1.6.0 // indirect
+	gorm.io/gorm v1.30.0 // indirect
+	modernc.org/libc v1.65.8 // indirect
+	modernc.org/mathutil v1.7.1 // indirect
+	modernc.org/memory v1.11.0 // indirect
+	modernc.org/sqlite v1.37.1 // indirect
 )

+ 40 - 0
mcp-servers/go.sum

@@ -1,3 +1,4 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
 github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
 github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -9,21 +10,28 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
 github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
 github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
 github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
 github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
 github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
 github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
+github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
+github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
 github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
 github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
 github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
@@ -36,6 +44,7 @@ github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziH
 github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
 github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612 h1:BYLNYdZaepitbZreRIa9xeCQZocWmy/wj4cGIH0qyw0=
 github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612/go.mod h1:wgqthQa8SAYs0yyljVeCOQlZ027VW5CmLsbi9jWC08c=
+github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
 github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
 github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
@@ -43,8 +52,15 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -66,9 +82,12 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4
 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
 github.com/mark3labs/mcp-go v0.30.0 h1:Taz7fiefkxY/l8jz1nA90V+WdM2eoMtlvwfWforVYbo=
 github.com/mark3labs/mcp-go v0.30.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
+github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
 github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
 github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
 github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
@@ -78,6 +97,8 @@ github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0V
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
@@ -87,6 +108,8 @@ github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvK
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
 github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
 github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -122,6 +145,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
 golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
+golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -207,4 +232,19 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
+gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
+gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
+modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
+modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
+modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/libc v1.65.8 h1:7PXRJai0TXZ8uNA3srsmYzmTyrLoHImV5QxHeni108Q=
+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
+modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
+modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
+modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

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

@@ -3,6 +3,7 @@ package gptvis
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"gpt-vis",
 			"GPT Vis",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/antvis/mcp-server-chart",

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

@@ -3,6 +3,7 @@ package hefengweather
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"hefeng-weather",
 			"HeFeng Weather",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/shanggqm/hefeng-mcp-weather",

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

@@ -3,6 +3,7 @@ package howtocook
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"howtocook",
 			"HowToCook Recipe Server",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			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.",

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

@@ -3,6 +3,7 @@ package jinatools
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"jina",
 			"Jina AI Tools",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/PsychArch/jina-mcp-tools",

+ 67 - 38
mcp-servers/mcp.go

@@ -3,6 +3,8 @@ package mcpservers
 import (
 	"errors"
 	"fmt"
+
+	"github.com/labring/aiproxy/core/model"
 )
 
 type ConfigValueValidator func(value string) error
@@ -50,6 +52,14 @@ type ConfigTemplate struct {
 
 type ConfigTemplates = map[string]ConfigTemplate
 
+type ProxyConfigTemplate struct {
+	ConfigTemplate
+	Type    model.ProxyParamType
+	Default string
+}
+
+type ProxyConfigTemplates = map[string]ProxyConfigTemplate
+
 func ValidateConfigTemplatesConfig(
 	ct ConfigTemplates,
 	config, reusingConfig map[string]string,
@@ -82,46 +92,48 @@ func ValidateConfigTemplatesConfig(
 
 func CheckConfigTemplatesValidate(ct ConfigTemplates) error {
 	for key, value := range ct {
-		if value.Name == "" {
-			return fmt.Errorf("config %s name is required", key)
-		}
-		if value.Description == "" {
-			return fmt.Errorf("config %s description is required", key)
-		}
-		if value.Example == "" || value.Validator == nil {
-			continue
-		}
-		if err := value.Validator(value.Example); err != nil {
-			return fmt.Errorf("config %s example is invalid: %w", key, err)
+		err := CheckConfigTemplateValidate(value)
+		if err != nil {
+			return fmt.Errorf("config %s validate error: %w", key, err)
 		}
 	}
 	return nil
 }
 
-type NewServerFunc func(config, reusingConfig map[string]string) (Server, error)
+func CheckProxyConfigTemplatesValidate(ct ProxyConfigTemplates) error {
+	for key, value := range ct {
+		err := CheckConfigTemplateValidate(value.ConfigTemplate)
+		if err != nil {
+			return fmt.Errorf("config %s validate error: %w", key, err)
+		}
+	}
+	return nil
+}
 
-type McpType string
+func CheckConfigTemplateValidate(value ConfigTemplate) error {
+	if value.Name == "" {
+		return errors.New("name is required")
+	}
+	if value.Description == "" {
+		return errors.New("description is required")
+	}
+	if value.Example == "" || value.Validator == nil {
+		return nil
+	}
+	if err := value.Validator(value.Example); err != nil {
+		return fmt.Errorf("example is invalid: %w", err)
+	}
+	return nil
+}
 
-const (
-	McpTypeEmbed McpType = "embed"
-	McpTypeDocs  McpType = "docs"
-)
+type NewServerFunc func(config, reusingConfig map[string]string) (Server, error)
 
 type McpServer struct {
-	ID              string
-	Name            string
-	Type            McpType
-	GitHubURL       string
-	Description     string
-	DescriptionCN   string
-	Readme          string
-	ReadmeURL       string
-	ReadmeCN        string
-	ReadmeCNURL     string
-	LogoURL         string
-	Tags            []string
-	ConfigTemplates ConfigTemplates
-	newServer       NewServerFunc
+	model.PublicMCP
+	ConfigTemplates      ConfigTemplates
+	ProxyConfigTemplates ProxyConfigTemplates
+	newServer            NewServerFunc
+	disableCache         bool
 }
 
 type McpConfig func(*McpServer)
@@ -162,13 +174,13 @@ func WithReadmeCNURL(readmeCNURL string) McpConfig {
 	}
 }
 
-func WithGitHubURL(githubURL string) McpConfig {
+func WithGitHubURL(gitHubURL string) McpConfig {
 	return func(e *McpServer) {
-		e.GitHubURL = githubURL
+		e.GitHubURL = gitHubURL
 	}
 }
 
-func WithType(t McpType) McpConfig {
+func WithType(t model.PublicMCPType) McpConfig {
 	return func(e *McpServer) {
 		e.Type = t
 	}
@@ -192,17 +204,31 @@ func WithConfigTemplates(configTemplates ConfigTemplates) McpConfig {
 	}
 }
 
+func WithProxyConfigType(proxyConfigTemplates ProxyConfigTemplates) McpConfig {
+	return func(e *McpServer) {
+		e.ProxyConfigTemplates = proxyConfigTemplates
+	}
+}
+
 func WithNewServerFunc(newServer NewServerFunc) McpConfig {
 	return func(e *McpServer) {
 		e.newServer = newServer
 	}
 }
 
-func NewMcp(id, name string, mcpType McpType, opts ...McpConfig) McpServer {
+func WithDisableCache(disableCache bool) McpConfig {
+	return func(e *McpServer) {
+		e.disableCache = disableCache
+	}
+}
+
+func NewMcp(id, name string, mcpType model.PublicMCPType, opts ...McpConfig) McpServer {
 	e := McpServer{
-		ID:   id,
-		Name: name,
-		Type: mcpType,
+		PublicMCP: model.PublicMCP{
+			ID:   id,
+			Name: name,
+			Type: mcpType,
+		},
 	}
 	for _, opt := range opts {
 		opt(&e)
@@ -211,6 +237,9 @@ func NewMcp(id, name string, mcpType McpType, opts ...McpConfig) McpServer {
 }
 
 func (e *McpServer) NewServer(config, reusingConfig map[string]string) (Server, error) {
+	if e.newServer != nil {
+		return nil, errors.New("not impl new server")
+	}
 	if err := ValidateConfigTemplatesConfig(e.ConfigTemplates, config, reusingConfig); err != nil {
 		return nil, fmt.Errorf("mcp %s config is invalid: %w", e.ID, err)
 	}

+ 3 - 0
mcp-servers/mcpregister/init.go

@@ -11,9 +11,12 @@ import (
 	_ "github.com/labring/aiproxy/mcp-servers/fetch"
 	_ "github.com/labring/aiproxy/mcp-servers/firecrawl"
 	_ "github.com/labring/aiproxy/mcp-servers/flomo"
+	_ "github.com/labring/aiproxy/mcp-servers/gezhe"
 	_ "github.com/labring/aiproxy/mcp-servers/gpt-vis"
 	_ "github.com/labring/aiproxy/mcp-servers/hefeng-weather"
+	_ "github.com/labring/aiproxy/mcp-servers/howtocook"
 	_ "github.com/labring/aiproxy/mcp-servers/jina-tools"
+	_ "github.com/labring/aiproxy/mcp-servers/tavily"
 	_ "github.com/labring/aiproxy/mcp-servers/time"
 	_ "github.com/labring/aiproxy/mcp-servers/web-search"
 )

+ 34 - 13
mcp-servers/register.go

@@ -7,6 +7,8 @@ import (
 	"sync"
 	"sync/atomic"
 	"time"
+
+	"github.com/labring/aiproxy/core/model"
 )
 
 type mcpServerCacheItem struct {
@@ -57,32 +59,43 @@ func Register(mcp McpServer) {
 	if mcp.Name == "" {
 		panic("mcp name is required")
 	}
-	if mcp.Description == "" && mcp.DescriptionCN == "" {
-		panic(fmt.Sprintf("mcp %s description or description_cn is required", mcp.ID))
+	if mcp.Description == "" &&
+		mcp.DescriptionCN == "" &&
+		mcp.Readme == "" &&
+		mcp.ReadmeURL == "" &&
+		mcp.ReadmeCN == "" &&
+		mcp.ReadmeCNURL == "" {
+		panic(
+			fmt.Sprintf(
+				"mcp %s description or description_cn readme or readme_url or readme_cn or readme_cn_url is required",
+				mcp.ID,
+			),
+		)
 	}
 	switch mcp.Type {
-	case McpTypeEmbed:
+	case model.PublicMCPTypeEmbed:
 		if mcp.newServer == nil {
 			panic(fmt.Sprintf("mcp %s new server is required", mcp.ID))
 		}
-	case McpTypeDocs:
-		if mcp.Readme == "" && mcp.ReadmeURL == "" && mcp.ReadmeCN == "" && mcp.ReadmeCNURL == "" {
-			panic(
-				fmt.Sprintf(
-					"mcp %s readme or readme_url or readme_cn or readme_cn_url is required",
-					mcp.ID,
-				),
-			)
+	case model.PublicMCPTypeProxySSE,
+		model.PublicMCPTypeProxyStreamable:
+		if len(mcp.ProxyConfigTemplates) == 0 {
+			panic(fmt.Sprintf("mcp %s proxy config templates is required", mcp.ID))
 		}
 	default:
-		panic(fmt.Sprintf("mcp %s type is invalid", mcp.ID))
 	}
 
-	if mcp.ConfigTemplates != nil {
+	if len(mcp.ConfigTemplates) != 0 {
 		if err := CheckConfigTemplatesValidate(mcp.ConfigTemplates); err != nil {
 			panic(fmt.Sprintf("mcp %s config templates example is invalid: %v", mcp.ID, err))
 		}
 	}
+	if len(mcp.ProxyConfigTemplates) != 0 {
+		if err := CheckProxyConfigTemplatesValidate(mcp.ProxyConfigTemplates); err != nil {
+			panic(fmt.Sprintf("mcp %s config templates example is invalid: %v", mcp.ID, err))
+		}
+	}
+
 	if _, ok := servers[mcp.ID]; ok {
 		panic(fmt.Sprintf("mcp %s already registered", mcp.ID))
 	}
@@ -102,6 +115,14 @@ func GetMCPServer(id string, config, reusingConfig map[string]string) (Server, e
 		return nil, fmt.Errorf("mcp %s config is invalid: %w", id, err)
 	}
 
+	if embedServer.disableCache {
+		return embedServer.NewServer(config, reusingConfig)
+	}
+
+	if len(reusingConfig) == 0 {
+		return loadCacheServer(embedServer, config)
+	}
+
 	for _, template := range embedServer.ConfigTemplates {
 		switch template.Required {
 		case ConfigRequiredTypeReusingOptional,

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

@@ -3,6 +3,7 @@ package tavily
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"tavily",
 			"Tavily AI Search",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			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.",

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

@@ -3,6 +3,7 @@ package time
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -17,7 +18,7 @@ func init() {
 		mcpservers.NewMcp(
 			"time",
 			"Time",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithGitHubURL(
 				"https://github.com/modelcontextprotocol/servers/tree/main/src/time",

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

@@ -3,6 +3,7 @@ package websearch
 import (
 	_ "embed"
 
+	"github.com/labring/aiproxy/core/model"
 	mcpservers "github.com/labring/aiproxy/mcp-servers"
 )
 
@@ -18,7 +19,7 @@ func init() {
 		mcpservers.NewMcp(
 			"web-search",
 			"Web Search",
-			mcpservers.McpTypeEmbed,
+			model.PublicMCPTypeEmbed,
 			mcpservers.WithNewServerFunc(NewServer),
 			mcpservers.WithConfigTemplates(configTemplates),
 			mcpservers.WithTags([]string{"search", "web", "google", "bing", "arxiv", "searchxng"}),