diff --git a/.golangci.yml b/.golangci.yml index 4500cab7..d54c31f0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -43,6 +43,7 @@ linters: - loggercheck - mirror - misspell + - modernize - musttag - nakedret - noctx @@ -97,12 +98,21 @@ linters: errcheck: check-type-assertions: true forbidigo: + forbid: + - pattern: ^print(ln)?$ analyze-types: true prealloc: for-loops: true staticcheck: dot-import-whitelist: [] http-status-code-whitelist: [] + modernize: + disable: + - forvar + - omitzero + perfsprint: + # use modernize stringsbuilder + concat-loop: false usestdlibvars: time-date-month: true time-month: true @@ -136,8 +146,6 @@ formatters: settings: gofmt: rewrite-rules: - - pattern: "interface{}" - replacement: "any" - pattern: "a[b:len(a)]" replacement: "a[b:]" gofumpt: diff --git a/core/controller/mcp/groupmcp-server.go b/core/controller/mcp/groupmcp-server.go index 1413138d..e889f373 100644 --- a/core/controller/mcp/groupmcp-server.go +++ b/core/controller/mcp/groupmcp-server.go @@ -1,6 +1,7 @@ package controller import ( + "maps" "net/http" "net/url" @@ -167,13 +168,9 @@ func handleGroupProxyStreamable(c *gin.Context, config *model.GroupMCPProxyConfi return } - headers := make(map[string]string) + headers := maps.Clone(config.Headers) backendQuery := backendURL.Query() - for k, v := range config.Headers { - headers[k] = v - } - for k, v := range config.Querys { backendQuery.Set(k, v) } diff --git a/core/controller/mcp/publicmcp-server.go b/core/controller/mcp/publicmcp-server.go index 09851d73..f73a3e40 100644 --- a/core/controller/mcp/publicmcp-server.go +++ b/core/controller/mcp/publicmcp-server.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "maps" "net/http" "net/url" @@ -192,9 +193,7 @@ func prepareProxyConfig( } } - for k, v := range publicMcp.ProxyConfig.Headers { - headers[k] = v - } + maps.Copy(headers, publicMcp.ProxyConfig.Headers) url.RawQuery = backendQuery.Encode() @@ -398,9 +397,7 @@ func handlePublicProxyStreamable( return } - for k, v := range config.Headers { - headers[k] = v - } + maps.Copy(headers, config.Headers) for k, v := range config.Querys { backendQuery.Set(k, v) diff --git a/core/model/channel.go b/core/model/channel.go index 0e0f1b01..2923b45e 100644 --- a/core/model/channel.go +++ b/core/model/channel.go @@ -536,6 +536,7 @@ func DeleteChannelsByIDs(ids []int) (err error) { defer func() { if err == nil { _ = InitModelConfigAndChannelCache() + for _, id := range ids { _ = monitor.ClearChannelAllModelErrors(context.Background(), id) } diff --git a/core/relay/adaptor/ali/stt-realtime.go b/core/relay/adaptor/ali/stt-realtime.go index 51b5767e..b55fd17f 100644 --- a/core/relay/adaptor/ali/stt-realtime.go +++ b/core/relay/adaptor/ali/stt-realtime.go @@ -223,10 +223,7 @@ func STTDoResponse( case "task-started": chunkSize := 3 * 1024 for i := 0; i < len(audioData); i += chunkSize { - end := i + chunkSize - if end > len(audioData) { - end = len(audioData) - } + end := min(i+chunkSize, len(audioData)) chunk := audioData[i:end] diff --git a/core/relay/adaptor/anthropic/main.go b/core/relay/adaptor/anthropic/main.go index 8836eba1..b1699008 100644 --- a/core/relay/adaptor/anthropic/main.go +++ b/core/relay/adaptor/anthropic/main.go @@ -242,6 +242,8 @@ func StreamHandler( writed bool ) + streamState := NewStreamState() + for scanner.Scan() { data := scanner.Bytes() if !render.IsValidSSEData(data) { @@ -253,7 +255,7 @@ func StreamHandler( break } - response, err := StreamResponse2OpenAI(m, data) + response, err := streamState.StreamResponse2OpenAI(m, data) if err != nil { if writed { log.Errorf("response error: %+v", err) diff --git a/core/relay/adaptor/anthropic/openai.go b/core/relay/adaptor/anthropic/openai.go index 348c16d6..228077fb 100644 --- a/core/relay/adaptor/anthropic/openai.go +++ b/core/relay/adaptor/anthropic/openai.go @@ -268,11 +268,7 @@ func batchPatchImage2Base64(ctx context.Context, imageTasks []*relaymodel.Claude continue } - wg.Add(1) - - go func() { - defer wg.Done() - + wg.Go(func() { _ = sem.Acquire(ctx, 1) defer sem.Release(1) @@ -291,7 +287,7 @@ func batchPatchImage2Base64(ctx context.Context, imageTasks []*relaymodel.Claude task.Source.URL = "" task.Source.MediaType = mimeType task.Source.Data = data - }() + }) } wg.Wait() @@ -303,8 +299,42 @@ func batchPatchImage2Base64(ctx context.Context, imageTasks []*relaymodel.Claude return nil } -// https://docs.anthropic.com/claude/reference/messages-streaming -func StreamResponse2OpenAI( +// StreamState maintains state during streaming response conversion +type StreamState struct { + // claudeIndexToToolCallIndex maps Claude's content block index to OpenAI tool call index + // Claude's index includes all content blocks (text, thinking, tool_use), but OpenAI only counts tool calls + claudeIndexToToolCallIndex map[int]int + // nextToolCallIndex tracks the next tool call index to assign (0-based) + nextToolCallIndex int +} + +func NewStreamState() *StreamState { + return &StreamState{ + claudeIndexToToolCallIndex: make(map[int]int), + nextToolCallIndex: 0, + } +} + +// getToolCallIndex returns the OpenAI tool call index for a given Claude content block index +// If this is the first time seeing this Claude index for a tool call, assigns a new tool call index +func (s *StreamState) getToolCallIndex(claudeIndex int, isNewToolCall bool) int { + if idx, exists := s.claudeIndexToToolCallIndex[claudeIndex]; exists { + return idx + } + + if isNewToolCall { + toolCallIndex := s.nextToolCallIndex + s.claudeIndexToToolCallIndex[claudeIndex] = toolCallIndex + s.nextToolCallIndex++ + return toolCallIndex + } + + // This shouldn't happen in normal flow, but return a safe default + return 0 +} + +// StreamResponse2OpenAI converts Claude streaming response to OpenAI format +func (s *StreamState) StreamResponse2OpenAI( meta *meta.Meta, respData []byte, ) (*relaymodel.ChatCompletionsStreamResponse, adaptor.Error) { @@ -340,8 +370,9 @@ func StreamResponse2OpenAI( if claudeResponse.ContentBlock != nil { content = claudeResponse.ContentBlock.Text if claudeResponse.ContentBlock.Type == toolUseType { + toolCallIndex := s.getToolCallIndex(claudeResponse.Index, true) tools = append(tools, relaymodel.ToolCall{ - Index: claudeResponse.Index, + Index: toolCallIndex, ID: claudeResponse.ContentBlock.ID, Type: "function", Function: relaymodel.Function{ @@ -354,8 +385,9 @@ func StreamResponse2OpenAI( if claudeResponse.Delta != nil { switch claudeResponse.Delta.Type { case "input_json_delta": + toolCallIndex := s.getToolCallIndex(claudeResponse.Index, false) tools = append(tools, relaymodel.ToolCall{ - Index: claudeResponse.Index, + Index: toolCallIndex, Type: "function", Function: relaymodel.Function{ Arguments: claudeResponse.Delta.PartialJSON, @@ -393,6 +425,7 @@ func StreamResponse2OpenAI( ToolCalls: tools, Role: "assistant", }, + Index: 0, FinishReason: stopReasonClaude2OpenAI(stopReason), } @@ -445,8 +478,9 @@ func Response2OpenAI( case toolUseType: args, _ := sonic.MarshalString(v.Input) tools = append(tools, relaymodel.ToolCall{ - ID: v.ID, - Type: "function", + Index: len(tools), + ID: v.ID, + Type: "function", Function: relaymodel.Function{ Name: v.Name, Arguments: args, @@ -514,6 +548,8 @@ func OpenAIStreamHandler( writed bool ) + streamState := NewStreamState() + for scanner.Scan() { data := scanner.Bytes() if !render.IsValidSSEData(data) { @@ -525,7 +561,7 @@ func OpenAIStreamHandler( break } - response, err := StreamResponse2OpenAI(m, data) + response, err := streamState.StreamResponse2OpenAI(m, data) if err != nil { if writed { log.Errorf("response error: %+v", err) diff --git a/core/relay/adaptor/aws/claude/claude.go b/core/relay/adaptor/aws/claude/claude.go index ec4e1f4a..0b5d1307 100644 --- a/core/relay/adaptor/aws/claude/claude.go +++ b/core/relay/adaptor/aws/claude/claude.go @@ -79,6 +79,8 @@ func StreamHandler(meta *meta.Meta, c *gin.Context) (model.Usage, adaptor.Error) writed bool ) + streamState := anthropic.NewStreamState() + log := common.GetLogger(c) for event := range stream.Events() { @@ -86,7 +88,7 @@ func StreamHandler(meta *meta.Meta, c *gin.Context) (model.Usage, adaptor.Error) case *types.ResponseStreamMemberChunk: data := v.Value.Bytes - response, err := anthropic.StreamResponse2OpenAI(meta, v.Value.Bytes) + response, err := streamState.StreamResponse2OpenAI(meta, v.Value.Bytes) if err != nil { if writed { log.Errorf("response error: %+v", err) diff --git a/core/relay/adaptor/aws/claude/openai.go b/core/relay/adaptor/aws/claude/openai.go index 558f2b49..52ea1310 100644 --- a/core/relay/adaptor/aws/claude/openai.go +++ b/core/relay/adaptor/aws/claude/openai.go @@ -90,12 +90,14 @@ func OpenaiStreamHandler(meta *meta.Meta, c *gin.Context) (model.Usage, adaptor. writed bool ) + streamState := anthropic.NewStreamState() + log := common.GetLogger(c) for event := range stream.Events() { switch v := event.(type) { case *types.ResponseStreamMemberChunk: - response, err := anthropic.StreamResponse2OpenAI(meta, v.Value.Bytes) + response, err := streamState.StreamResponse2OpenAI(meta, v.Value.Bytes) if err != nil { if writed { log.Errorf("response error: %+v", err) diff --git a/core/relay/adaptor/cohere/main.go b/core/relay/adaptor/cohere/main.go index 19772618..e1ef1f90 100644 --- a/core/relay/adaptor/cohere/main.go +++ b/core/relay/adaptor/cohere/main.go @@ -52,8 +52,8 @@ func ConvertRequest(textRequest *relaymodel.GeneralOpenAIRequest) *Request { cohereRequest.Model = "command-r" } - if strings.HasSuffix(cohereRequest.Model, "-internet") { - cohereRequest.Model = strings.TrimSuffix(cohereRequest.Model, "-internet") + if before, ok := strings.CutSuffix(cohereRequest.Model, "-internet"); ok { + cohereRequest.Model = before cohereRequest.Connectors = append(cohereRequest.Connectors, WebSearchConnector) } diff --git a/core/relay/adaptor/gemini/main.go b/core/relay/adaptor/gemini/main.go index 024b3cf5..f97b0a61 100644 --- a/core/relay/adaptor/gemini/main.go +++ b/core/relay/adaptor/gemini/main.go @@ -380,11 +380,7 @@ func processImageTasks(ctx context.Context, imageTasks []*Part) error { continue } - wg.Add(1) - - go func() { - defer wg.Done() - + wg.Go(func() { _ = sem.Acquire(ctx, 1) defer sem.Release(1) @@ -401,7 +397,7 @@ func processImageTasks(ctx context.Context, imageTasks []*Part) error { task.InlineData.MimeType = mimeType task.InlineData.Data = data - }() + }) } wg.Wait() diff --git a/core/relay/controller/dohelper.go b/core/relay/controller/dohelper.go index 2ea02918..392f7886 100644 --- a/core/relay/controller/dohelper.go +++ b/core/relay/controller/dohelper.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "maps" "net/http" "strings" "sync" @@ -228,9 +229,7 @@ func setupRequestHeader( req *http.Request, header http.Header, ) adaptor.Error { - for key, value := range header { - req.Header[key] = value - } + maps.Copy(req.Header, header) if err := a.SetupRequestHeader(meta, store, c, req); err != nil { return relaymodel.WrapperErrorWithMessage( diff --git a/core/relay/plugin/cache/cache.go b/core/relay/plugin/cache/cache.go index 610ac64b..1cad5ad2 100644 --- a/core/relay/plugin/cache/cache.go +++ b/core/relay/plugin/cache/cache.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + "maps" "net/http" "strconv" "sync" @@ -388,10 +389,7 @@ func (c *Cache) DoResponse( } // Convert http.Header to map[string][]string for JSON serialization - headerMap := make(map[string][]string) - for k, v := range rw.Header() { - headerMap[k] = v - } + headerMap := maps.Clone(rw.Header()) // Store in cache item := Item{ diff --git a/core/relay/plugin/thinksplit/splitter/splitter.go b/core/relay/plugin/thinksplit/splitter/splitter.go index 6bc8e609..b763fd23 100644 --- a/core/relay/plugin/thinksplit/splitter/splitter.go +++ b/core/relay/plugin/thinksplit/splitter/splitter.go @@ -159,10 +159,7 @@ func (s *Splitter) processSeekTail() ([]byte, []byte) { if data[i] == tail[j] { j++ if j == tailLen { - end := i - tailLen + 1 - if end < 0 { - end = 0 - } + end := max(i-tailLen+1, 0) result := data[:end] remaining := data[i+1:] diff --git a/core/relay/plugin/web-search/search.go b/core/relay/plugin/web-search/search.go index e214fa5f..1da29c7d 100644 --- a/core/relay/plugin/web-search/search.go +++ b/core/relay/plugin/web-search/search.go @@ -491,7 +491,7 @@ func (p *WebSearch) generateSearchContexts( // parseSearchContexts extracts search queries from LLM response func (p *WebSearch) parseSearchContexts(defaultLanguage, content string) []engine.SearchQuery { var searchContexts []engine.SearchQuery - for _, line := range strings.Split(content, "\n") { + for line := range strings.SplitSeq(content, "\n") { line = strings.TrimSpace(line) if line == "" { continue diff --git a/core/task/task.go b/core/task/task.go index 0d4ce380..045e2eba 100644 --- a/core/task/task.go +++ b/core/task/task.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "slices" + "strings" "time" "github.com/bytedance/sonic" @@ -209,18 +210,18 @@ func formatGroupUsageAlerts(alerts []model.GroupUsageAlertItem) string { return "" } - var result string + var result strings.Builder for _, alert := range alerts { - result += fmt.Sprintf( + result.WriteString(fmt.Sprintf( "GroupID: %s | 3-Day Avg: %.4f | Today: %.4f | Ratio: %.2fx\n", alert.GroupID, alert.ThreeDayAvgAmount, alert.TodayAmount, alert.Ratio, - ) + )) } - return result + return result.String() } // CleanLogTask 清理日志任务 diff --git a/mcp-servers/hosted/12306/parsers.go b/mcp-servers/hosted/12306/parsers.go index 73b0ff86..17662f20 100644 --- a/mcp-servers/hosted/12306/parsers.go +++ b/mcp-servers/hosted/12306/parsers.go @@ -3,6 +3,7 @@ package train12306 import ( "fmt" "regexp" + "slices" "sort" "strconv" "strings" @@ -178,9 +179,12 @@ func (s *Server) formatTicketsInfo(ticketsInfo []TicketInfo) string { return "没有查询到相关车次信息" } - result := "车次 | 出发站 -> 到达站 | 出发时间 -> 到达时间 | 历时\n" + var result strings.Builder + result.WriteString("车次 | 出发站 -> 到达站 | 出发时间 -> 到达时间 | 历时\n") + for _, ticketInfo := range ticketsInfo { - infoStr := fmt.Sprintf( + var infoStr strings.Builder + infoStr.WriteString(fmt.Sprintf( "%s(实际车次train_no: %s) %s(telecode: %s) -> %s(telecode: %s) %s -> %s 历时:%s", ticketInfo.StartTrainCode, ticketInfo.TrainNo, @@ -191,17 +195,19 @@ func (s *Server) formatTicketsInfo(ticketsInfo []TicketInfo) string { ticketInfo.StartTime, ticketInfo.ArriveTime, ticketInfo.Lishi, - ) + )) for _, price := range ticketInfo.Prices { ticketStatus := s.formatTicketStatus(price.Num) - infoStr += fmt.Sprintf("\n- %s: %s %.1f元", price.SeatName, ticketStatus, price.Price) + infoStr.WriteString( + fmt.Sprintf("\n- %s: %s %.1f元", price.SeatName, ticketStatus, price.Price), + ) } - result += infoStr + "\n" + result.WriteString(infoStr.String() + "\n") } - return result + return result.String() } // filterTicketsInfo filters and sorts ticket information @@ -271,13 +277,7 @@ func (s *Server) matchesTrainFilter(ticketInfo TicketInfo, filter string) bool { // containsFlag checks if the flag list contains a specific flag func (s *Server) containsFlag(flags []string, flag string) bool { - for _, f := range flags { - if f == flag { - return true - } - } - - return false + return slices.Contains(flags, flag) } // sortTicketsInfo sorts ticket information @@ -855,40 +855,43 @@ func (s *Server) compareInterlineDuration(a, b InterlineInfo) int { // formatInterlinesInfo formats interline information for display func (s *Server) formatInterlinesInfo(interlinesInfo []InterlineInfo) string { - result := "出发时间 -> 到达时间 | 出发车站 -> 中转车站 -> 到达车站 | 换乘标志 |换乘等待时间| 总历时\n\n" + var result strings.Builder + result.WriteString("出发时间 -> 到达时间 | 出发车站 -> 中转车站 -> 到达车站 | 换乘标志 |换乘等待时间| 总历时\n\n") for _, interlineInfo := range interlinesInfo { - result += fmt.Sprintf( + result.WriteString(fmt.Sprintf( "%s %s -> %s %s | ", interlineInfo.StartDate, interlineInfo.StartTime, interlineInfo.ArriveDate, interlineInfo.ArriveTime, - ) - result += fmt.Sprintf( + )) + result.WriteString(fmt.Sprintf( "%s -> %s -> %s | ", interlineInfo.FromStationName, interlineInfo.MiddleStationName, interlineInfo.EndStationName, - ) + )) switch { case interlineInfo.SameStation: - result += "同站换乘" + result.WriteString("同站换乘") case interlineInfo.SameTrain: - result += "同车换乘" + result.WriteString("同车换乘") default: - result += "换站换乘" + result.WriteString("换站换乘") } - result += fmt.Sprintf(" | %s | %s\n\n", interlineInfo.WaitTime, interlineInfo.Lishi) - result += "\t" + strings.ReplaceAll( + result.WriteString( + fmt.Sprintf(" | %s | %s\n\n", interlineInfo.WaitTime, interlineInfo.Lishi), + ) + result.WriteString("\t" + strings.ReplaceAll( s.formatTicketsInfo(interlineInfo.TicketList), "\n", "\n\t", - ) - result += "\n" + )) + result.WriteString("\n") } - return result + return result.String() } diff --git a/mcp-servers/hosted/12306/tools.go b/mcp-servers/hosted/12306/tools.go index 94ee80bd..9528bf12 100644 --- a/mcp-servers/hosted/12306/tools.go +++ b/mcp-servers/hosted/12306/tools.go @@ -103,7 +103,7 @@ func (s *Server) addGetStationCodeOfCitysTool() { } result := make(map[string]any) - for _, city := range strings.Split(citys, "|") { + for city := range strings.SplitSeq(citys, "|") { if station, exists := s.cityStationCodes[city]; exists { result[city] = station } else { @@ -147,7 +147,7 @@ func (s *Server) addGetStationCodeByNamesTool() { } result := make(map[string]any) - for _, stationName := range strings.Split(stationNames, "|") { + for stationName := range strings.SplitSeq(stationNames, "|") { cleanName := strings.TrimSuffix(stationName, "站") if station, exists := s.nameStations[cleanName]; exists { diff --git a/mcp-servers/hosted/firecrawl/server.go b/mcp-servers/hosted/firecrawl/server.go index d24e01e9..9218992b 100644 --- a/mcp-servers/hosted/firecrawl/server.go +++ b/mcp-servers/hosted/firecrawl/server.go @@ -132,11 +132,8 @@ func (s *Server) withRetry( strings.Contains(err.Error(), "429") if isRateLimit && attempt < s.config.MaxAttempts { - delay := time.Duration(float64(s.config.InitialDelay) * - float64(int(1)<<(attempt-1)) * s.config.BackoffFactor) - if delay > s.config.MaxDelay { - delay = s.config.MaxDelay - } + delay := min(time.Duration(float64(s.config.InitialDelay)* + float64(int(1)<<(attempt-1))*s.config.BackoffFactor), s.config.MaxDelay) select { case <-time.After(delay): diff --git a/mcp-servers/hosted/hefeng-weather/server.go b/mcp-servers/hosted/hefeng-weather/server.go index 9adf500f..dae03c3b 100644 --- a/mcp-servers/hosted/hefeng-weather/server.go +++ b/mcp-servers/hosted/hefeng-weather/server.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "slices" "strconv" "strings" "time" @@ -154,10 +155,8 @@ func ListTools(ctx context.Context) ([]mcp.Tool, error) { // validateDays validates the days parameter func validateDays(days string) error { validDays := []string{"now", "24h", "72h", "168h", "3d", "7d", "10d", "15d", "30d"} - for _, validDay := range validDays { - if days == validDay { - return nil - } + if slices.Contains(validDays, days) { + return nil } return fmt.Errorf("无效的预报天数: %s,有效值为: %s", days, strings.Join(validDays, ", ")) diff --git a/mcp-servers/hosted/howtocook/server.go b/mcp-servers/hosted/howtocook/server.go index b22a83ec..04dd3074 100644 --- a/mcp-servers/hosted/howtocook/server.go +++ b/mcp-servers/hosted/howtocook/server.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "math/rand" + "slices" "strings" "github.com/bytedance/sonic" @@ -780,13 +781,7 @@ func (s *Server) processRecipeIngredients(recipe Recipe, ingredientMap map[strin existingItem.RecipeCount++ // Add recipe name if not already present - found := false - for _, recipeName := range existingItem.Recipes { - if recipeName == recipe.Name { - found = true - break - } - } + found := slices.Contains(existingItem.Recipes, recipe.Name) if !found { existingItem.Recipes = append(existingItem.Recipes, recipe.Name) diff --git a/mcp-servers/hosted/jina-tools/jina.go b/mcp-servers/hosted/jina-tools/jina.go index 9d2a7dc4..153aed2f 100644 --- a/mcp-servers/hosted/jina-tools/jina.go +++ b/mcp-servers/hosted/jina-tools/jina.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "maps" "net/http" "net/url" "strconv" @@ -135,12 +136,7 @@ func ListTools(ctx context.Context) ([]mcp.Tool, error) { // createHeaders creates HTTP headers with optional API key func (s *JinaServer) createHeaders(baseHeaders map[string]string) map[string]string { - headers := make(map[string]string) - - // Copy base headers - for k, v := range baseHeaders { - headers[k] = v - } + headers := maps.Clone(baseHeaders) // Add authorization if API key is available if s.apiKey != "" { diff --git a/mcp-servers/hosted/nothon/utils.go b/mcp-servers/hosted/nothon/utils.go index d6368c22..1d287942 100644 --- a/mcp-servers/hosted/nothon/utils.go +++ b/mcp-servers/hosted/nothon/utils.go @@ -29,8 +29,7 @@ func ParseEnabledTools(enabledToolsStr string) map[string]bool { return enabledToolsSet } - tools := strings.Split(enabledToolsStr, ",") - for _, tool := range tools { + for tool := range strings.SplitSeq(enabledToolsStr, ",") { tool = strings.TrimSpace(tool) if tool != "" { enabledToolsSet[tool] = true diff --git a/mcp-servers/hosted/web-search/server.go b/mcp-servers/hosted/web-search/server.go index fdd7d6bf..a0930d49 100644 --- a/mcp-servers/hosted/web-search/server.go +++ b/mcp-servers/hosted/web-search/server.go @@ -50,10 +50,8 @@ var configTemplates = map[string]mcpservers.ConfigTemplate{ Description: "Default search engine to use (google, bing, arxiv)", Validator: func(value string) error { validEngines := []string{"google", "bing", "arxiv", "searchxng", "bingcn"} - for _, e := range validEngines { - if value == e { - return nil - } + if slices.Contains(validEngines, value) { + return nil } return fmt.Errorf( "invalid engine: %s, must be one of: %s", diff --git a/openapi-mcp/convert/convert.go b/openapi-mcp/convert/convert.go index 0311f90c..edef01d6 100644 --- a/openapi-mcp/convert/convert.go +++ b/openapi-mcp/convert/convert.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "maps" "net/http" "net/url" "strings" @@ -451,7 +452,9 @@ func (c *Converter) generateResponseDescription(responses openapi3.Responses) st } response := responseRef.Value - desc := fmt.Sprintf("- status: %s, description: %s", code, *response.Description) + + var desc strings.Builder + desc.WriteString(fmt.Sprintf("- status: %s, description: %s", code, *response.Description)) rawSchema, ok := response.Extensions["schema"].(map[string]any) if ok && len(rawSchema) > 0 { @@ -474,7 +477,7 @@ func (c *Converter) generateResponseDescription(responses openapi3.Responses) st continue } - desc += fmt.Sprintf(", schema: %s", str) + desc.WriteString(fmt.Sprintf(", schema: %s", str)) } if len(response.Content) > 0 { @@ -490,12 +493,14 @@ func (c *Converter) generateResponseDescription(responses openapi3.Responses) st continue } - desc += fmt.Sprintf(", content type: %s, schema: %s", contentType, str) + desc.WriteString( + fmt.Sprintf(", content type: %s, schema: %s", contentType, str), + ) } } } - responseDescriptions = append(responseDescriptions, desc) + responseDescriptions = append(responseDescriptions, desc.String()) } return strings.Join(responseDescriptions, "\n\n") @@ -779,10 +784,7 @@ func (c *Converter) processSchemaProperty( visited[refKey] = true // Create a copy of the visited map to avoid cross-contamination between different branches - visitedCopy := make(map[string]bool) - for k, v := range visited { - visitedCopy[k] = v - } + visitedCopy := maps.Clone(visited) visited = visitedCopy }