diff --git a/api/handler.go b/api/handler.go index 63b31c32..17da6d0e 100644 --- a/api/handler.go +++ b/api/handler.go @@ -1,16 +1,19 @@ package api import ( + "context" // "fmt" "net/http" // "os" - + "github.com/gin-gonic/gin" + "github.com/modelcontextprotocol/go-sdk/mcp" + "pansou/config" "pansou/model" "pansou/service" - jsonutil "pansou/util/json" "pansou/util" + jsonutil "pansou/util/json" "strings" ) @@ -32,7 +35,7 @@ func SearchHandler(c *gin.Context) { // GET方式:从URL参数获取 // 获取keyword,必填参数 keyword := c.Query("kw") - + // 处理channels参数,支持逗号分隔 channelsStr := c.Query("channels") var channels []string @@ -46,32 +49,32 @@ func SearchHandler(c *gin.Context) { } } } - + // 处理并发数 concurrency := 0 concStr := c.Query("conc") if concStr != "" && concStr != " " { concurrency = util.StringToInt(concStr) } - + // 处理强制刷新 forceRefresh := false refreshStr := c.Query("refresh") if refreshStr != "" && refreshStr != " " && refreshStr == "true" { forceRefresh = true } - + // 处理结果类型和来源类型 resultType := c.Query("res") if resultType == "" || resultType == " " { resultType = "merge" // 直接设置为默认值merge } - + sourceType := c.Query("src") if sourceType == "" || sourceType == " " { sourceType = "all" // 直接设置为默认值all } - + // 处理plugins参数,支持逗号分隔 var plugins []string // 检查请求中是否存在plugins参数 @@ -91,7 +94,7 @@ func SearchHandler(c *gin.Context) { // 如果请求中不存在plugins参数,设置为nil plugins = nil } - + // 处理cloud_types参数,支持逗号分隔 var cloudTypes []string // 检查请求中是否存在cloud_types参数 @@ -111,7 +114,7 @@ func SearchHandler(c *gin.Context) { // 如果请求中不存在cloud_types参数,设置为nil cloudTypes = nil } - + // 处理ext参数,JSON格式 var ext map[string]interface{} extStr := c.Query("ext") @@ -130,7 +133,7 @@ func SearchHandler(c *gin.Context) { if ext == nil { ext = make(map[string]interface{}) } - + // 处理filter参数,JSON格式 var filter *model.FilterConfig filterStr := c.Query("filter") @@ -167,12 +170,12 @@ func SearchHandler(c *gin.Context) { return } } - + // 检查并设置默认值 if len(req.Channels) == 0 { req.Channels = config.AppConfig.DefaultChannels } - + // 如果未指定结果类型,默认返回merge并转换为merged_by_type if req.ResultType == "" { req.ResultType = "merged_by_type" @@ -180,12 +183,12 @@ func SearchHandler(c *gin.Context) { // 将merge转换为merged_by_type,以兼容内部处理 req.ResultType = "merged_by_type" } - + // 如果未指定数据来源类型,默认为全部 if req.SourceType == "" { req.SourceType = "all" } - + // 参数互斥逻辑:当src=tg时忽略plugins参数,当src=plugin时忽略channels参数 if req.SourceType == "tg" { req.Plugins = nil // 忽略plugins参数 @@ -197,14 +200,14 @@ func SearchHandler(c *gin.Context) { req.Plugins = nil } } - + // 可选:启用调试输出(生产环境建议注释掉) - // fmt.Printf("🔧 [调试] 搜索参数: keyword=%s, channels=%v, concurrency=%d, refresh=%v, resultType=%s, sourceType=%s, plugins=%v, cloudTypes=%v, ext=%v\n", + // fmt.Printf("🔧 [调试] 搜索参数: keyword=%s, channels=%v, concurrency=%d, refresh=%v, resultType=%s, sourceType=%s, plugins=%v, cloudTypes=%v, ext=%v\n", // req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins, req.CloudTypes, req.Ext) - + // 执行搜索 result, err := searchService.Search(req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins, req.CloudTypes, req.Ext) - + if err != nil { response := model.NewErrorResponse(500, "搜索失败: "+err.Error()) jsonData, _ := jsonutil.Marshal(response) @@ -221,4 +224,24 @@ func SearchHandler(c *gin.Context) { response := model.NewSuccessResponse(result) jsonData, _ := jsonutil.Marshal(response) c.Data(http.StatusOK, "application/json", jsonData) -} \ No newline at end of file +} + +func SearchMcpHandler(_ context.Context, request *mcp.CallToolRequest, mcpReq model.McpSearchRequest) (toolCallResult *mcp.CallToolResult, result model.SearchResponse, err error) { + searchReq := model.SearchRequest{ + Keyword: mcpReq.Keyword, + ForceRefresh: mcpReq.ForceRefresh, + Filter: mcpReq.Filter, + CloudTypes: mcpReq.CloudTypes, + Channels: config.AppConfig.DefaultChannels, // 设置默认值 + SourceType: "all", // 默认为全部 + ResultType: "merged_by_type", + Plugins: nil, + } + // 执行搜索 + result, err = searchService.Search(searchReq.Keyword, searchReq.Channels, searchReq.Concurrency, searchReq.ForceRefresh, searchReq.ResultType, searchReq.SourceType, searchReq.Plugins, searchReq.CloudTypes, searchReq.Ext) + if err != nil { + return + } + + return +} diff --git a/api/health.go b/api/health.go new file mode 100644 index 00000000..c216b661 --- /dev/null +++ b/api/health.go @@ -0,0 +1,49 @@ +package api + +import ( + "pansou/config" +) + +type HealthResponse struct { + Status string `json:"status" jsonschema:"状态,如果是ok则表示服务正常"` + AuthEnabled bool `json:"auth_enabled" jsonschema:"是否启用认证,如果启用则需要先通过登录获取token"` + PluginsEnabled bool `json:"plugins_enabled" jsonschema:"是否启用异步插件"` + ChannelsCount int `json:"channels_count" jsonschema:"是否启用异步插件"` + Channels []string `json:"channels" jsonschema:"异步插件列表"` + PluginCount int `json:"plugin_count" jsonschema:"插件数量"` + Plugins []string `json:"plugins" jsonschema:"插件列表"` +} + +func Health() HealthResponse { + // 根据配置决定是否返回插件信息 + pluginCount := 0 + pluginNames := []string{} + pluginsEnabled := config.AppConfig.AsyncPluginEnabled + + if pluginsEnabled && searchService != nil && searchService.GetPluginManager() != nil { + plugins := searchService.GetPluginManager().GetPlugins() + pluginCount = len(plugins) + for _, p := range plugins { + pluginNames = append(pluginNames, p.Name()) + } + } + // 获取频道信息 + channels := config.AppConfig.DefaultChannels + channelsCount := len(channels) + + response := HealthResponse{ + Status: "ok", + AuthEnabled: config.AppConfig.AuthEnabled, // 添加认证状态 + PluginsEnabled: pluginsEnabled, + Channels: channels, + ChannelsCount: channelsCount, + } + + // 只有当插件启用时才返回插件相关信息 + if pluginsEnabled { + response.PluginCount = pluginCount + response.Plugins = pluginNames + } + + return response +} diff --git a/api/mcp_tools.go b/api/mcp_tools.go new file mode 100644 index 00000000..bf31b8da --- /dev/null +++ b/api/mcp_tools.go @@ -0,0 +1,25 @@ +package api + +import ( + "context" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +const ( + McpMethodNameSearch = "search" + McpMethodNameHealth = "health" +) + +// SetupMcpTool 设置MCP工具 +func SetupMcpTool() *mcp.Server { + server := mcp.NewServer(&mcp.Implementation{Name: "pansou", Version: "v2.0"}, nil) + server.AddReceivingMiddleware(McpAuthMiddleware) + mcp.AddTool(server, &mcp.Tool{Name: McpMethodNameHealth, Description: "获取服务器状态"}, func(_ context.Context, request *mcp.CallToolRequest, input map[string]any) (toolCallResult *mcp.CallToolResult, result HealthResponse, _ error) { + result = Health() + return + }) + mcp.AddTool(server, &mcp.Tool{Name: McpMethodNameSearch, Description: "搜索网盘资源"}, SearchMcpHandler) + + return server +} diff --git a/api/middleware.go b/api/middleware.go index 68ae51cc..99d0f9cd 100644 --- a/api/middleware.go +++ b/api/middleware.go @@ -1,12 +1,16 @@ package api import ( + "context" + "errors" "fmt" "net/url" "strings" "time" "github.com/gin-gonic/gin" + "github.com/modelcontextprotocol/go-sdk/mcp" + "pansou/config" "pansou/util" ) @@ -17,12 +21,12 @@ func CORSMiddleware() gin.HandlerFunc { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") - + if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } - + c.Next() } } @@ -32,22 +36,22 @@ func LoggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 开始时间 startTime := time.Now() - + // 处理请求 c.Next() - + // 结束时间 endTime := time.Now() - + // 执行时间 latencyTime := endTime.Sub(startTime) - + // 请求方式 reqMethod := c.Request.Method - + // 请求路由 reqURI := c.Request.RequestURI - + // 对于搜索API,尝试解码关键词以便更好地显示 displayURI := reqURI if strings.Contains(reqURI, "/api/search") && strings.Contains(reqURI, "kw=") { @@ -60,16 +64,16 @@ func LoggerMiddleware() gin.HandlerFunc { } } } - + // 状态码 statusCode := c.Writer.Status() - + // 请求IP clientIP := c.ClientIP() - + // 日志格式 gin.DefaultWriter.Write([]byte( - fmt.Sprintf("| %s | %s | %s | %d | %s\n", + fmt.Sprintf("| %s | %s | %s | %d | %s\n", clientIP, reqMethod, displayURI, statusCode, latencyTime.String()))) } } @@ -138,4 +142,19 @@ func AuthMiddleware() gin.HandlerFunc { c.Set("username", claims.Username) c.Next() } -} \ No newline at end of file +} + +func McpAuthMiddleware(h mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, req mcp.Request) (result mcp.Result, err error) { + // mcp token为空则,直接返回有效 + if config.AppConfig.AuthMcpToken == "" { + return h(ctx, method, req) + } + extra := req.GetExtra() + if extra == nil || extra.Header.Get("Authorization") != config.AppConfig.AuthMcpToken { + return nil, errors.New("未授权:确认McpToken") + } + + return h(ctx, method, req) + } +} diff --git a/api/router.go b/api/router.go index 80090314..d4ecd440 100644 --- a/api/router.go +++ b/api/router.go @@ -1,30 +1,42 @@ package api import ( - "github.com/gin-gonic/gin" + "net/http" "pansou/config" "pansou/plugin" "pansou/service" "pansou/util" + + "github.com/gin-gonic/gin" + "github.com/modelcontextprotocol/go-sdk/mcp" ) // SetupRouter 设置路由 func SetupRouter(searchService *service.SearchService) *gin.Engine { // 设置搜索服务 SetSearchService(searchService) - + // 设置为生产模式 gin.SetMode(gin.ReleaseMode) - + // 创建默认路由 r := gin.Default() - + + // 创建MCP服务,注册到gin路由 + mcpServer := SetupMcpTool() + // 注册MCP路由 + handler := mcp.NewStreamableHTTPHandler(func(request *http.Request) *mcp.Server { + return mcpServer + }, &mcp.StreamableHTTPOptions{}) + // 不走gin的http中间件 + r.Any("/mcp", gin.WrapH(handler)) + // 添加中间件 r.Use(CORSMiddleware()) r.Use(LoggerMiddleware()) r.Use(util.GzipMiddleware()) // 添加压缩中间件 r.Use(AuthMiddleware()) // 添加认证中间件 - + // 定义API路由组 api := r.Group("/api") { @@ -35,48 +47,17 @@ func SetupRouter(searchService *service.SearchService) *gin.Engine { auth.POST("/verify", VerifyHandler) auth.POST("/logout", LogoutHandler) } - + // 搜索接口 - 支持POST和GET两种方式 api.POST("/search", SearchHandler) api.GET("/search", SearchHandler) // 添加GET方式支持 - + // 健康检查接口 api.GET("/health", func(c *gin.Context) { - // 根据配置决定是否返回插件信息 - pluginCount := 0 - pluginNames := []string{} - pluginsEnabled := config.AppConfig.AsyncPluginEnabled - - if pluginsEnabled && searchService != nil && searchService.GetPluginManager() != nil { - plugins := searchService.GetPluginManager().GetPlugins() - pluginCount = len(plugins) - for _, p := range plugins { - pluginNames = append(pluginNames, p.Name()) - } - } - - // 获取频道信息 - channels := config.AppConfig.DefaultChannels - channelsCount := len(channels) - - response := gin.H{ - "status": "ok", - "auth_enabled": config.AppConfig.AuthEnabled, // 添加认证状态 - "plugins_enabled": pluginsEnabled, - "channels": channels, - "channels_count": channelsCount, - } - - // 只有当插件启用时才返回插件相关信息 - if pluginsEnabled { - response["plugin_count"] = pluginCount - response["plugins"] = pluginNames - } - - c.JSON(200, response) + c.JSON(200, Health()) }) } - + // 注册插件的Web路由(如果插件实现了PluginWithWebHandler接口) // 只有当插件功能启用且插件在启用列表中时才注册路由 if config.AppConfig.AsyncPluginEnabled && searchService != nil && searchService.GetPluginManager() != nil { @@ -87,6 +68,6 @@ func SetupRouter(searchService *service.SearchService) *gin.Engine { } } } - + return r -} \ No newline at end of file +} diff --git a/config/config.go b/config/config.go index c795d713..c9931fe5 100644 --- a/config/config.go +++ b/config/config.go @@ -52,6 +52,7 @@ type Config struct { AuthUsers map[string]string // 用户名:密码映射 AuthTokenExpiry time.Duration // Token有效期 AuthJWTSecret string // JWT签名密钥 + AuthMcpToken string // 设置MCP token,如果不设置则默认mcp不验证 } @@ -63,7 +64,7 @@ func Init() { proxyURL := getProxyURL() pluginTimeoutSeconds := getPluginTimeout() asyncResponseTimeoutSeconds := getAsyncResponseTimeout() - + AppConfig = &Config{ DefaultChannels: getDefaultChannels(), DefaultConcurrency: getDefaultConcurrency(), @@ -105,9 +106,9 @@ func Init() { AuthUsers: getAuthUsers(), AuthTokenExpiry: getAuthTokenExpiry(), AuthJWTSecret: getAuthJWTSecret(), - + AuthMcpToken: getAuthMcpToken(), } - + // 应用GC配置 applyGCSettings() } @@ -130,11 +131,11 @@ func getDefaultConcurrency() int { return concurrency } } - + // 环境变量未设置或无效,使用基于环境变量的简单计算 // 计算频道数 channelCount := len(getDefaultChannels()) - + // 估计插件数(从环境变量或默认值,实际在应用启动后会根据真实插件数调整) pluginCountEnv := os.Getenv("PLUGIN_COUNT") pluginCount := 0 @@ -144,18 +145,18 @@ func getDefaultConcurrency() int { pluginCount = count } } - + // 如果没有指定插件数,默认使用7个(当前已知的插件数) if pluginCount == 0 { pluginCount = 7 } - + // 计算并发数 = 频道数 + 插件数 + 10 concurrency := channelCount + pluginCount + 10 if concurrency < 1 { concurrency = 1 // 确保至少为1 } - + return concurrency } @@ -165,22 +166,22 @@ func UpdateDefaultConcurrency(pluginCount int) { if AppConfig == nil { return } - + // 只有当未通过环境变量指定并发数时才进行调整 concurrencyEnv := os.Getenv("CONCURRENCY") if concurrencyEnv != "" { return } - + // 计算频道数 channelCount := len(AppConfig.DefaultChannels) - + // 计算并发数 = 频道数 + 插件数(插件禁用时为0)+ 10 concurrency := channelCount + pluginCount + 10 if concurrency < 1 { concurrency = 1 // 确保至少为1 } - + // 更新配置 AppConfig.DefaultConcurrency = concurrency } @@ -337,12 +338,12 @@ func getEnabledPlugins() []string { // 未设置环境变量时返回nil,表示不启用任何插件 return nil } - + if plugins == "" { // 设置为空字符串,也表示不启用任何插件 return []string{} } - + // 按逗号分割插件名 result := make([]string, 0) for _, plugin := range strings.Split(plugins, ",") { @@ -351,7 +352,7 @@ func getEnabledPlugins() []string { result = append(result, plugin) } } - + return result } @@ -377,17 +378,17 @@ func getAsyncMaxBackgroundWorkers() int { return size } } - + // 自动计算:根据CPU核心数计算 // 每个CPU核心分配5个工作者,最小20个 cpuCount := runtime.NumCPU() workers := cpuCount * 5 - + // 确保至少有20个工作者 if workers < 20 { workers = 20 } - + return workers } @@ -400,16 +401,16 @@ func getAsyncMaxBackgroundTasks() int { return size } } - + // 自动计算:工作者数量的5倍,最小100个 workers := getAsyncMaxBackgroundWorkers() tasks := workers * 5 - + // 确保至少有100个任务 if tasks < 100 { tasks = 100 } - + return tasks } @@ -435,20 +436,20 @@ func getHTTPReadTimeout() time.Duration { return time.Duration(timeout) * time.Second } } - + // 自动计算:默认30秒,异步模式下根据异步响应超时调整 timeout := 30 * time.Second - + // 如果启用了异步插件,确保读取超时足够长 if getAsyncPluginEnabled() { // 读取超时应该至少是异步响应超时的3倍,确保有足够时间完成异步操作 asyncTimeoutSecs := getAsyncResponseTimeout() - asyncTimeoutExtended := time.Duration(asyncTimeoutSecs * 3) * time.Second + asyncTimeoutExtended := time.Duration(asyncTimeoutSecs*3) * time.Second if asyncTimeoutExtended > timeout { timeout = asyncTimeoutExtended } } - + return timeout } @@ -461,20 +462,20 @@ func getHTTPWriteTimeout() time.Duration { return time.Duration(timeout) * time.Second } } - + // 自动计算:默认60秒,但根据插件超时和异步处理时间调整 timeout := 60 * time.Second - + // 如果启用了异步插件,确保写入超时足够长 pluginTimeoutSecs := getPluginTimeout() - + // 计算1.5倍的插件超时时间(使用整数运算:乘以3再除以2) - pluginTimeoutExtended := time.Duration(pluginTimeoutSecs * 3 / 2) * time.Second - + pluginTimeoutExtended := time.Duration(pluginTimeoutSecs*3/2) * time.Second + if pluginTimeoutExtended > timeout { timeout = pluginTimeoutExtended } - + return timeout } @@ -487,7 +488,7 @@ func getHTTPIdleTimeout() time.Duration { return time.Duration(timeout) * time.Second } } - + // 自动计算:默认120秒,考虑到保持连接的效益 return 120 * time.Second } @@ -501,17 +502,17 @@ func getHTTPMaxConns() int { return maxConns } } - + // 自动计算:根据CPU核心数计算 // 每个CPU核心分配200个连接,最小1000个 cpuCount := runtime.NumCPU() maxConns := cpuCount * 200 - + // 确保至少有1000个连接 if maxConns < 1000 { maxConns = 1000 } - + return maxConns } @@ -540,7 +541,7 @@ func getAuthUsers() map[string]string { if usersEnv == "" { return nil } - + users := make(map[string]string) pairs := strings.Split(usersEnv, ",") for _, pair := range pairs { @@ -585,16 +586,19 @@ func getAuthJWTSecret() string { return secret } +// 从环境变量获取Mcp Token,为空则MCP不验证 +func getAuthMcpToken() string { + return os.Getenv("AUTH_MCP_TOKEN") +} + // 应用GC设置 func applyGCSettings() { // 设置GC百分比 debug.SetGCPercent(AppConfig.GCPercent) - + // 如果启用内存优化 if AppConfig.OptimizeMemory { // 释放操作系统内存 debug.FreeOSMemory() } } - - \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 27e50372..07e92060 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,8 @@ services: # - AUTH_USERS=admin:admin123,user:pass456 # - AUTH_TOKEN_EXPIRY=24 # - AUTH_JWT_SECRET=your-secret-key-here + # 如何需要认证mcp 则填写,不填写默认不验证mcp,不使用上面的用户名及密码是为了省去AI每次要判断token的有效请 + # - AUTH_MCP_TOKEN=your-token-mcp # 如果需要代理,取消下面的注释并设置代理地址 # - PROXY=socks5://proxy:7897 volumes: @@ -54,4 +56,4 @@ volumes: networks: pansou-network: - name: pansou-network \ No newline at end of file + name: pansou-network \ No newline at end of file diff --git a/docs/MCP-SERVICE.md b/docs/MCP-SERVICE.md index 9e68bf3c..23081f72 100644 --- a/docs/MCP-SERVICE.md +++ b/docs/MCP-SERVICE.md @@ -189,10 +189,28 @@ node .\typescript\dist\index.js } ``` +- 添加streamable方式的MCP +```json +{ + "mcpServers": { + "pansou": { + "type": "streamableHttp", + "description": "", + "isActive": true, + "baseUrl": "http://localhost:8888/mcp", + "headers": { + "Authorization": "your-mcp-token" + } + } + } +} +``` + **注意**: - 请将 `C:\\full\\path\\to\\your\\project` 替换为您项目实际的完整路径 - 如需强制指定部署模式,可修改 `DOCKER_MODE` 和 `AUTO_START_BACKEND` 参数 - **重要**:从当前版本开始,必须通过 `ENABLED_PLUGINS` 显式指定要启用的插件,否则不会启用任何插件 +- **注意**:如果启动的时候指定了AUTH_MCP_TOKEN环境变量,则streamable的方式连接的时候才需要验证Authorization ### 4. 启动 MCP 服务并开始使用 diff --git a/go.mod b/go.mod index 71a4088f..76209132 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/bytedance/sonic v1.14.0 github.com/gin-gonic/gin v1.9.1 - github.com/golang-jwt/jwt/v5 v5.2.0 + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/modelcontextprotocol/go-sdk v1.4.0 golang.org/x/net v0.41.0 ) @@ -24,6 +25,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/jsonschema-go v0.4.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -32,11 +34,15 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/robertkrimen/otto v0.5.1 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.3 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.39.0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.26.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect diff --git a/go.sum b/go.sum index 0a2f0eaf..cd8b416c 100644 --- a/go.sum +++ b/go.sum @@ -33,12 +33,15 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -49,6 +52,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8= +github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -60,6 +65,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0= github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w= +github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -77,6 +86,8 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -92,6 +103,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -103,8 +116,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -118,8 +131,9 @@ golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= diff --git a/model/request.go b/model/request.go index 9b643f7f..07bb0dae 100644 --- a/model/request.go +++ b/model/request.go @@ -8,14 +8,22 @@ type FilterConfig struct { // SearchRequest 搜索请求参数 type SearchRequest struct { - Keyword string `json:"kw" binding:"required"` // 搜索关键词 - Channels []string `json:"channels"` // 搜索的频道列表 - Concurrency int `json:"conc"` // 并发搜索数量 - ForceRefresh bool `json:"refresh"` // 强制刷新,不使用缓存 - ResultType string `json:"res"` // 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type) - SourceType string `json:"src"` // 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) - Plugins []string `json:"plugins"` // 指定搜索的插件列表,不指定则搜索全部插件 - Ext map[string]interface{} `json:"ext"` // 扩展参数,用于传递给插件的自定义参数 - CloudTypes []string `json:"cloud_types"` // 指定返回的网盘类型列表,不指定则返回所有类型 - Filter *FilterConfig `json:"filter,omitempty"` // 过滤配置,用于过滤返回结果 -} \ No newline at end of file + Keyword string `json:"kw" binding:"required"` // 搜索关键词 + Channels []string `json:"channels"` // 搜索的频道列表 + Concurrency int `json:"conc"` // 并发搜索数量 + ForceRefresh bool `json:"refresh"` // 强制刷新,不使用缓存 + ResultType string `json:"res"` // 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type) + SourceType string `json:"src"` // 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) + Plugins []string `json:"plugins"` // 指定搜索的插件列表,不指定则搜索全部插件 + Ext map[string]interface{} `json:"ext"` // 扩展参数,用于传递给插件的自定义参数 + CloudTypes []string `json:"cloud_types"` // 指定返回的网盘类型列表,不指定则返回所有类型 + Filter *FilterConfig `json:"filter,omitempty"` // 过滤配置,用于过滤返回结果 +} + +// McpSearchRequest MCP搜索请求参数简化,避免AI乱使用参数 +type McpSearchRequest struct { + Keyword string `json:"kw" jsonschema:"搜索关键词"` + CloudTypes []string `json:"cloud_types" jsonschema:"指定返回的网盘类型列表,不指定则返回所有类型,支持的类型:baidu,aliyun,quark,tianyi,uc,mobile,115,pikpak,xunlei,123,magnet,ed2k,others"` + ForceRefresh bool `json:"refresh" jsonschema:"强制刷新,不使用缓存"` + Filter *FilterConfig `json:"filter,omitempty" jsonschema:"过滤配置,用于过滤返回结果"` +}