diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 5312667..aeecd97 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -23,9 +23,9 @@ jobs: submodules: 'recursive' fetch-depth: 0 - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - args: --disable-all -E errcheck -E staticcheck + version: v2.1 skip-cache: true problem-matchers: true - name: Test (go test) diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..52708f0 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,69 @@ +# This configuration file is not a recommendation. +# +# We intentionally use a limited set of linters. +# This configuration file is used with different version of golangci-lint to avoid regressions: +# the linters can change between version, +# their configuration may be not compatible or their reports can be different, +# and this can break some of our tests. +# Also, some linters are not relevant for the project (e.g. linters related to SQL). +# +# We have specific constraints, so we use a specific configuration. +# +# See the file `.golangci.reference.yml` to have a list of all available configuration options. + +version: "2" + +linters: + default: none + # This list of linters is not a recommendation (same thing for all this configuration file). + # We intentionally use a limited set of linters. + # See the comment on top of this file. + enable: + - errcheck + - staticcheck + - errorlint + + settings: + errorlint: + asserts: false + staticcheck: + checks: + # Invalid regular expression. + # https://staticcheck.dev/docs/checks/#SA1000 + - all + - "-ST1000" + - "-S1023" + - "-S1005" + - "-QF1004" + + exclusions: + paths: + - dist/ + - docs/ + - tests/ + - bin/ + - images/ + - install/ + - utils/ + +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + goimports: + local-prefixes: + - github.com/gojue/moling + exclusions: + paths: + - dist/ + - docs/ + - tests/ + - bin/ + - images/ + - install/ + - utils/ diff --git a/cli/cmd/client.go b/cli/cmd/client.go index 2217066..a1f4e13 100644 --- a/cli/cmd/client.go +++ b/cli/cmd/client.go @@ -21,11 +21,13 @@ package cmd import ( - "github.com/gojue/moling/client" - "github.com/rs/zerolog" - "github.com/spf13/cobra" "os" "time" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" + + "github.com/gojue/moling/client" ) var clientCmd = &cobra.Command{ diff --git a/cli/cmd/config.go b/cli/cmd/config.go index 9deac9f..eeabf91 100644 --- a/cli/cmd/config.go +++ b/cli/cmd/config.go @@ -25,10 +25,11 @@ import ( "path/filepath" "time" - "github.com/gojue/moling/pkg/comm" - "github.com/gojue/moling/pkg/services" "github.com/rs/zerolog" "github.com/spf13/cobra" + + "github.com/gojue/moling/pkg/comm" + "github.com/gojue/moling/pkg/services" ) var configCmd = &cobra.Command{ @@ -58,15 +59,15 @@ func ConfigCommandFunc(command *cobra.Command, args []string) error { // 当前配置文件检测 hasConfig := false var nowConfig []byte - nowConfigJson := make(map[string]interface{}) + nowConfigJSON := make(map[string]any) configFilePath := filepath.Join(mlConfig.BasePath, mlConfig.ConfigFile) if nowConfig, err = os.ReadFile(configFilePath); err == nil { hasConfig = true } if hasConfig { - err = json.Unmarshal(nowConfig, &nowConfigJson) + err = json.Unmarshal(nowConfig, &nowConfigJSON) if err != nil { - return fmt.Errorf("Error unmarshaling JSON: %v, payload:%s\n", err, string(nowConfig)) + return fmt.Errorf("error unmarshaling JSON: %w, payload:%s", err, string(nowConfig)) } } @@ -74,16 +75,16 @@ func ConfigCommandFunc(command *cobra.Command, args []string) error { bf.WriteString("\n{\n") // 写入GlobalConfig - mlConfigJson, err := json.Marshal(mlConfig) + mlConfigJSON, err := json.Marshal(mlConfig) if err != nil { - return fmt.Errorf("Error marshaling GlobalConfig: %v\n", err) + return fmt.Errorf("error marshaling GlobalConfig: %w", err) } bf.WriteString("\t\"MoLingConfig\":\n") - bf.WriteString(fmt.Sprintf("\t%s,\n", mlConfigJson)) + bf.WriteString(fmt.Sprintf("\t%s,\n", mlConfigJSON)) first := true for srvName, nsv := range services.ServiceList() { // 获取服务对应的配置 - cfg, ok := nowConfigJson[string(srvName)].(map[string]interface{}) + cfg, ok := nowConfigJSON[string(srvName)].(map[string]any) srv, err := nsv(ctx) if err != nil { @@ -93,7 +94,7 @@ func ConfigCommandFunc(command *cobra.Command, args []string) error { if ok { err = srv.LoadConfig(cfg) if err != nil { - return fmt.Errorf("Error loading config for service %s: %v\n", srv.Name(), err) + return fmt.Errorf("error loading config for service %s: %w", srv.Name(), err) } } else { logger.Debug().Str("service", string(srv.Name())).Msg("Service not found in config, using default config") @@ -101,7 +102,7 @@ func ConfigCommandFunc(command *cobra.Command, args []string) error { // srv Init err = srv.Init() if err != nil { - return fmt.Errorf("Error initializing service %s: %v\n", srv.Name(), err) + return fmt.Errorf("error initializing service %s: %w", srv.Name(), err) } if !first { bf.WriteString(",\n") @@ -112,31 +113,31 @@ func ConfigCommandFunc(command *cobra.Command, args []string) error { } bf.WriteString("}\n") // 解析原始 JSON 字符串 - var data interface{} + var data any err = json.Unmarshal(bf.Bytes(), &data) if err != nil { - return fmt.Errorf("Error unmarshaling JSON: %v, payload:%s\n", err, bf.String()) + return fmt.Errorf("error unmarshaling JSON: %w, payload:%s", err, bf.String()) } // 格式化 JSON - formattedJson, err := json.MarshalIndent(data, "", " ") + formattedJSON, err := json.MarshalIndent(data, "", " ") if err != nil { - return fmt.Errorf("Error marshaling JSON: %v\n", err) + return fmt.Errorf("error marshaling JSON: %w", err) } // 如果不存在配置文件 if !hasConfig { logger.Info().Msgf("Configuration file %s does not exist. Creating a new one.", configFilePath) - err = os.WriteFile(configFilePath, formattedJson, 0644) + err = os.WriteFile(configFilePath, formattedJSON, 0644) if err != nil { - return fmt.Errorf("Error writing configuration file: %v\n", err) + return fmt.Errorf("error writing configuration file: %w", err) } logger.Info().Msgf("Configuration file %s created successfully.", configFilePath) } logger.Info().Str("config", configFilePath).Msg("Current loaded configuration file path") logger.Info().Msg("You can modify the configuration file to change the settings.") if !initial { - logger.Info().Msgf("Configuration details: \n%s\n", formattedJson) + logger.Info().Msgf("Configuration details: \n%s\n", formattedJSON) } return nil } diff --git a/cli/cmd/perrun.go b/cli/cmd/perrun.go index 257d167..4e63641 100644 --- a/cli/cmd/perrun.go +++ b/cli/cmd/perrun.go @@ -19,8 +19,9 @@ package cmd import ( "path/filepath" - "github.com/gojue/moling/pkg/utils" "github.com/spf13/cobra" + + "github.com/gojue/moling/pkg/utils" ) // mlsCommandPreFunc is a pre-run function for the MoLing command. diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 8e281c5..6ca8b2c 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -29,6 +29,9 @@ import ( "syscall" "time" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/gojue/moling/cli/cobrautl" "github.com/gojue/moling/pkg/comm" "github.com/gojue/moling/pkg/config" @@ -36,8 +39,6 @@ import ( "github.com/gojue/moling/pkg/services" "github.com/gojue/moling/pkg/services/abstract" "github.com/gojue/moling/pkg/utils" - "github.com/rs/zerolog" - "github.com/spf13/cobra" ) const ( @@ -161,7 +162,7 @@ func initLogger(mlDataPath string) zerolog.Logger { // 初始化 RotateWriter rw, err := utils.NewRotateWriter(logFile, MaxLogSize) // 512MB 阈值 if err != nil { - panic(fmt.Sprintf("failed to open log file %s: %v", logFile, err)) + panic(fmt.Sprintf("failed to open log file %s: %s", logFile, err.Error())) } logger = zerolog.New(rw).With().Timestamp().Logger() logger.Info().Uint32("MaxLogSize", MaxLogSize).Msgf("Log files are automatically rotated when they exceed the size threshold, and saved to %s.1 and %s.2 respectively", LogFileName, LogFileName) @@ -173,7 +174,7 @@ func mlsCommandFunc(command *cobra.Command, args []string) error { mlConfig.SetLogger(loger) var err error var nowConfig []byte - var nowConfigJson map[string]interface{} + var nowConfigJSON map[string]any // 增加实例重复运行检测 pidFilePath := filepath.Join(mlConfig.BasePath, MLPidName) @@ -187,9 +188,9 @@ func mlsCommandFunc(command *cobra.Command, args []string) error { loger.Info().Str("ServerName", MCPServerName).Str("version", GitVersion).Msg("start") configFilePath := filepath.Join(mlConfig.BasePath, mlConfig.ConfigFile) if nowConfig, err = os.ReadFile(configFilePath); err == nil { - err = json.Unmarshal(nowConfig, &nowConfigJson) + err = json.Unmarshal(nowConfig, &nowConfigJSON) if err != nil { - return fmt.Errorf("Error unmarshaling JSON: %v, config file:%s\n", err, configFilePath) + return fmt.Errorf("error unmarshaling JSON: %w, config file:%s", err, configFilePath) } } loger.Info().Str("config_file", configFilePath).Msg("load config file") @@ -211,7 +212,7 @@ func mlsCommandFunc(command *cobra.Command, args []string) error { } loger.Debug().Str("moduleName", string(srvName)).Msgf("starting %s service", srvName) } - cfg, ok := nowConfigJson[string(srvName)].(map[string]interface{}) + cfg, ok := nowConfigJSON[string(srvName)].(map[string]any) srv, err := nsv(ctxNew) if err != nil { loger.Error().Err(err).Msgf("failed to create service %s", srv.Name()) diff --git a/client/client.go b/client/client.go index b2777ff..3776fe4 100644 --- a/client/client.go +++ b/client/client.go @@ -23,8 +23,9 @@ package client import ( "encoding/json" "errors" - "github.com/rs/zerolog" "os" + + "github.com/rs/zerolog" ) var ( @@ -40,7 +41,7 @@ type MCPServerConfig struct { IsActive bool `json:"isActive"` // Is the MCP Server active Command string `json:"command,omitempty"` // Command to start the MCP Server, STDIO mode only Args []string `json:"args,omitempty"` // Arguments to pass to the command, STDIO mode only - BaseUrl string `json:"baseUrl,omitempty"` // Base URL of the MCP Server, SSE mode only + BaseURL string `json:"baseUrl,omitempty"` // Base URL of the MCP Server, SSE mode only TimeOut uint16 `json:"timeout,omitempty"` // Timeout for the MCP Server, default is 300 seconds ServerName string } @@ -52,7 +53,7 @@ func NewMCPServerConfig(description string, command string, srvName string) MCPS IsActive: true, Command: command, Args: []string{"-m", "Browser"}, - BaseUrl: "", + BaseURL: "", ServerName: srvName, TimeOut: 300, } @@ -118,19 +119,18 @@ func (c *Manager) SetupConfig() { } c.logger.Info().Str("Client Name", name).Msgf("Successfully added config to %s", path) } - return } // appendConfig appends the mlMCPConfig to the client config. func (c *Manager) appendConfig(name string, payload []byte) ([]byte, error) { var err error - var jsonMap map[string]interface{} + var jsonMap map[string]any var jsonBytes []byte err = json.Unmarshal(payload, &jsonMap) if err != nil { return nil, err } - jsonMcpServer, ok := jsonMap[MCPServersKey].(map[string]interface{}) + jsonMcpServer, ok := jsonMap[MCPServersKey].(map[string]any) if !ok { return nil, errors.New("MCPServersKey not found in JSON") } @@ -151,7 +151,7 @@ func (c *Manager) checkExist(path string) bool { c.logger.Debug().Msgf("Client config file %s does not exist", path) return false } - c.logger.Info().Msgf("check file failed, error:%v", err) + c.logger.Info().Msgf("check file failed, error:%s", err.Error()) return false } return true diff --git a/client/client_test.go b/client/client_test.go index f6fbdcd..2cc4870 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -21,9 +21,10 @@ package client import ( - "github.com/rs/zerolog" "os" "testing" + + "github.com/rs/zerolog" ) func TestClientManager_ListClient(t *testing.T) { @@ -76,13 +77,13 @@ func TestClientManager_ListClient(t *testing.T) { result, err := cm.appendConfig("TestClient", payload) if err != nil { - t.Fatalf("Expected no error, got %v", err) + t.Fatalf("Expected no error, got %s", err.Error()) } - var resultMap map[string]interface{} + var resultMap map[string]any err = json.Unmarshal(result, &resultMap) if err != nil { - t.Fatalf("Expected valid JSON, got error %v", err) + t.Fatalf("Expected valid JSON, got error %s", err.Error()) } if resultMap["existingKey"] != "existingValue" { @@ -106,9 +107,11 @@ func TestClientManager_checkExist(t *testing.T) { // Test with an existing file file, err := os.CreateTemp("", "testfile") if err != nil { - t.Fatalf("Failed to create temp file: %v", err) + t.Fatalf("Failed to create temp file: %s", err.Error()) } - defer os.Remove(file.Name()) + defer func() { + _ = os.Remove(file.Name()) + }() t.Logf("Created temp file: %s", file.Name()) exists = cm.checkExist(file.Name()) if !exists { diff --git a/pkg/comm/comm.go b/pkg/comm/comm.go index 2da6b4f..27b88fd 100644 --- a/pkg/comm/comm.go +++ b/pkg/comm/comm.go @@ -21,8 +21,9 @@ import ( "os" "path/filepath" - "github.com/gojue/moling/pkg/config" "github.com/rs/zerolog" + + "github.com/gojue/moling/pkg/config" ) type MoLingServerType string diff --git a/pkg/config/config.go b/pkg/config/config.go index 75d5aa0..f1b156c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -43,7 +43,7 @@ type MoLingConfig struct { Description string // Description of the MCP Server, default: CliDescription Command string // Command to start the MCP Server, STDIO mode only, default: CliName Args string // Arguments to pass to the command, STDIO mode only, default: empty - BaseUrl string // BaseUrl , SSE mode only. + BaseURL string // BaseURL , SSE mode only. ServerName string // ServerName MCP ServerName, add to the MCP Client config logger zerolog.Logger } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ce243bf..1568efc 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -39,18 +39,18 @@ func TestConfigLoad(t *testing.T) { jsonData, err := os.ReadFile(configFile) if err != nil { - t.Fatalf("failed to read config file: %v", err) + t.Fatalf("failed to read config file: %s", err.Error()) } - var jsonMap map[string]interface{} + var jsonMap map[string]any if err := json.Unmarshal(jsonData, &jsonMap); err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) + t.Fatalf("Failed to unmarshal JSON: %s", err.Error()) } - mlConfig, ok := jsonMap["MoLingConfig"].(map[string]interface{}) + mlConfig, ok := jsonMap["MoLingConfig"].(map[string]any) if !ok { t.Fatalf("failed to parse MoLingConfig from JSON") } if err := utils.MergeJSONToStruct(cfg, mlConfig); err != nil { - t.Fatalf("failed to merge JSON to struct: %v", err) + t.Fatalf("failed to merge JSON to struct: %s", err.Error()) } t.Logf("Config loaded, MoLing Config.BasePath: %s", cfg.BasePath) if cfg.BasePath != "/newpath/.moling" { diff --git a/pkg/server/server.go b/pkg/server/server.go index 683b299..2eb4870 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -23,11 +23,12 @@ import ( "strings" "time" + "github.com/mark3labs/mcp-go/server" + "github.com/rs/zerolog" + "github.com/gojue/moling/pkg/comm" "github.com/gojue/moling/pkg/config" "github.com/gojue/moling/pkg/services/abstract" - "github.com/mark3labs/mcp-go/server" - "github.com/rs/zerolog" ) type MoLingServer struct { @@ -100,17 +101,17 @@ func (m *MoLingServer) loadService(srv abstract.Service) error { return nil } -func (s *MoLingServer) Serve() error { - mLogger := log.New(s.logger, s.mlConfig.ServerName, 0) - if s.listenAddr != "" { - ltnAddr := fmt.Sprintf("http://%s", strings.TrimPrefix(s.listenAddr, "http://")) +func (m *MoLingServer) Serve() error { + mLogger := log.New(m.logger, m.mlConfig.ServerName, 0) + if m.listenAddr != "" { + ltnAddr := fmt.Sprintf("http://%s", strings.TrimPrefix(m.listenAddr, "http://")) consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} - multi := zerolog.MultiLevelWriter(consoleWriter, s.logger) - s.logger = zerolog.New(multi).With().Timestamp().Logger() - s.logger.Info().Str("listenAddr", s.listenAddr).Str("BaseURL", ltnAddr).Msg("Starting SSE server") - s.logger.Warn().Msgf("The SSE server URL must be: %s. Please do not make mistakes, even if it is another IP or domain name on the same computer, it cannot be mixed.", ltnAddr) - return server.NewSSEServer(s.server, server.WithBaseURL(ltnAddr)).Start(s.listenAddr) + multi := zerolog.MultiLevelWriter(consoleWriter, m.logger) + m.logger = zerolog.New(multi).With().Timestamp().Logger() + m.logger.Info().Str("listenAddr", m.listenAddr).Str("BaseURL", ltnAddr).Msg("Starting SSE server") + m.logger.Warn().Msgf("The SSE server URL must be: %s. Please do not make mistakes, even if it is another IP or domain name on the same computer, it cannot be mixed.", ltnAddr) + return server.NewSSEServer(m.server, server.WithBaseURL(ltnAddr)).Start(m.listenAddr) } - s.logger.Info().Msg("Starting STDIO server") - return server.ServeStdio(s.server, server.WithErrorLogger(mLogger)) + m.logger.Info().Msg("Starting STDIO server") + return server.ServeStdio(m.server, server.WithErrorLogger(mLogger)) } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 0812d2b..33a6b0e 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -42,17 +42,17 @@ func TestNewMLServer(t *testing.T) { } err := utils.CreateDirectory(mlConfig.BasePath) if err != nil { - t.Errorf("Failed to create base directory: %v", err) + t.Errorf("Failed to create base directory: %s", err.Error()) } for _, dirName := range mlDirectories { err = utils.CreateDirectory(filepath.Join(mlConfig.BasePath, dirName)) if err != nil { - t.Errorf("Failed to create directory %s: %v", dirName, err) + t.Errorf("Failed to create directory %s: %s", dirName, err.Error()) } } logger, ctx, err := comm.InitTestEnv() if err != nil { - t.Fatalf("Failed to initialize test environment: %v", err) + t.Fatalf("Failed to initialize test environment: %s", err.Error()) } logger.Info().Msg("TestBrowserServer") mlConfig.SetLogger(logger) @@ -60,22 +60,22 @@ func TestNewMLServer(t *testing.T) { // Create a new server with the filesystem service fs, err := filesystem.NewFilesystemServer(ctx) if err != nil { - t.Errorf("Failed to create filesystem server: %v", err) + t.Errorf("Failed to create filesystem server: %s", err.Error()) } err = fs.Init() if err != nil { - t.Errorf("Failed to initialize filesystem server: %v", err) + t.Errorf("Failed to initialize filesystem server: %s", err.Error()) } srvs := []abstract.Service{ fs, } srv, err := NewMoLingServer(ctx, srvs, mlConfig) if err != nil { - t.Errorf("Failed to create server: %v", err) + t.Errorf("Failed to create server: %s", err.Error()) } err = srv.Serve() if err != nil { - t.Errorf("Failed to start server: %v", err) + t.Errorf("Failed to start server: %s", err.Error()) } t.Logf("Server started successfully: %v", srv) } diff --git a/pkg/services/abstract/abstract.go b/pkg/services/abstract/abstract.go index 7c0e5a6..8f06df5 100644 --- a/pkg/services/abstract/abstract.go +++ b/pkg/services/abstract/abstract.go @@ -19,10 +19,11 @@ package abstract import ( "context" - "github.com/gojue/moling/pkg/comm" - "github.com/gojue/moling/pkg/config" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" + + "github.com/gojue/moling/pkg/comm" + "github.com/gojue/moling/pkg/config" ) type ServiceFactory func(ctx context.Context) (Service, error) @@ -44,7 +45,7 @@ type Service interface { // Config returns the configuration of the service as a string. Config() string // LoadConfig loads the configuration for the service from a map. - LoadConfig(jsonData map[string]interface{}) error + LoadConfig(jsonData map[string]any) error // Init initializes the service with the given context and configuration. Init() error diff --git a/pkg/services/abstract/mlservice.go b/pkg/services/abstract/mlservice.go index a5269a1..ac9a05b 100644 --- a/pkg/services/abstract/mlservice.go +++ b/pkg/services/abstract/mlservice.go @@ -20,11 +20,12 @@ import ( "context" "sync" - "github.com/gojue/moling/pkg/config" - "github.com/gojue/moling/pkg/utils" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "github.com/rs/zerolog" + + "github.com/gojue/moling/pkg/config" + "github.com/gojue/moling/pkg/utils" ) type PromptEntry struct { @@ -165,7 +166,7 @@ func (mls *MLService) Name() string { } // LoadConfig loads the configuration for the service from a map. -func (mls *MLService) LoadConfig(jsonData map[string]interface{}) error { +func (mls *MLService) LoadConfig(jsonData map[string]any) error { //panic("not implemented yet") // TODO: Implement err := utils.MergeJSONToStruct(mls.mlConfig, jsonData) if err != nil { diff --git a/pkg/services/abstract/mlservice_test.go b/pkg/services/abstract/mlservice_test.go index 4d3f380..c415b17 100644 --- a/pkg/services/abstract/mlservice_test.go +++ b/pkg/services/abstract/mlservice_test.go @@ -27,7 +27,7 @@ func TestMLService_AddResource(t *testing.T) { service := &MLService{} err := service.InitResources() if err != nil { - t.Fatalf("Failed to initialize MLService: %v", err) + t.Fatalf("Failed to initialize MLService: %s", err.Error()) } resource := mcp.Resource{Name: "testResource"} handler := func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { @@ -54,7 +54,7 @@ func TestMLService_AddResourceTemplate(t *testing.T) { service := &MLService{} err := service.InitResources() if err != nil { - t.Fatalf("Failed to initialize MLService: %v", err) + t.Fatalf("Failed to initialize MLService: %s", err.Error()) } template := mcp.ResourceTemplate{Name: "testTemplate"} handler := func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { @@ -81,7 +81,7 @@ func TestMLService_AddPrompt(t *testing.T) { service := &MLService{} err := service.InitResources() if err != nil { - t.Fatalf("Failed to initialize MLService: %v", err) + t.Fatalf("Failed to initialize MLService: %s", err.Error()) } prompt := "testPrompt" handler := func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { @@ -118,7 +118,7 @@ func TestMLService_AddTool(t *testing.T) { service := &MLService{} err := service.InitResources() if err != nil { - t.Fatalf("Failed to initialize MLService: %v", err) + t.Fatalf("Failed to initialize MLService: %s", err.Error()) } tool := mcp.Tool{Name: "testTool"} handler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -148,7 +148,7 @@ func TestMLService_AddNotificationHandler(t *testing.T) { service := &MLService{} err := service.InitResources() if err != nil { - t.Fatalf("Failed to initialize MLService: %v", err) + t.Fatalf("Failed to initialize MLService: %s", err.Error()) } name := "testHandler" handler := func(ctx context.Context, n mcp.JSONRPCNotification) { diff --git a/pkg/services/browser/browser.go b/pkg/services/browser/browser.go index 49be41a..903c348 100644 --- a/pkg/services/browser/browser.go +++ b/pkg/services/browser/browser.go @@ -28,12 +28,13 @@ import ( "time" "github.com/chromedp/chromedp" + "github.com/mark3labs/mcp-go/mcp" + "github.com/rs/zerolog" + "github.com/gojue/moling/pkg/comm" "github.com/gojue/moling/pkg/config" "github.com/gojue/moling/pkg/services/abstract" "github.com/gojue/moling/pkg/utils" - "github.com/mark3labs/mcp-go/mcp" - "github.com/rs/zerolog" ) const ( @@ -81,11 +82,11 @@ func (bs *BrowserServer) Init() error { // Initialize the browser server err := bs.initBrowser(bs.config.BrowserDataPath) if err != nil { - return fmt.Errorf("failed to initialize browser: %v", err) + return fmt.Errorf("failed to initialize browser: %w", err) } err = utils.CreateDirectory(bs.config.DataPath) if err != nil { - return fmt.Errorf("failed to create data directory: %v", err) + return fmt.Errorf("failed to create data directory: %w", err) } // Create a new context for the browser @@ -129,7 +130,7 @@ func (bs *BrowserServer) Init() error { pe := abstract.PromptEntry{ PromptVar: mcp.Prompt{ Name: "browser_prompt", - Description: fmt.Sprintf("Get the relevant functions and prompts of the Browser MCP Server."), + Description: "Get the relevant functions and prompts of the Browser MCP Server", //Arguments: make([]mcp.PromptArgument, 0), }, HandlerFunc: bs.handlePrompt, @@ -267,7 +268,7 @@ func (bs *BrowserServer) Init() error { func (bs *BrowserServer) initBrowser(userDataDir string) error { _, err := os.Stat(userDataDir) if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to stat user data directory: %v", err) + return fmt.Errorf("failed to stat user data directory: %w", err) } // Check if the directory exists, if it does, we can reuse it @@ -279,7 +280,7 @@ func (bs *BrowserServer) initBrowser(userDataDir string) error { bs.Logger.Debug().Msg("Browser is already running, removing SingletonLock") err = os.RemoveAll(singletonLock) if err != nil { - bs.Logger.Error().Str("Lock", singletonLock).Msgf("Browser can't work due to failed removal of SingletonLock: %v", err) + bs.Logger.Error().Str("Lock", singletonLock).Msgf("Browser can't work due to failed removal of SingletonLock: %s", err.Error()) } } return nil @@ -287,7 +288,7 @@ func (bs *BrowserServer) initBrowser(userDataDir string) error { // Create the directory err = os.MkdirAll(userDataDir, 0755) if err != nil { - return fmt.Errorf("failed to create user data directory: %v", err) + return fmt.Errorf("failed to create user data directory: %w", err) } return nil } @@ -295,7 +296,7 @@ func (bs *BrowserServer) initBrowser(userDataDir string) error { func (bs *BrowserServer) handlePrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { // 处理浏览器提示 return &mcp.GetPromptResult{ - Description: fmt.Sprintf(""), + Description: "", Messages: []mcp.PromptMessage{ { Role: mcp.RoleUser, @@ -318,7 +319,7 @@ func (bs *BrowserServer) handleNavigate(ctx context.Context, request mcp.CallToo err := chromedp.Run(bs.Context, chromedp.Navigate(url)) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to navigate: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to navigate: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Navigated to %s", url)), nil } @@ -349,13 +350,13 @@ func (bs *BrowserServer) handleScreenshot(ctx context.Context, request mcp.CallT err = chromedp.Run(bs.Context, chromedp.Screenshot(selector, &buf, chromedp.NodeVisible)) } if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to take screenshot: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to take screenshot: %s", err.Error())), nil } newName := filepath.Join(bs.config.DataPath, fmt.Sprintf("%s_%d.png", strings.TrimRight(name, ".png"), rand.Int())) err = os.WriteFile(newName, buf, 0644) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to save screenshot: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to save screenshot: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Screenshot saved to:%s", newName)), nil } @@ -375,7 +376,7 @@ func (bs *BrowserServer) handleClick(ctx context.Context, request mcp.CallToolRe chromedp.Click(selector, chromedp.NodeVisible), ) if err != nil { - return mcp.NewToolResultError(fmt.Errorf("failed to click element: %v", err).Error()), nil + return mcp.NewToolResultError(fmt.Errorf("failed to click element: %s", err.Error()).Error()), nil } return mcp.NewToolResultText(fmt.Sprintf("Clicked element %s", selector)), nil } @@ -397,7 +398,7 @@ func (bs *BrowserServer) handleFill(ctx context.Context, request mcp.CallToolReq defer cancelFunc() err := chromedp.Run(runCtx, chromedp.SendKeys(selector, value, chromedp.NodeVisible)) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to fill input field: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to fill input field: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Filled input %s with value %s", selector, value)), nil } @@ -416,7 +417,7 @@ func (bs *BrowserServer) handleSelect(ctx context.Context, request mcp.CallToolR defer cancelFunc() err := chromedp.Run(runCtx, chromedp.SetValue(selector, value, chromedp.NodeVisible)) if err != nil { - return mcp.NewToolResultError(fmt.Errorf("failed to select value: %v", err).Error()), nil + return mcp.NewToolResultError(fmt.Errorf("failed to select value: %s", err.Error()).Error()), nil } return mcp.NewToolResultText(fmt.Sprintf("Selected value %s for element %s", value, selector)), nil } @@ -433,7 +434,7 @@ func (bs *BrowserServer) handleHover(ctx context.Context, request mcp.CallToolRe defer cancelFunc() err := chromedp.Run(runCtx, chromedp.Evaluate(`document.querySelector('`+selector+`').dispatchEvent(new Event('mouseover'))`, &res)) if err != nil { - return mcp.NewToolResultError(fmt.Errorf("failed to hover over element: %v", err).Error()), nil + return mcp.NewToolResultError(fmt.Errorf("failed to hover over element: %s", err.Error()).Error()), nil } return mcp.NewToolResultText(fmt.Sprintf("Hovered over element %s, result:%t", selector, res)), nil } @@ -444,12 +445,12 @@ func (bs *BrowserServer) handleEvaluate(ctx context.Context, request mcp.CallToo if !ok { return mcp.NewToolResultError("script must be a string"), nil } - var result interface{} + var result any runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second) defer cancelFunc() err := chromedp.Run(runCtx, chromedp.Evaluate(script, &result)) if err != nil { - return mcp.NewToolResultError(fmt.Errorf("failed to execute script: %v", err).Error()), nil + return mcp.NewToolResultError(fmt.Errorf("failed to execute script: %s", err.Error()).Error()), nil } return mcp.NewToolResultText(fmt.Sprintf("Script executed successfully: %v", result)), nil } @@ -479,7 +480,7 @@ func (bs *BrowserServer) Name() comm.MoLingServerType { } // LoadConfig loads the configuration from a JSON object. -func (bs *BrowserServer) LoadConfig(jsonData map[string]interface{}) error { +func (bs *BrowserServer) LoadConfig(jsonData map[string]any) error { err := utils.MergeJSONToStruct(bs.config, jsonData) if err != nil { return err diff --git a/pkg/services/browser/browser_config.go b/pkg/services/browser/browser_config.go index 1d93067..a73cc42 100644 --- a/pkg/services/browser/browser_config.go +++ b/pkg/services/browser/browser_config.go @@ -86,7 +86,7 @@ func (cfg *BrowserConfig) Check() error { if cfg.PromptFile != "" { read, err := os.ReadFile(cfg.PromptFile) if err != nil { - return fmt.Errorf("failed to read prompt file:%s, error: %v", cfg.PromptFile, err) + return fmt.Errorf("failed to read prompt file:%s, error: %w", cfg.PromptFile, err) } cfg.prompt = string(read) } diff --git a/pkg/services/browser/browser_debugger.go b/pkg/services/browser/browser_debugger.go index 48e9842..c28aadf 100644 --- a/pkg/services/browser/browser_debugger.go +++ b/pkg/services/browser/browser_debugger.go @@ -84,14 +84,14 @@ func (bs *BrowserServer) handleSetBreakpoint(ctx context.Context, request mcp.Ca defer cancel() err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error { t := chromedp.FromContext(ctx).Target - params := map[string]interface{}{ + params := map[string]any{ "url": url, "line": int(line), "column": int(column), "condition": condition, } - var result map[string]interface{} + var result map[string]any // 使用Execute方法执行Debugger.setBreakpoint命令 if err := t.Execute(ctx, "Debugger.setBreakpoint", params, &result); err != nil { return err @@ -106,7 +106,7 @@ func (bs *BrowserServer) handleSetBreakpoint(ctx context.Context, request mcp.Ca })) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to set breakpoint: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to set breakpoint: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Breakpoint set with ID: %s", breakpointID)), nil } @@ -123,13 +123,13 @@ func (bs *BrowserServer) handleRemoveBreakpoint(ctx context.Context, request mcp err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error { t := chromedp.FromContext(ctx).Target // 使用Execute方法执行Debugger.removeBreakpoint命令 - return t.Execute(ctx, "Debugger.removeBreakpoint", map[string]interface{}{ + return t.Execute(ctx, "Debugger.removeBreakpoint", map[string]any{ "breakpointId": breakpointID, }, nil) })) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to remove breakpoint: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to remove breakpoint: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Breakpoint %s removed", breakpointID)), nil } @@ -145,7 +145,7 @@ func (bs *BrowserServer) handlePause(ctx context.Context, request mcp.CallToolRe })) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to pause execution: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to pause execution: %s", err.Error())), nil } return mcp.NewToolResultText("JavaScript execution paused"), nil } @@ -161,14 +161,14 @@ func (bs *BrowserServer) handleResume(ctx context.Context, request mcp.CallToolR })) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to resume execution: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to resume execution: %s", err.Error())), nil } return mcp.NewToolResultText("JavaScript execution resumed"), nil } // handleStepOver handles stepping over the next line of JavaScript code in the browser. func (bs *BrowserServer) handleGetCallstack(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - var callstack interface{} + var callstack any rctx, cancel := context.WithCancel(bs.Context) defer cancel() err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error { @@ -178,12 +178,12 @@ func (bs *BrowserServer) handleGetCallstack(ctx context.Context, request mcp.Cal })) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to get call stack: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to get call stack: %s", err.Error())), nil } callstackJSON, err := json.Marshal(callstack) if err != nil { - return mcp.NewToolResultError(fmt.Sprintf("failed to marshal call stack: %v", err)), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to marshal call stack: %s", err.Error())), nil } return mcp.NewToolResultText(fmt.Sprintf("Current call stack: %s", string(callstackJSON))), nil diff --git a/pkg/services/browser/browser_test.go b/pkg/services/browser/browser_test.go index 8b978b0..4f5f7db 100644 --- a/pkg/services/browser/browser_test.go +++ b/pkg/services/browser/browser_test.go @@ -34,121 +34,12 @@ func TestBrowserServer(t *testing.T) { //} logger, ctx, err := comm.InitTestEnv() if err != nil { - t.Fatalf("Failed to initialize test environment: %v", err) + t.Fatalf("Failed to initialize test environment: %s", err.Error()) } logger.Info().Msg("TestBrowserServer") _, err = NewBrowserServer(ctx) if err != nil { - t.Fatalf("Failed to create BrowserServer: %v", err) + t.Fatalf("Failed to create BrowserServer: %s", err.Error()) } - - /* - t.Run("TestNavigate", func(t *testing.T) { - request := mcp.CallToolRequest{ - Request: mcp.Request{ - Method: "tools/call", - }, - } - args = map[string]interface{}{ - "url": "https://www.baidu.com", - } - result, err := bs.handleNavigate(ctx, request) - if err != nil { - t.Fatalf("handleNavigate failed: %v", err) - } - if result.Content[0].(mcp.TextContent).Text != "Navigated to https://www.example.com" { - t.Errorf("Unexpected result: %v", result.Content[0].(mcp.TextContent).Text) - } - }) - */ - // - //t.Run("TestScreenshot", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "name": "test_screenshot", - // }, - // }, - // } - // _, err := bs.handleScreenshot(ctx, request) - // if err != nil { - // t.Fatalf("handleScreenshot failed: %v", err) - // } - //}) - // - //t.Run("TestClick", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "selector": "body", - // }, - // }, - // } - // _, err := bs.handleClick(ctx, request) - // if err != nil { - // t.Fatalf("handleClick failed: %v", err) - // } - //}) - // - //t.Run("TestFill", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "selector": "input[name='q']", - // "value": "test", - // }, - // }, - // } - // _, err := bs.handleFill(ctx, request) - // if err != nil { - // t.Fatalf("handleFill failed: %v", err) - // } - //}) - // - //t.Run("TestSelect", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "selector": "select[name='dropdown']", - // "value": "option1", - // }, - // }, - // } - // _, err := bs.handleSelect(ctx, request) - // if err != nil { - // t.Fatalf("handleSelect failed: %v", err) - // } - //}) - // - //t.Run("TestHover", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "selector": "body", - // }, - // }, - // } - // _, err := bs.handleHover(ctx, request) - // if err != nil { - // t.Fatalf("handleHover failed: %v", err) - // } - //}) - // - //t.Run("TestEvaluate", func(t *testing.T) { - // request := mcp.CallToolRequest{ - // Params: mcp.ToolParams{ - // Arguments: map[string]interface{}{ - // "script": "document.title", - // }, - // }, - // } - // result, err := bs.handleEvaluate(ctx, request) - // if err != nil { - // t.Fatalf("handleEvaluate failed: %v", err) - // } - // if result.Content[0].(mcp.TextContent).Text == "" { - // t.Errorf("Unexpected result: %v", result.Content[0].(mcp.TextContent).Text) - // } - //}) } diff --git a/pkg/services/command/command.go b/pkg/services/command/command.go index b1796c0..ae8df39 100644 --- a/pkg/services/command/command.go +++ b/pkg/services/command/command.go @@ -24,12 +24,13 @@ import ( "path/filepath" "strings" + "github.com/mark3labs/mcp-go/mcp" + "github.com/rs/zerolog" + "github.com/gojue/moling/pkg/comm" "github.com/gojue/moling/pkg/config" "github.com/gojue/moling/pkg/services/abstract" "github.com/gojue/moling/pkg/utils" - "github.com/mark3labs/mcp-go/mcp" - "github.com/rs/zerolog" ) var ( @@ -87,7 +88,7 @@ func (cs *CommandServer) Init() error { pe := abstract.PromptEntry{ PromptVar: mcp.Prompt{ Name: "command_prompt", - Description: fmt.Sprintf("get command prompt"), + Description: "get command prompt", //Arguments: make([]mcp.PromptArgument, 0), }, HandlerFunc: cs.handlePrompt, @@ -106,7 +107,7 @@ func (cs *CommandServer) Init() error { func (cs *CommandServer) handlePrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{ - Description: fmt.Sprintf(""), + Description: "", Messages: []mcp.PromptMessage{ { Role: mcp.RoleUser, @@ -200,7 +201,7 @@ func (cs *CommandServer) Close() error { } // LoadConfig loads the configuration from a JSON object. -func (cs *CommandServer) LoadConfig(jsonData map[string]interface{}) error { +func (cs *CommandServer) LoadConfig(jsonData map[string]any) error { err := utils.MergeJSONToStruct(cs.config, jsonData) if err != nil { return err diff --git a/pkg/services/command/command_config.go b/pkg/services/command/command_config.go index 7cb7b35..9869bc7 100644 --- a/pkg/services/command/command_config.go +++ b/pkg/services/command/command_config.go @@ -111,7 +111,7 @@ func (cc *CommandConfig) Check() error { if cc.PromptFile != "" { read, err := os.ReadFile(cc.PromptFile) if err != nil { - return fmt.Errorf("failed to read prompt file:%s, error: %v", cc.PromptFile, err) + return fmt.Errorf("failed to read prompt file:%s, error: %w", cc.PromptFile, err) } cc.prompt = string(read) } diff --git a/pkg/services/command/command_exec_test.go b/pkg/services/command/command_exec_test.go index dab9ea9..1cf3240 100644 --- a/pkg/services/command/command_exec_test.go +++ b/pkg/services/command/command_exec_test.go @@ -92,8 +92,8 @@ func TestAllowCmd(t *testing.T) { } // 将 struct 转换为 map -func StructToMap(obj interface{}) map[string]interface{} { - result := make(map[string]interface{}) +func StructToMap(obj any) map[string]any { + result := make(map[string]any) val := reflect.ValueOf(obj) if val.Kind() == reflect.Ptr { val = val.Elem() diff --git a/pkg/services/filesystem/file_system.go b/pkg/services/filesystem/file_system.go index bac1a6d..0d6e0e6 100644 --- a/pkg/services/filesystem/file_system.go +++ b/pkg/services/filesystem/file_system.go @@ -28,12 +28,13 @@ import ( "strings" "time" + "github.com/mark3labs/mcp-go/mcp" + "github.com/rs/zerolog" + "github.com/gojue/moling/pkg/comm" "github.com/gojue/moling/pkg/config" "github.com/gojue/moling/pkg/services/abstract" "github.com/gojue/moling/pkg/utils" - "github.com/mark3labs/mcp-go/mcp" - "github.com/rs/zerolog" ) const ( @@ -85,7 +86,7 @@ func NewFilesystemServer(ctx context.Context) (abstract.Service, error) { err = fs.InitResources() if err != nil { - return nil, fmt.Errorf("failed to initialize filesystem server: %v", err) + return nil, fmt.Errorf("failed to initialize filesystem server: %w", err) } return fs, nil @@ -100,7 +101,7 @@ func (fs *FilesystemServer) Init() error { pe := abstract.PromptEntry{ PromptVar: mcp.Prompt{ Name: "filesystem_prompt", - Description: fmt.Sprintf("Get the relevant functions and prompts of the FileSystem MCP Server."), + Description: "Get the relevant functions and prompts of the FileSystem MCP Server.", }, HandlerFunc: fs.handlePrompt, } @@ -191,7 +192,7 @@ func (fs *FilesystemServer) Init() error { // handlePrompt handles the prompt request for the FilesystemServer func (fs *FilesystemServer) handlePrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{ - Description: fmt.Sprintf(""), + Description: "", Messages: []mcp.PromptMessage{ { Role: mcp.RoleUser, @@ -1032,7 +1033,7 @@ func (fs *FilesystemServer) Close() error { } // LoadConfig loads the configuration from a JSON object. -func (fs *FilesystemServer) LoadConfig(jsonData map[string]interface{}) error { +func (fs *FilesystemServer) LoadConfig(jsonData map[string]any) error { err := utils.MergeJSONToStruct(fs.config, jsonData) if err != nil { return err diff --git a/pkg/services/filesystem/file_system_config.go b/pkg/services/filesystem/file_system_config.go index 998eab7..bacdcf1 100644 --- a/pkg/services/filesystem/file_system_config.go +++ b/pkg/services/filesystem/file_system_config.go @@ -112,7 +112,7 @@ func (fc *FileSystemConfig) Check() error { if fc.PromptFile != "" { read, err := os.ReadFile(fc.PromptFile) if err != nil { - return fmt.Errorf("failed to read prompt file:%s, error: %v", fc.PromptFile, err) + return fmt.Errorf("failed to read prompt file:%s, error: %w", fc.PromptFile, err) } fc.prompt = string(read) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 0087a62..13cdd88 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -53,7 +53,7 @@ func StringInSlice(s string, modules []string) bool { } // MergeJSONToStruct 将JSON中的字段合并到结构体中 -func MergeJSONToStruct(target interface{}, jsonMap map[string]interface{}) error { +func MergeJSONToStruct(target any, jsonMap map[string]any) error { // 获取目标结构体的反射值 val := reflect.ValueOf(target).Elem() typ := val.Type()