#
tokens: 49939/50000 49/50 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/gojue/moling?page={x} to view the full context.

# Directory Structure

```
├── .github
│   └── workflows
│       ├── go-test.yml
│       └── release.yml
├── .gitignore
├── .golangci.yml
├── bin
│   └── .gitkeep
├── CHANGELOG.md
├── cli
│   ├── cmd
│   │   ├── client.go
│   │   ├── config.go
│   │   ├── perrun.go
│   │   ├── root.go
│   │   └── utils.go
│   ├── cobrautl
│   │   └── help.go
│   └── main.go
├── client
│   ├── client_config_windows.go
│   ├── client_config.go
│   ├── client_test.go
│   └── client.go
├── dist
│   └── .gitkeep
├── functions.mk
├── go.mod
├── go.sum
├── images
│   ├── logo-colorful.png
│   ├── logo.svg
│   └── screenshot_claude.png
├── install
│   ├── install.ps1
│   └── install.sh
├── LICENSE
├── main.go
├── Makefile
├── Makefile.release
├── pkg
│   ├── comm
│   │   ├── comm.go
│   │   └── errors.go
│   ├── config
│   │   ├── config_test.go
│   │   ├── config_test.json
│   │   └── config.go
│   ├── server
│   │   ├── server_test.go
│   │   └── server.go
│   ├── services
│   │   ├── abstract
│   │   │   ├── abstract.go
│   │   │   ├── mlservice_test.go
│   │   │   └── mlservice.go
│   │   ├── browser
│   │   │   ├── browser_config.go
│   │   │   ├── browser_debugger.go
│   │   │   ├── browser_test.go
│   │   │   └── browser.go
│   │   ├── command
│   │   │   ├── command_config.go
│   │   │   ├── command_exec_test.go
│   │   │   ├── command_exec_windows.go
│   │   │   ├── command_exec.go
│   │   │   └── command.go
│   │   ├── filesystem
│   │   │   ├── file_system_config.go
│   │   │   ├── file_system_windows.go
│   │   │   └── file_system.go
│   │   └── register.go
│   └── utils
│       ├── pid_unix.go
│       ├── pid_windows.go
│       ├── pid.go
│       ├── rotewriter.go
│       └── utils.go
├── prompts
│   ├──  filesystem.md
│   ├── browser.md
│   └── command.md
├── README_JA_JP.md
├── README_ZH_HANS.md
├── README.md
└── variables.mk
```

# Files

--------------------------------------------------------------------------------
/bin/.gitkeep:
--------------------------------------------------------------------------------

```

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
/services/mo*
/bin/moling
```

--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------

```yaml
# 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/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
## MoLing MCP Server

English | [中文](./README_ZH_HANS.md) | [日本語](./README_JA_JP.md)

[![GitHub stars](https://img.shields.io/github/stars/gojue/moling.svg?label=Stars&logo=github)](https://github.com/gojue/moling/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/gojue/moling?label=Forks&logo=github)](https://github.com/gojue/moling/forks)
[![CI](https://github.com/gojue/moling/actions/workflows/go-test.yml/badge.svg)](https://github.com/gojue/moling/actions/workflows/go-test.yml)
[![Github Version](https://img.shields.io/github/v/release/gojue/moling?display_name=tag&include_prereleases&sort=semver)](https://github.com/gojue/moling/releases)

---

![](./images/logo.svg)

### Introduction
MoLing is a computer-use and browser-use MCP Server that implements system interaction through operating system APIs, enabling file system operations such as reading, writing, merging, statistics, and aggregation, as well as the ability to execute system commands. It is a dependency-free local office automation assistant.

### Advantages
> [!IMPORTANT]
> Requiring no installation of any dependencies, MoLing can be run directly and is compatible with multiple operating systems, including Windows, Linux, and macOS. 
> This eliminates the hassle of dealing with environment conflicts involving Node.js, Python, Docker and other development environments.

### Features

> [!CAUTION]
> Command-line operations are dangerous and should be used with caution.

- **File System Operations**: Reading, writing, merging, statistics, and aggregation
- **Command-line Terminal**: Execute system commands directly
- **Browser Control**: Powered by `github.com/chromedp/chromedp`
    - Chrome browser is required.
    - In Windows, the full path to Chrome needs to be configured in the system environment variables.
- **Future Plans**:
    - Personal PC data organization
    - Document writing assistance
    - Schedule planning
    - Life assistant features

> [!WARNING]
> Currently, MoLing has only been tested on macOS, and other operating systems may have issues.

### Supported MCP Clients

- [Claude](https://claude.ai/)
- [Cline](https://cline.bot/)
- [Cherry Studio](https://cherry-ai.com/)
- etc. (who support MCP protocol)

#### Demos

https://github.com/user-attachments/assets/229c4dd5-23b4-4b53-9e25-3eba8734b5b7

MoLing in [Claude](https://claude.ai/)
![](./images/screenshot_claude.png)

#### Configuration Format

##### MCP Server (MoLing) configuration

The configuration file will be generated at `/Users/username/.moling/config/config.json`, and you can modify its
contents as needed.

If the file does not exist, you can create it using `moling config --init`.

##### MCP Client configuration
For example, to configure the Claude client, add the following configuration:

> [!TIP]
> 
> Only 3-6 lines of configuration are needed.
> 
> Claude config path: `~/Library/Application\ Support/Claude/claude_desktop_config`

```json
{
  "mcpServers": {
    "MoLing": {
      "command": "/usr/local/bin/moling",
      "args": []
    }
  }
}
```

and, `/usr/local/bin/moling` is the path to the MoLing server binary you downloaded.

**Automatic Configuration**

run `moling client --install` to automatically install the configuration for the MCP client.

MoLing will automatically detect the MCP client and install the configuration for you. including: Cline, Claude, Roo
Code, etc.

### Operation Modes

- **Stdio Mode**: CLI-based interactive mode for user-friendly experience
- **SSE Mode**: Server-Side Rendering mode optimized for headless/automated environments

### Installation

#### Option 1: Install via Script
##### Linux/MacOS
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/gojue/moling/HEAD/install/install.sh)"
```
##### Windows

> [!WARNING]
> Not tested, unsure if it works.

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/gojue/moling/HEAD/install/install.ps1 | iex"
```

#### Option 2: Direct Download
1. Download the installation package from [releases page](https://github.com/gojue/moling/releases)
2. Extract the package
3. Run the server:
   ```sh
   ./moling
   ```

#### Option 3: Build from Source
1. Clone the repository:
```sh
git clone https://github.com/gojue/moling.git
cd moling
```
2. Build the project (requires Golang toolchain):
```sh
make build
```
3. Run the compiled binary:
```sh
./bin/moling
```

### Usage
After starting the server, connect using any supported MCP client by configuring it to point to your MoLing server address.

### License
Apache License 2.0. See [LICENSE](LICENSE) for details.

```

--------------------------------------------------------------------------------
/cli/cmd/utils.go:
--------------------------------------------------------------------------------

```go
package cmd

```

--------------------------------------------------------------------------------
/functions.mk:
--------------------------------------------------------------------------------

```
define allow-override
  $(if $(or $(findstring environment,$(origin $(1))),\
            $(findstring command line,$(origin $(1)))),,\
    $(eval $(1) = $(2)))
endef

# TARGET_OS , TARGET_ARCH
define gobuild
	CGO_ENABLED=0 \
	GOOS=$(1) GOARCH=$(2) \
	$(eval OUT_BIN_SUFFIX=$(if $(filter $(1),windows),.exe,)) \
	$(CMD_GO) build -trimpath -mod=readonly -ldflags "-w -s -X 'github.com/gojue/moling/cli/cmd.GitVersion=$(1)_$(2)_$(VERSION_NUM)'" -o $(OUT_BIN)$(OUT_BIN_SUFFIX)
	$(CMD_FILE) $(OUT_BIN)$(OUT_BIN_SUFFIX)
endef


```

--------------------------------------------------------------------------------
/pkg/config/config_test.json:
--------------------------------------------------------------------------------

```json
{
  "BrowserServer": {
  },
  "CommandServer": {
    "allowed_commands": [
      "ls",
      "cat",
      "echo"
    ]
  },
  "FilesystemServer": {
    "allowed_dirs": [
      "/tmp/.moling/data/"
    ],
    "cache_path": "/tmp/.moling/data"
  },
  "MoLingConfig": {
    "HomeDir": "",
    "SystemInfo": "",
    "Username": "",
    "base_path": "/newpath/.moling",
    "config_file": "config/config.json",
    "debug": false,
    "listen_addr": "",
    "version": "darwin-arm64-20250330084836-0077553"
  },
  "MoaServer": {}
}
```

--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import "github.com/gojue/moling/cli"

func main() {
	cli.Start()
}

```

--------------------------------------------------------------------------------
/cli/main.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package cli

import (
	"github.com/gojue/moling/cli/cmd"
)

func Start() {
	cmd.Execute()
}

```

--------------------------------------------------------------------------------
/pkg/comm/errors.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package comm

import "errors"

var (
	ErrConfigNotLoaded = errors.New("config not loaded, please call LoadConfig() first")
)

```

--------------------------------------------------------------------------------
/pkg/services/filesystem/file_system_windows.go:
--------------------------------------------------------------------------------

```go
/*
 * Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Repository: https://github.com/gojue/moling
 */

package filesystem

import "os"

func init() {
	dirName, err := os.UserCacheDir()
	if err != nil {
		dirName, err = os.UserHomeDir()
		if err != nil {
			return
		}
	}
	allowedDirsDefault = dirName
}

```

--------------------------------------------------------------------------------
/pkg/services/command/command_exec_windows.go:
--------------------------------------------------------------------------------

```go
//go:build windows

// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

// Package services Description: This file contains the implementation of the CommandServer interface for Windows.
package command

import (
	"os/exec"
)

// ExecCommand executes a command and returns its output.
func ExecCommand(command string) (string, error) {
	var cmd *exec.Cmd
	cmd = exec.Command("cmd", "/C", command)
	output, err := cmd.CombinedOutput()
	return string(output), err
}

```

--------------------------------------------------------------------------------
/.github/workflows/go-test.yml:
--------------------------------------------------------------------------------

```yaml
name: Golang CI and Test

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build-on-ubuntu2204:
    strategy:
      matrix:
        os: [ darwin, windows, linux ]
        arch: [amd64, arm64]
    runs-on: ubuntu-22.04
    name: test on ${{ matrix.os }} ${{ matrix.arch }}
    steps:
      - uses: actions/setup-go@v5
        with:
          go-version: '1.24.1'
      - uses: actions/checkout@v4
        with:
          submodules: 'recursive'
          fetch-depth: 0
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: v2.1
          skip-cache: true
          problem-matchers: true
      - name: Test (go test)
        run: |
          make clean
          TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make env
          TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make test
      - name: MoLing Build
        run: |
          make clean
          TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make env
          TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make build
```

--------------------------------------------------------------------------------
/pkg/utils/pid_unix.go:
--------------------------------------------------------------------------------

```go
//go:build darwin || linux || freebsd || openbsd || netbsd

/*
 * Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Repository: https://github.com/gojue/moling
 */

package utils

import (
	"errors"
	"os"
	"syscall"
)

func lockFile(file *os.File) (bool, error) {
	err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		if errors.Is(err, syscall.EWOULDBLOCK) {
			return false, nil // 已经被锁定,返回false但不返回错误
		}
		return false, err
	}
	return true, nil
}

func unlockFile(file *os.File) error {
	return syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
}

```

--------------------------------------------------------------------------------
/cli/cmd/perrun.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package cmd

import (
	"path/filepath"

	"github.com/spf13/cobra"

	"github.com/gojue/moling/pkg/utils"
)

// mlsCommandPreFunc is a pre-run function for the MoLing command.
func mlsCommandPreFunc(cmd *cobra.Command, args []string) error {
	err := utils.CreateDirectory(mlConfig.BasePath)
	if err != nil {
		return err
	}
	for _, dirName := range mlDirectories {
		err = utils.CreateDirectory(filepath.Join(mlConfig.BasePath, dirName))
		if err != nil {
			return err
		}
	}
	return nil
}

```

--------------------------------------------------------------------------------
/pkg/services/command/command_exec.go:
--------------------------------------------------------------------------------

```go
//go:build !windows

// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package command

import (
	"context"
	"errors"
	"os/exec"
	"time"
)

// ExecCommand executes a command and returns its output.
func ExecCommand(command string) (string, error) {
	var cmd *exec.Cmd
	ctx, cfunc := context.WithTimeout(context.Background(), time.Second*10)
	defer cfunc()
	cmd = exec.CommandContext(ctx, "sh", "-c", command)
	output, err := cmd.CombinedOutput()
	if err != nil {
		switch {
		case errors.Is(err, exec.ErrNotFound):
			// 命令未找到
			return "", errors.New("command not found")
		case errors.Is(ctx.Err(), context.DeadlineExceeded):
			// 超时时仅返回输出,不返回错误
			return string(output), nil
		default:
			return string(output), nil
		}
	}

	return string(output), nil
}

```

--------------------------------------------------------------------------------
/pkg/services/browser/browser_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package browser

import (
	"testing"

	"github.com/gojue/moling/pkg/comm"
)

func TestBrowserServer(t *testing.T) {
	//
	//cfg := &BrowserConfig{
	//	Headless:        true,
	//	Timeout:         30,
	//	UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
	//	DefaultLanguage: "en-US",
	//	URLTimeout:      10,
	//	SelectorQueryTimeout:      10,
	//}
	logger, ctx, err := comm.InitTestEnv()
	if err != nil {
		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: %s", err.Error())
	}
}

```

--------------------------------------------------------------------------------
/variables.mk:
--------------------------------------------------------------------------------

```
CMD_MAKE = make
CMD_TR ?= tr
CMD_CUT ?= cut
CMD_AWK ?= awk
CMD_SED ?= sed
CMD_FILE ?= file
CMD_GIT ?= git
CMD_CLANG ?= clang
CMD_RM ?= rm
CMD_INSTALL ?= install
CMD_MKDIR ?= mkdir
CMD_TOUCH ?= touch
CMD_GO ?= go
CMD_GREP ?= grep
CMD_CAT ?= cat
CMD_MD5 ?= md5sum
CMD_TAR ?= tar
CMD_CHECKSUM ?= sha256sum
CMD_GITHUB ?= gh
CMD_MV ?= mv
CMD_CP ?= cp
CMD_CD ?= cd
CMD_ECHO ?= echo


#
# tools version
#

GO_VERSION = $(shell $(CMD_GO) version 2>/dev/null | $(CMD_AWK) '{print $$3}' | $(CMD_SED) 's:go::g' | $(CMD_CUT) -d. -f1,2)
GO_VERSION_MAJ = $(shell $(CMD_ECHO) $(GO_VERSION) | $(CMD_CUT) -d'.' -f1)
GO_VERSION_MIN = $(shell $(CMD_ECHO) $(GO_VERSION) | $(CMD_CUT) -d'.' -f2)

# tags date info
VERSION_NUM ?= v0.0.0
NOW_DATE := $(shell date +"%Y-%m-%d %H:%M:%S")
TAG_COMMIT := $(shell git rev-list --abbrev-commit --tags --max-count=1)
TAG := $(shell git describe --abbrev=0 --tags ${TAG_COMMIT} 2>/dev/null || true)
COMMIT := $(shell git rev-parse --short HEAD)
DATE := $(shell git log -1 --format=%cd --date=format:"%Y %m %d")
LAST_GIT_TAG := $(COMMIT)_$(NOW_DATE)

ifndef SNAPSHOT_VERSION
	VERSION_NUM = $(LAST_GIT_TAG)
else
	VERSION_NUM = $(SNAPSHOT_VERSION)_$(NOW_DATE)
endif

#
# environment
#
#SNAPSHOT_VERSION ?= $(shell git rev-parse HEAD)
BUILD_DATE := $(shell date +%Y-%m-%d)
TARGET_TAG :=
OS_NAME ?= $(shell uname -s|tr 'A-Z' 'a-z')
OS_ARCH ?= $(shell uname -m)
OS_VERSION_SHORT := $(shell uname -r | cut -d'-' -f 1)
TARGET_OS ?= $(OS_NAME)
TARGET_ARCH ?= $(if $(filter x86_64,$(OS_ARCH)),amd64,arm64)
OUT_BIN := bin/moling
```

--------------------------------------------------------------------------------
/install/install.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

set -e

echo "Welcome to MoLing MCP Server initialization script."
echo "Home page: https://gojue.cc/moling"
echo "Github: https://github.com/gojue/moling"

# Determine the OS and architecture
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)

case $ARCH in
  x86_64)
    ARCH="amd64"
    ;;
  arm64|aarch64)
    ARCH="arm64"
    ;;
  *)
    echo "Unsupported architecture: $ARCH"
    exit 1
    ;;
esac

# Determine the download URL
VERSION=$(curl -s https://api.github.com/repos/gojue/moling/releases/latest | grep 'tag_name' | cut -d\" -f4)
BASE_URL="https://github.com/gojue/moling/releases/download/${VERSION}"
FILE_NAME="moling-${VERSION}-${OS}-${ARCH}.tar.gz"

DOWNLOAD_URL="${BASE_URL}/${FILE_NAME}"

# Download the installation package
echo "Downloading ${DOWNLOAD_URL}..."
curl -LO "${DOWNLOAD_URL}"
echo "Download completed. filename: ${FILE_NAME}"
# Extract the package
tar -xzf "${FILE_NAME}"

# Move the binary to /usr/local/bin
mv moling /usr/local/bin/moling
chmod +x /usr/local/bin/moling

# Clean up
rm -rf moling "${FILE_NAME}"

# Check if the installation was successful
if command -v moling &> /dev/null; then
    echo "MoLing installation was successful!"
else
    echo "MoLing installation failed."
    exit 1
fi

# initialize the configuration
echo "Initializing MoLing configuration..."
moling config --init
echo "MoLing configuration initialized successfully!"

echo "setup MCP Server configuration into MCP Client"
moling client -i
echo "MCP Client configuration setup successfully!"
```

--------------------------------------------------------------------------------
/pkg/services/register.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package services

import (
	"github.com/gojue/moling/pkg/comm"
	"github.com/gojue/moling/pkg/services/abstract"
	"github.com/gojue/moling/pkg/services/browser"
	"github.com/gojue/moling/pkg/services/command"
	"github.com/gojue/moling/pkg/services/filesystem"
)

var serviceLists = make(map[comm.MoLingServerType]abstract.ServiceFactory)

// RegisterServ register service
func RegisterServ(n comm.MoLingServerType, f abstract.ServiceFactory) {
	serviceLists[n] = f
}

// ServiceList  get service lists
func ServiceList() map[comm.MoLingServerType]abstract.ServiceFactory {
	return serviceLists
}

func init() {
	// Register the filesystem service
	RegisterServ(filesystem.FilesystemServerName, filesystem.NewFilesystemServer)
	// Register the browser service
	RegisterServ(browser.BrowserServerName, browser.NewBrowserServer)
	// Register the command service
	RegisterServ(command.CommandServerName, command.NewCommandServer)
}

```

--------------------------------------------------------------------------------
/install/install.ps1:
--------------------------------------------------------------------------------

```
#!/usr/bin/env pwsh

Set-StrictMode -Version Latest

Write-Output "Welcome to MoLing MCP Server initialization script."
Write-Output "Home page: https://gojue.cc/moling"
Write-Output "Github: https://github.com/gojue/moling"

# Determine the OS and architecture
$OS = (Get-CimInstance Win32_OperatingSystem).Caption
$ARCH = (Get-CimInstance Win32_Processor).Architecture

switch ($ARCH) {
    9 { $ARCH = "amd64" }
    5 { $ARCH = "arm64" }
    default {
        Write-Error "Unsupported architecture: $ARCH"
        exit 1
    }
}

# Determine the download URL
$VERSION = "v0.0.1"
$BASE_URL = "https://github.com/gojue/moling/releases/download/$VERSION"
$FILE_NAME = "moling-$VERSION-windows-$ARCH.zip"
$DOWNLOAD_URL = "$BASE_URL/$FILE_NAME"

# Download the installation package
Write-Output "Downloading $DOWNLOAD_URL..."
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $FILE_NAME

# Extract the package
Write-Output "Extracting $FILE_NAME..."
Expand-Archive -Path $FILE_NAME -DestinationPath "moling"

# Move the binary to C:\Program Files
$destination = "C:\Program Files\moling"
if (-Not (Test-Path -Path $destination)) {
    New-Item -ItemType Directory -Path $destination
}
Move-Item -Path "moling\moling.exe" -Destination "$destination\moling.exe"

# Add to PATH
$env:Path += ";$destination"
[System.Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)

# MCP Client configuration
& "$destination\moling.exe" client --install

# Clean up
Remove-Item -Recurse -Force "moling"
Remove-Item -Force $FILE_NAME

Write-Output "MoLing has been installed successfully!"
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: MoLing Release
on:
  push:
    tags:
      - "v*"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-on-ubuntu2204:
    strategy:
      matrix:
        os: [ darwin, windows, linux ]
        arch: [ amd64, arm64 ]
    runs-on: ubuntu-22.04
    name: Release on ${{ matrix.os }} ${{ matrix.arch }}
    steps:
      - uses: actions/setup-go@v5
        with:
          go-version: '1.24.1'
      - uses: actions/checkout@v4
        with:
          submodules: 'recursive'
          fetch-depth: 0
      - name: MoLing Build
        run: |
          make clean
          SNAPSHOT_VERSION=${{ github.ref_name }} TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make env
          SNAPSHOT_VERSION=${{ github.ref_name }} TARGET_OS=${{ matrix.os }} TARGET_ARCH=${{ matrix.arch }} make build
          pwd
          ls -al ./bin
      - name: Create Archive
        run: |
          mkdir -p ./dist
          pwd
          ls -al ./bin
          if [ "${{ matrix.os }}" = "windows" ]; then
            cd ./bin && zip -qr ./../dist/moling-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.arch }}.zip ./bin/ . && cd ..
          else
            tar -czvf dist/moling-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.arch }}.tar.gz -C ./bin/ .
          fi
      - name: Upload Release Asset
        uses: softprops/action-gh-release@v2
        if: startsWith(github.ref, 'refs/tags/')
        with:
          tag_name: ${{ github.ref_name }}
          generate_release_notes: true
          files: |
            ./dist/moling-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.arch }}.*
```

--------------------------------------------------------------------------------
/pkg/comm/comm.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package comm

import (
	"context"
	"os"
	"path/filepath"

	"github.com/rs/zerolog"

	"github.com/gojue/moling/pkg/config"
)

type MoLingServerType string

type contextKey string

// MoLingConfigKey is a context key for storing the version of MoLing
const (
	MoLingConfigKey contextKey = "moling_config"
	MoLingLoggerKey contextKey = "moling_logger"
)

// InitTestEnv initializes the test environment by creating a temporary log file and setting up the logger.
func InitTestEnv() (zerolog.Logger, context.Context, error) {
	logFile := filepath.Join(os.TempDir(), "moling.log")
	zerolog.SetGlobalLevel(zerolog.DebugLevel)
	var logger zerolog.Logger
	f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
	if err != nil {
		return zerolog.Logger{}, nil, err
	}
	logger = zerolog.New(f).With().Timestamp().Logger()
	mlConfig := &config.MoLingConfig{
		ConfigFile: filepath.Join("config", "test_config.json"),
		BasePath:   os.TempDir(),
	}
	ctx := context.WithValue(context.Background(), MoLingConfigKey, mlConfig)
	ctx = context.WithValue(ctx, MoLingLoggerKey, logger)
	return logger, ctx, nil
}

```

--------------------------------------------------------------------------------
/pkg/utils/pid_windows.go:
--------------------------------------------------------------------------------

```go
//go:build windows

/*
 * Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Repository: https://github.com/gojue/moling
 */

package utils

import (
	"errors"
	"os"
	"syscall"
	"unsafe"
)

var (
	kernel32     = syscall.NewLazyDLL("kernel32.dll")
	lockFileEx   = kernel32.NewProc("LockFileEx")
	unlockFileEx = kernel32.NewProc("UnlockFileEx")
)

const (
	LockfileExclusiveLock   = 2
	LockfileFailImmediately = 1
)
const ErrorLockViolation = syscall.Errno(33) // 0x21

// lockFile locks the given file using Windows API.
func lockFile(file *os.File) (bool, error) {
	handle := syscall.Handle(file.Fd())
	var overlapped syscall.Overlapped

	flags := LockfileExclusiveLock | LockfileFailImmediately
	r, _, err := lockFileEx.Call(
		uintptr(handle),
		uintptr(flags),
		0,
		1,
		0,
		uintptr(unsafe.Pointer(&overlapped)),
	)

	if r == 0 {
		if !errors.Is(err, ErrorLockViolation) {
			return false, err
		}
		return false, nil
	}

	return true, nil
}

// unlockFile unlocks the given file using Windows API.
func unlockFile(file *os.File) error {
	handle := syscall.Handle(file.Fd())
	var overlapped syscall.Overlapped

	r, _, err := unlockFileEx.Call(
		uintptr(handle),
		0,
		1,
		0,
		uintptr(unsafe.Pointer(&overlapped)),
	)

	if r == 0 {
		return err
	}

	return nil
}

```

--------------------------------------------------------------------------------
/pkg/config/config_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package config

import (
	"encoding/json"
	"os"
	"testing"

	"github.com/gojue/moling/pkg/utils"
)

// TestConfigLoad tests the loading of the configuration from a JSON file.
func TestConfigLoad(t *testing.T) {
	configFile := "config_test.json"
	cfg := &MoLingConfig{}
	cfg.ConfigFile = "config.json"
	cfg.BasePath = "/tmp/moling"
	cfg.Version = "1.0.0"
	cfg.ListenAddr = ":8080"
	cfg.Debug = true
	cfg.Username = "user1"
	cfg.HomeDir = "/Users/user1"
	cfg.SystemInfo = "Darwin 15.3.3"

	jsonData, err := os.ReadFile(configFile)
	if err != nil {
		t.Fatalf("failed to read config file: %s", err.Error())
	}
	var jsonMap map[string]any
	if err := json.Unmarshal(jsonData, &jsonMap); err != nil {
		t.Fatalf("Failed to unmarshal JSON: %s", err.Error())
	}
	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: %s", err.Error())
	}
	t.Logf("Config loaded, MoLing Config.BasePath: %s", cfg.BasePath)
	if cfg.BasePath != "/newpath/.moling" {
		t.Fatalf("expected BasePath to be '/newpath/.moling', got '%s'", cfg.BasePath)
	}
}

```

--------------------------------------------------------------------------------
/client/client_config_windows.go:
--------------------------------------------------------------------------------

```go
/*
 *
 *  Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Repository: https://github.com/gojue/moling
 *
 */

package client

import (
	"os"
	"path/filepath"
)

func init() {
	clientLists["VSCODE Cline"] = filepath.Join(os.Getenv("APPDATA"), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae CN Cline"] = filepath.Join(os.Getenv("APPDATA"), "Trae CN", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae Cline"] = filepath.Join(os.Getenv("APPDATA"), "Trae", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae"] = filepath.Join(os.Getenv("APPDATA"), "Trae", "User", "mcp.json")
	clientLists["Trae CN"] = filepath.Join(os.Getenv("APPDATA"), "Trae CN", "User", "mcp.json")
	clientLists["VSCODE Roo Code"] = filepath.Join(os.Getenv("APPDATA"), "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "mcp_settings.json")
	clientLists["Trae CN Roo"] = filepath.Join(os.Getenv("APPDATA"), "Trae CN", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "mcp_settings.json")
	clientLists["Trae Roo"] = filepath.Join(os.Getenv("APPDATA"), "Trae", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "mcp_settings.json")
	clientLists["Claude"] = filepath.Join(os.Getenv("APPDATA"), "Claude", "claude_desktop_config.json")
	clientLists["Cursor"] = filepath.Join(os.Getenv("APPDATA"), "Cursor", "mcp.json")
}

```

--------------------------------------------------------------------------------
/pkg/services/abstract/abstract.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package abstract

import (
	"context"

	"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)

// Service defines the interface for a service with various handlers and tools.
type Service interface {
	Ctx() context.Context
	// Resources returns a map of resources and their corresponding handler functions.
	Resources() map[mcp.Resource]server.ResourceHandlerFunc
	// ResourceTemplates returns a map of resource templates and their corresponding handler functions.
	ResourceTemplates() map[mcp.ResourceTemplate]server.ResourceTemplateHandlerFunc
	// Prompts returns a map of prompts and their corresponding handler functions.
	Prompts() []PromptEntry
	// Tools returns a slice of server tools.
	Tools() []server.ServerTool
	// NotificationHandlers returns a map of notification handlers.
	NotificationHandlers() map[string]server.NotificationHandlerFunc

	// 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]any) error

	// Init initializes the service with the given context and configuration.
	Init() error

	MlConfig() *config.MoLingConfig

	// Name returns the name of the service.
	Name() comm.MoLingServerType

	// Close closes the service and releases any resources it holds.
	Close() error
}

```

--------------------------------------------------------------------------------
/pkg/utils/pid.go:
--------------------------------------------------------------------------------

```go
/*
 * Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Repository: https://github.com/gojue/moling
 */

package utils

import (
	"fmt"
	"os"
)

var pidFile *os.File

// CreatePIDFile creates and locks a PID file to prevent multiple instances.
func CreatePIDFile(pidFilePath string) error {
	// Open or create the PID file
	file, err := os.OpenFile(pidFilePath, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to open PID file: %w", err)
	}

	// Try to lock the file using platform-specific code
	locked, err := lockFile(file)
	if err != nil {
		_ = file.Close()
		return fmt.Errorf("failed to lock PID file: %w", err)
	}
	if !locked {
		_ = file.Close()
		return fmt.Errorf("another instance is already running: %s", pidFilePath)
	}

	// Write the current PID to the file
	err = file.Truncate(0)
	if err != nil {
		_ = unlockFile(file)
		_ = file.Close()
		return fmt.Errorf("failed to truncate PID file: %w", err)
	}
	_, err = file.WriteString(fmt.Sprintf("%d\n", os.Getpid()))
	if err != nil {
		_ = unlockFile(file)
		_ = file.Close()
		return fmt.Errorf("failed to write PID to file: %w", err)
	}

	// Keep the file open to maintain the lock
	pidFile = file
	return nil
}

// RemovePIDFile releases the lock and removes the PID file.
func RemovePIDFile(pidFilePath string) error {
	if pidFile != nil {
		err := unlockFile(pidFile)
		if err != nil {
			return fmt.Errorf("failed to unlock PID file: %w", err)
		}
		_ = pidFile.Close()
		pidFile = nil
		err = os.Remove(pidFilePath)
		if err != nil {
			return fmt.Errorf("failed to remove PID file: %w", err)
		}
	}
	return nil
}

```

--------------------------------------------------------------------------------
/pkg/server/server_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package server

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/gojue/moling/pkg/comm"
	"github.com/gojue/moling/pkg/config"
	"github.com/gojue/moling/pkg/services/abstract"
	"github.com/gojue/moling/pkg/services/filesystem"
	"github.com/gojue/moling/pkg/utils"
)

func TestNewMLServer(t *testing.T) {
	// Create a new MoLingConfig
	mlConfig := config.MoLingConfig{
		BasePath: filepath.Join(os.TempDir(), "moling_test"),
	}
	mlDirectories := []string{
		"logs",    // log file
		"config",  // config file
		"browser", // browser cache
		"data",    // data
		"cache",
	}
	err := utils.CreateDirectory(mlConfig.BasePath)
	if err != nil {
		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: %s", dirName, err.Error())
		}
	}
	logger, ctx, err := comm.InitTestEnv()
	if err != nil {
		t.Fatalf("Failed to initialize test environment: %s", err.Error())
	}
	logger.Info().Msg("TestBrowserServer")
	mlConfig.SetLogger(logger)

	// Create a new server with the filesystem service
	fs, err := filesystem.NewFilesystemServer(ctx)
	if err != nil {
		t.Errorf("Failed to create filesystem server: %s", err.Error())
	}
	err = fs.Init()
	if err != nil {
		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: %s", err.Error())
	}
	err = srv.Serve()
	if err != nil {
		t.Errorf("Failed to start server: %s", err.Error())
	}
	t.Logf("Server started successfully: %v", srv)
}

```

--------------------------------------------------------------------------------
/client/client_config.go:
--------------------------------------------------------------------------------

```go
//go:build !windows

/*
 *
 *  Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Repository: https://github.com/gojue/moling
 *
 */

package client

import (
	"os"
	"path/filepath"
)

func init() {
	clientLists["VSCode Cline"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae CN Cline"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae CN", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae Cline"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json")
	clientLists["Trae"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae", "User", "mcp.json")
	clientLists["Trae CN"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae CN", "User", "mcp.json")
	clientLists["VSCode Roo"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "mcp_settings.json")
	clientLists["Trae CN Roo"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae CN", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "mcp_settings.json")
	clientLists["Trae Roo"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Trae", "User", "globalStorage", "rooveterinaryinc.roo-cline", "settings", "mcp_settings.json")
	clientLists["Claude"] = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Claude", "claude_desktop_config.json")
	clientLists["Cursor"] = filepath.Join(os.Getenv("HOME"), ".cursor", "mcp.json")
}

```

--------------------------------------------------------------------------------
/pkg/config/config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package config

import (
	"github.com/rs/zerolog"
)

// Config is an interface that defines a method for checking configuration validity.
type Config interface {
	// Check validates the configuration and returns an error if the configuration is invalid.
	Check() error
}

// MoLingConfig is a struct that holds the configuration for the MoLing server.
type MoLingConfig struct {
	ConfigFile string `json:"config_file"` // The path to the configuration file.
	BasePath   string `json:"base_path"`   // The base path for the server, used for storing files. automatically created if not exists. eg: /Users/user1/.moling
	//AllowDir   []string `json:"allow_dir"`   // The directories that are allowed to be accessed by the server.
	Version    string `json:"version"`     // The version of the MoLing server.
	ListenAddr string `json:"listen_addr"` // The address to listen on for SSE mode.
	Debug      bool   `json:"debug"`       // Debug mode, if true, the server will run in debug mode.
	Module     string `json:"module"`      // The module to load, default: all
	Username   string // The username of the user running the server.
	HomeDir    string // The home directory of the user running the server. macOS: /Users/user1, Linux: /home/user1
	SystemInfo string // The system information of the user running the server. macOS: Darwin 15.3.3, Linux: Ubuntu 20.04.1 LTS

	// for MCP Server Config
	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.
	ServerName  string // ServerName MCP ServerName, add to the MCP Client config
	logger      zerolog.Logger
}

func (cfg *MoLingConfig) Check() error {
	panic("not implemented yet") // TODO: Implement Check
}

func (cfg *MoLingConfig) Logger() zerolog.Logger {
	return cfg.logger
}

func (cfg *MoLingConfig) SetLogger(logger zerolog.Logger) {
	cfg.logger = logger
}

```

--------------------------------------------------------------------------------
/cli/cmd/client.go:
--------------------------------------------------------------------------------

```go
/*
 *
 *  Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Repository: https://github.com/gojue/moling
 *
 */

package cmd

import (
	"os"
	"time"

	"github.com/rs/zerolog"
	"github.com/spf13/cobra"

	"github.com/gojue/moling/client"
)

var clientCmd = &cobra.Command{
	Use:   "client",
	Short: "Provides automated access to MoLing MCP Server for local MCP clients, Cline, Roo Code, and Claude, etc.",
	Long: `Automatically checks the MCP clients installed on the current computer, displays them, and automatically adds the MoLing MCP Server configuration to enable one-click activation, reducing the hassle of manual configuration.
Currently supports the following clients: Cline, Roo Code, Claude
    moling client -l --list   List the current installed MCP clients
    moling client -i --install Add MoLing MCP Server configuration to the currently installed MCP clients on this computer
`,
	RunE: ClientCommandFunc,
}

var (
	list    bool
	install bool
)

// ClientCommandFunc executes the "config" command.
func ClientCommandFunc(command *cobra.Command, args []string) error {
	logger := initLogger(mlConfig.BasePath)
	consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
	multi := zerolog.MultiLevelWriter(consoleWriter, logger)
	logger = zerolog.New(multi).With().Timestamp().Logger()
	mlConfig.SetLogger(logger)
	logger.Debug().Msg("Start to show MCP Clients")
	mcpConfig := client.NewMCPServerConfig(CliDescription, CliName, MCPServerName)
	exePath, err := os.Executable()
	if err == nil {
		logger.Debug().Str("exePath", exePath).Msg("executable path, will use this path to find the config file")
		mcpConfig.Command = exePath
	}
	cm := client.NewManager(logger, mcpConfig)
	if install {
		logger.Info().Msg("Start to add MCP Server configuration into MCP Clients.")
		cm.SetupConfig()
		logger.Info().Msg("Add MCP Server configuration into MCP Clients successfully.")
		return nil
	}
	logger.Info().Msg("Start to list MCP Clients")
	cm.ListClient()
	logger.Info().Msg("List MCP Clients successfully.")
	return nil
}

func init() {
	clientCmd.PersistentFlags().BoolVar(&list, "list", false, "List the current installed MCP clients")
	clientCmd.PersistentFlags().BoolVarP(&install, "install", "i", false, "Add MoLing MCP Server configuration to the currently installed MCP clients on this computer. default is all")
	rootCmd.AddCommand(clientCmd)
}

```

--------------------------------------------------------------------------------
/pkg/services/command/command_exec_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package command

import (
	"context"
	"errors"
	"os/exec"
	"reflect"
	"testing"
	"time"

	"github.com/gojue/moling/pkg/comm"
)

// MockCommandServer is a mock implementation of CommandServer for testing purposes.
type MockCommandServer struct {
	CommandServer
}

// TestExecuteCommand tests the ExecCommand function.
func TestExecuteCommand(t *testing.T) {
	execCmd := "echo 'Hello, World!'"
	// Test a simple command
	output, err := ExecCommand(execCmd)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	expectedOutput := "Hello, World!\n"
	if output != expectedOutput {
		t.Errorf("Expected output %q, got %q", expectedOutput, output)
	}
	t.Logf("Command output: %s", output)
	// Test a command with a timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10)
	defer cancel()

	execCmd = "curl ifconfig.me|grep Time"
	output, err = ExecCommand(execCmd)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	t.Logf("Command output: %s", output)
	cmd := exec.CommandContext(ctx, "sleep", "1")
	err = cmd.Run()
	if err == nil {
		t.Fatalf("Expected timeout error, got nil")
	}
	if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
		t.Errorf("Expected context deadline exceeded error, got %v", ctx.Err())
	}
}

func TestAllowCmd(t *testing.T) {
	// Test with a command that is allowed
	_, ctx, err := comm.InitTestEnv()
	if err != nil {
		t.Fatalf("Failed to initialize test environment: %v", err)
	}

	cs, err := NewCommandServer(ctx)
	if err != nil {
		t.Fatalf("Failed to create CommandServer: %v", err)
	}

	cc := StructToMap(NewCommandConfig())
	t.Logf("CommandConfig: %v", cc)
	err = cs.LoadConfig(cc)
	if err != nil {
		t.Fatalf("Failed to load config: %v", err)
	}
	cmd := "cd /var/logs/notfound && git log --since=\"today\" --pretty=format:\"%h - %an, %ar : %s\""
	cs1 := cs.(*CommandServer)
	if !cs1.isAllowedCommand(cmd) {
		t.Errorf("Command '%s' is not allowed", cmd)
	}
	t.Log("Command is allowed:", cmd)
}

// 将 struct 转换为 map
func StructToMap(obj any) map[string]any {
	result := make(map[string]any)
	val := reflect.ValueOf(obj)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	if val.Kind() != reflect.Struct {
		return nil
	}
	typ := val.Type()
	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		value := val.Field(i)
		// 跳过未导出的字段
		if field.PkgPath != "" {
			continue
		}
		// 获取字段的 json tag(如果存在)
		key := field.Name
		if tag := field.Tag.Get("json"); tag != "" {
			key = tag
		}
		result[key] = value.Interface()
	}
	return result
}

```

--------------------------------------------------------------------------------
/client/client_test.go:
--------------------------------------------------------------------------------

```go
/*
 *
 *  Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Repository: https://github.com/gojue/moling
 *
 */

package client

import (
	"os"
	"testing"

	"github.com/rs/zerolog"
)

func TestClientManager_ListClient(t *testing.T) {
	logger := zerolog.New(os.Stdout)
	mcpConfig := NewMCPServerConfig("MoLing UnitTest Description", "moling_test", "MoLing MCP Server")
	cm := NewManager(logger, mcpConfig)
	// Mock client list
	clientLists["TestClient"] = "/path/to/nonexistent/file"

	cm.ListClient()
	// Check logs or other side effects as needed
}

/*
	func TestClientManager_SetupConfig(t *testing.T) {
		logger := zerolog.New(os.Stdout)
		mcpConfig := NewMCPServerConfig("MoLing UnitTest Description", "moling_test", "MoLing MCP Server")
		cm := NewManager(logger, mcpConfig)

		// Mock client list
		clientLists["TestClient"] = "/path/to/nonexistent/file"

		cm.SetupConfig()
		// Check logs or other side effects as needed
	}

	func TestClientManager_appendConfig(t *testing.T) {
		logger := zerolog.New(os.Stdout)
		mcpConfig := NewMCPServerConfig("MoLing UnitTest Description", "moling_test", "MoLing MCP Server")
		cm := NewManager(logger, mcpConfig)

		// Mock payload
		payload := []byte(`{
	  "Cline": {
	    "description": "MoLing UnitTest Description",
	    "isActive": true,
	    "command": "moling_test"
	  },
	  "mcpServers": {
	    "testABC": {
	      "args": [
	        "--allow-dir",
	        "/tmp/,/Users/username/Downloads"
	      ],
	      "command": "npx",
	      "timeout": 300
	    }
	  }
	}`)

	result, err := cm.appendConfig("TestClient", payload)
	if err != nil {
		t.Fatalf("Expected no error, got %s", err.Error())
	}

	var resultMap map[string]any
	err = json.Unmarshal(result, &resultMap)
	if err != nil {
		t.Fatalf("Expected valid JSON, got error %s", err.Error())
	}

	if resultMap["existingKey"] != "existingValue" {
		t.Errorf("Expected existingKey to be existingValue, got %v", resultMap["existingKey"])
	}

}
*/

func TestClientManager_checkExist(t *testing.T) {
	logger := zerolog.New(os.Stdout)
	mcpConfig := NewMCPServerConfig("MoLing UnitTest Description", "moling_test", "MoLing MCP Server")
	cm := NewManager(logger, mcpConfig)

	// Test with a non-existent file
	exists := cm.checkExist("/path/to/nonexistent/file")
	if exists {
		t.Errorf("Expected file to not exist")
	}

	// Test with an existing file
	file, err := os.CreateTemp("", "testfile")
	if err != nil {
		t.Fatalf("Failed to create temp file: %s", err.Error())
	}
	defer func() {
		_ = os.Remove(file.Name())
	}()
	t.Logf("Created temp file: %s", file.Name())
	exists = cm.checkExist(file.Name())
	if !exists {
		t.Errorf("Expected file to exist")
	}
}

```

--------------------------------------------------------------------------------
/pkg/utils/utils.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package utils

import (
	"fmt"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

// CreateDirectory checks if a directory exists, and creates it if it doesn't
func CreateDirectory(path string) error {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.MkdirAll(path, 0o755)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}
	return nil
}

// StringInSlice checks if a string is in a slice of strings
func StringInSlice(s string, modules []string) bool {
	for _, module := range modules {
		if module == s {
			return true
		}
	}
	return false
}

// MergeJSONToStruct 将JSON中的字段合并到结构体中
func MergeJSONToStruct(target any, jsonMap map[string]any) error {
	// 获取目标结构体的反射值
	val := reflect.ValueOf(target).Elem()
	typ := val.Type()

	// 遍历JSON map中的每个字段
	for jsonKey, jsonValue := range jsonMap {
		// 遍历结构体的每个字段
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			// 检查JSON字段名是否与结构体的JSON tag匹配
			if field.Tag.Get("json") == jsonKey {
				// 获取结构体字段的反射值
				fieldVal := val.Field(i)
				// 检查字段是否可设置
				if fieldVal.CanSet() {
					// 将JSON值转换为结构体字段的类型
					jsonVal := reflect.ValueOf(jsonValue)
					if jsonVal.Type().ConvertibleTo(fieldVal.Type()) {
						fieldVal.Set(jsonVal.Convert(fieldVal.Type()))
					} else {
						return fmt.Errorf("type mismatch for field %s, value:%v", jsonKey, jsonValue)
					}
				}
			}
		}
	}
	return nil
}

// DetectMimeType tries to determine the MIME type of a file
func DetectMimeType(path string) string {
	// First try by extension
	ext := filepath.Ext(path)
	if ext != "" {
		mimeType := mime.TypeByExtension(ext)
		if mimeType != "" {
			return mimeType
		}
	}

	// If that fails, try to read a bit of the file
	file, err := os.Open(path)
	if err != nil {
		return "application/octet-stream" // Default
	}
	defer file.Close()

	// Read first 512 bytes to detect content type
	buffer := make([]byte, 512)
	n, err := file.Read(buffer)
	if err != nil {
		return "application/octet-stream" // Default
	}

	// Use http.DetectContentType
	return http.DetectContentType(buffer[:n])
}

// IsTextFile determines if a file is likely a text file based on MIME type
func IsTextFile(mimeType string) bool {
	return strings.HasPrefix(mimeType, "text/") ||
		mimeType == "application/json" ||
		mimeType == "application/xml" ||
		mimeType == "application/javascript" ||
		mimeType == "application/x-javascript" ||
		strings.Contains(mimeType, "+xml") ||
		strings.Contains(mimeType, "+json")
}

// IsImageFile determines if a file is an image based on MIME type
func IsImageFile(mimeType string) bool {
	return strings.HasPrefix(mimeType, "image/")
}

// PathToResourceURI converts a file path to a resource URI
func PathToResourceURI(path string) string {
	return "file://" + path
}

```

--------------------------------------------------------------------------------
/pkg/server/server.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling
package server

import (
	"context"
	"fmt"
	"log"
	"os"
	"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"
)

type MoLingServer struct {
	ctx        context.Context
	server     *server.MCPServer
	services   []abstract.Service
	logger     zerolog.Logger
	mlConfig   config.MoLingConfig
	listenAddr string // SSE mode listen address, if empty, use STDIO mode.
}

func NewMoLingServer(ctx context.Context, srvs []abstract.Service, mlConfig config.MoLingConfig) (*MoLingServer, error) {
	mcpServer := server.NewMCPServer(
		mlConfig.ServerName,
		mlConfig.Version,
		server.WithResourceCapabilities(true, true),
		server.WithLogging(),
		server.WithPromptCapabilities(true),
	)
	// Set the context for the server
	ms := &MoLingServer{
		ctx:        ctx,
		server:     mcpServer,
		services:   srvs,
		listenAddr: mlConfig.ListenAddr,
		logger:     ctx.Value(comm.MoLingLoggerKey).(zerolog.Logger),
		mlConfig:   mlConfig,
	}
	err := ms.init()
	return ms, err
}

func (m *MoLingServer) init() error {
	var err error
	for _, srv := range m.services {
		m.logger.Debug().Str("serviceName", string(srv.Name())).Msg("Loading service")
		err = m.loadService(srv)
		if err != nil {
			m.logger.Info().Err(err).Str("serviceName", string(srv.Name())).Msg("Failed to load service")
		}
	}
	return err
}

func (m *MoLingServer) loadService(srv abstract.Service) error {

	// Add resources
	for r, rhf := range srv.Resources() {
		m.server.AddResource(r, rhf)
	}

	// Add Resource Templates
	for rt, rthf := range srv.ResourceTemplates() {
		m.server.AddResourceTemplate(rt, rthf)
	}

	// Add Tools
	m.server.AddTools(srv.Tools()...)

	// Add Notification Handlers
	for n, nhf := range srv.NotificationHandlers() {
		m.server.AddNotificationHandler(n, nhf)
	}

	// Add Prompts
	for _, pe := range srv.Prompts() {
		// Add Prompt
		m.server.AddPrompt(pe.Prompt(), pe.Handler())
	}
	return nil
}

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, 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)
	}
	m.logger.Info().Msg("Starting STDIO server")
	return server.ServeStdio(m.server, server.WithErrorLogger(mLogger))
}

```

--------------------------------------------------------------------------------
/cli/cobrautl/help.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package cobrautl

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

var (
	commandUsageTemplate *template.Template
	templFuncs           = template.FuncMap{
		"descToLines": func(s string) []string {
			// trim leading/trailing whitespace and split into slice of lines
			return strings.Split(strings.Trim(s, "\n\t "), "\n")
		},
		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
			parts := []string{cmd.Name()}
			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
				cmd = cmd.Parent()
				parts = append([]string{cmd.Name()}, parts...)
			}
			return strings.Join(parts, " ")
		},
	}
)

func init() {
	commandUsage := `
{{ $cmd := .Cmd }}\
{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
NAME:
{{ if not .Cmd.HasParent }}\
{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
{{else}}\
{{printf "\t%s - %s" $cmdname .Cmd.Short}}
{{end}}\

USAGE:
{{printf "\t%s" .Cmd.UseLine}}
{{ if not .Cmd.HasParent }}\

VERSION:
{{printf "\t%s" .Version}}
{{end}}\
{{if .Cmd.HasSubCommands}}\

COMMANDS:
{{range .SubCommands}}\
{{ $cmdname := cmdName . $cmd }}\
{{ if .Runnable }}\
{{printf "\t%s\t%s" $cmdname .Short}}
{{end}}\
{{end}}\
{{end}}\
{{ if .Cmd.Long }}\

DESCRIPTION:
{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
{{end}}\
{{end}}\
{{if .Cmd.HasLocalFlags}}\

OPTIONS:
{{.LocalFlags}}\
{{end}}\
{{if .Cmd.HasInheritedFlags}}\

GLOBAL OPTIONS:
{{.GlobalFlags}}\
{{end}}
`[1:]

	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
}

func molingFlagUsages(flagSet *pflag.FlagSet) string {
	x := new(bytes.Buffer)

	flagSet.VisitAll(func(flag *pflag.Flag) {
		if len(flag.Deprecated) > 0 {
			return
		}
		var format string
		if len(flag.Shorthand) > 0 {
			format = "  -%s, --%s"
		} else {
			format = "   %s   --%s"
		}
		if len(flag.NoOptDefVal) > 0 {
			format = format + "["
		}
		if flag.Value.Type() == "string" {
			// put quotes on the value
			format = format + "=%q"
		} else {
			format = format + "=%s"
		}
		if len(flag.NoOptDefVal) > 0 {
			format = format + "]"
		}
		format = format + "\t%s\n"
		shorthand := flag.Shorthand
		fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
	})

	return x.String()
}

func getSubCommands(cmd *cobra.Command) []*cobra.Command {
	var subCommands []*cobra.Command
	for _, subCmd := range cmd.Commands() {
		subCommands = append(subCommands, subCmd)
		subCommands = append(subCommands, getSubCommands(subCmd)...)
	}
	return subCommands
}

func UsageFunc(cmd *cobra.Command, version string) error {
	subCommands := getSubCommands(cmd)
	tabOut := getTabOutWithWriter(os.Stdout)
	err := commandUsageTemplate.Execute(tabOut, struct {
		Cmd         *cobra.Command
		LocalFlags  string
		GlobalFlags string
		SubCommands []*cobra.Command
		Version     string
	}{
		cmd,
		molingFlagUsages(cmd.LocalFlags()),
		molingFlagUsages(cmd.InheritedFlags()),
		subCommands,
		version,
	})
	if err != nil {
		return err
	}
	err = tabOut.Flush()
	return err
}

func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
	aTabOut := new(tabwriter.Writer)
	aTabOut.Init(writer, 0, 8, 1, '\t', 0)
	return aTabOut
}

```

--------------------------------------------------------------------------------
/pkg/services/filesystem/file_system_config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package filesystem

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

const (
	// FileSystemPromptDefault is the default prompt for the file system.
	FileSystemPromptDefault = `
You are a powerful local filesystem management assistant capable of performing various file operations and management tasks. Your capabilities include:

1. **File Browsing**: Navigate to specified directories to load lists of files and folders.

2. **File Operations**:
   - Create new files or folders
   - Delete specified files or folders
   - Copy and move files and folders
   - Rename files or folders

3. **File Content Operations**:
   - Read the contents of text files and return them
   - Write text to specified files
   - Append content to existing files

4. **File Information Retrieval**:
   - Retrieve properties of files or folders (e.g., size, creation date, modification date)
   - Check if files or folders exist

5. **Search Functionality**:
   - Search for files in specified directories, supporting wildcard matching
   - Filter search results by file type or modification date

For all actions, please provide clear instructions, including:
- The specific action you want to perform
- Required parameters (directory paths, filenames, content, etc.)
- Any optional parameters (e.g., new filenames, search patterns, etc.)
- Relevant expected outcomes

You should confirm actions before execution when dealing with sensitive operations or destructive commands. Report back with clear status updates, success/failure indicators, and any relevant output or results.
`
)

var (
	allowedDirsDefault = os.TempDir()
)

// FileSystemConfig represents the configuration for the file system.
type FileSystemConfig struct {
	PromptFile  string `json:"prompt_file"` // PromptFile is the prompt file for the file system.
	prompt      string
	AllowedDir  string `json:"allowed_dir"` // AllowedDirs is a list of allowed directories. split by comma. e.g. /tmp,/var/tmp
	allowedDirs []string
	CachePath   string `json:"cache_path"` // CachePath is the root path for the file system.
}

// NewFileSystemConfig creates a new FileSystemConfig with the given allowed directories.
func NewFileSystemConfig(path string) *FileSystemConfig {
	paths := strings.Split(path, ",")
	path = ""
	if strings.TrimSpace(path) == "" {
		path = allowedDirsDefault
		paths = []string{allowedDirsDefault}
	}

	return &FileSystemConfig{
		AllowedDir:  path,
		CachePath:   path,
		allowedDirs: paths,
	}
}

// Check validates the allowed directories in the FileSystemConfig.
func (fc *FileSystemConfig) Check() error {
	fc.prompt = FileSystemPromptDefault
	normalized := make([]string, 0, len(fc.allowedDirs))
	for _, dir := range fc.allowedDirs {
		abs, err := filepath.Abs(strings.TrimSpace(dir))
		if err != nil {
			return fmt.Errorf("failed to resolve path %s: %w", dir, err)
		}
		info, err := os.Stat(abs)
		if err != nil {
			return fmt.Errorf("failed to access directory %s: %w", abs, err)
		}
		if !info.IsDir() {
			return fmt.Errorf("path is not a directory: %s", abs)
		}

		normalized = append(normalized, filepath.Clean(abs)+string(filepath.Separator))
	}
	fc.allowedDirs = normalized

	if fc.PromptFile != "" {
		read, err := os.ReadFile(fc.PromptFile)
		if err != nil {
			return fmt.Errorf("failed to read prompt file:%s, error: %w", fc.PromptFile, err)
		}
		fc.prompt = string(read)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/pkg/services/command/command_config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package command

import (
	"fmt"
	"os"
	"strings"
)

const (
	CommandPromptDefault = `
You are a powerful terminal command assistant capable of executing various command-line on %s operations and management tasks. Your capabilities include:

1. **File and Directory Management**:
    - List files and subdirectories in a directory
    - Create new files or directories
    - Delete specified files or directories
    - Copy and move files and directories
    - Rename files or directories

2. **File Content Operations**:
    - View the contents of text files
    - Edit file contents
    - Redirect output to a file
    - Search file contents

3. **System Information Retrieval**:
    - Retrieve system information (e.g., CPU usage, memory usage, etc.)
    - View the current user and their permissions
    - Check the current working directory

4. **Network Operations**:
    - Check network connection status (e.g., using the ping command)
    - Query domain information (e.g., using the whois command)
    - Manage network services (e.g., start, stop, and restart services)

5. **Process Management**:
    - List currently running processes
    - Terminate specified processes
    - Adjust process priorities

Before executing any actions, please provide clear instructions, including:
- The specific command you want to execute
- Required parameters (file paths, directory names, etc.)
- Any optional parameters (e.g., modification options, output formats, etc.)
- Relevant expected results or output

When dealing with sensitive operations or destructive commands, please confirm before execution. Report back with clear status updates, success/failure indicators, and any relevant output or results.
`
)

// CommandConfig represents the configuration for allowed commands.
type CommandConfig struct {
	PromptFile      string `json:"prompt_file"` // PromptFile is the prompt file for the command.
	prompt          string
	AllowedCommand  string `json:"allowed_command"` // AllowedCommand is a list of allowed command. split by comma. e.g. ls,cat,echo
	allowedCommands []string
}

var (
	allowedCmdDefault = []string{
		"ls", "cat", "echo", "pwd", "head", "tail", "grep", "find", "stat", "df",
		"du", "free", "top", "ps", "uptime", "who", "w", "last", "uname", "hostname",
		"ifconfig", "netstat", "ping", "traceroute", "route", "ip", "ss", "lsof", "vmstat",
		"iostat", "mpstat", "sar", "uptime", "cut", "sort", "uniq", "wc", "awk", "sed",
		"diff", "cmp", "comm", "file", "basename", "dirname", "chmod", "chown", "curl",
		"nslookup", "dig", "host", "ssh", "scp", "sftp", "ftp", "wget", "tar", "gzip",
		"scutil", "networksetup, git", "cd",
	}
)

// NewCommandConfig creates a new CommandConfig with the given allowed commands.
func NewCommandConfig() *CommandConfig {
	return &CommandConfig{
		allowedCommands: allowedCmdDefault,
		AllowedCommand:  strings.Join(allowedCmdDefault, ","),
	}
}

// Check validates the allowed commands in the CommandConfig.
func (cc *CommandConfig) Check() error {
	cc.prompt = CommandPromptDefault
	var cnt int
	cnt = len(cc.allowedCommands)

	// Check if any command is empty
	for _, cmd := range cc.allowedCommands {
		if cmd == "" {
			cnt -= 1
		}
	}

	if cnt <= 0 {
		return fmt.Errorf("no allowed commands specified")
	}
	if cc.PromptFile != "" {
		read, err := os.ReadFile(cc.PromptFile)
		if err != nil {
			return fmt.Errorf("failed to read prompt file:%s, error: %w", cc.PromptFile, err)
		}
		cc.prompt = string(read)
	}
	return nil
}

```

--------------------------------------------------------------------------------
/cli/cmd/config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package cmd

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/rs/zerolog"
	"github.com/spf13/cobra"

	"github.com/gojue/moling/pkg/comm"
	"github.com/gojue/moling/pkg/services"
)

var configCmd = &cobra.Command{
	Use:   "config",
	Short: "Show the configuration of the current service list",
	Long: `Show the configuration of the current service list. You can refer to the configuration file to modify the configuration.
`,
	RunE: ConfigCommandFunc,
}

var (
	initial bool
)

// ConfigCommandFunc executes the "config" command.
func ConfigCommandFunc(command *cobra.Command, args []string) error {
	var err error
	logger := initLogger(mlConfig.BasePath)
	consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
	multi := zerolog.MultiLevelWriter(consoleWriter, logger)
	logger = zerolog.New(multi).With().Timestamp().Logger()
	mlConfig.SetLogger(logger)
	logger.Info().Msg("Start to show config")
	ctx := context.WithValue(context.Background(), comm.MoLingConfigKey, mlConfig)
	ctx = context.WithValue(ctx, comm.MoLingLoggerKey, logger)

	// 当前配置文件检测
	hasConfig := false
	var nowConfig []byte
	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)
		if err != nil {
			return fmt.Errorf("error unmarshaling JSON: %w, payload:%s", err, string(nowConfig))
		}
	}

	bf := bytes.Buffer{}
	bf.WriteString("\n{\n")

	// 写入GlobalConfig
	mlConfigJSON, err := json.Marshal(mlConfig)
	if err != nil {
		return fmt.Errorf("error marshaling GlobalConfig: %w", err)
	}
	bf.WriteString("\t\"MoLingConfig\":\n")
	bf.WriteString(fmt.Sprintf("\t%s,\n", mlConfigJSON))
	first := true
	for srvName, nsv := range services.ServiceList() {
		// 获取服务对应的配置
		cfg, ok := nowConfigJSON[string(srvName)].(map[string]any)

		srv, err := nsv(ctx)
		if err != nil {
			return err
		}
		// srv Loadconfig
		if ok {
			err = srv.LoadConfig(cfg)
			if err != nil {
				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")
		}
		// srv Init
		err = srv.Init()
		if err != nil {
			return fmt.Errorf("error initializing service %s: %w", srv.Name(), err)
		}
		if !first {
			bf.WriteString(",\n")
		}
		bf.WriteString(fmt.Sprintf("\t\"%s\":\n", srv.Name()))
		bf.WriteString(fmt.Sprintf("\t%s\n", srv.Config()))
		first = false
	}
	bf.WriteString("}\n")
	// 解析原始 JSON 字符串
	var data any
	err = json.Unmarshal(bf.Bytes(), &data)
	if err != nil {
		return fmt.Errorf("error unmarshaling JSON: %w, payload:%s", err, bf.String())
	}

	// 格式化 JSON
	formattedJSON, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		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)
		if err != nil {
			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)
	}
	return nil
}

func init() {
	configCmd.PersistentFlags().BoolVar(&initial, "init", false, fmt.Sprintf("Save configuration to %s", filepath.Join(mlConfig.BasePath, mlConfig.ConfigFile)))
	rootCmd.AddCommand(configCmd)
}

```

--------------------------------------------------------------------------------
/pkg/services/browser/browser_config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

// Package services provides a set of services for the MoLing application.
package browser

import (
	"fmt"
	"os"
	"path/filepath"
)

const BrowserPromptDefault = `
You are an AI-powered browser automation assistant capable of performing a wide range of web interactions and debugging tasks. Your capabilities include:

1. **Navigation**: Navigate to any specified URL to load web pages.

2. **Screenshot Capture**: Take full-page screenshots or capture specific elements using CSS selectors, with customizable dimensions (default: 1700x1100 pixels).

3. **Element Interaction**:
   - Click on elements identified by CSS selectors
   - Hover over specified elements
   - Fill input fields with provided values
   - Select options in dropdown menus

4. **JavaScript Execution**:
   - Run arbitrary JavaScript code in the browser context
   - Evaluate scripts and return results

5. **Debugging Tools**:
   - Enable/disable JavaScript debugging mode
   - Set breakpoints at specific script locations (URL + line number + optional column/condition)
   - Remove existing breakpoints by ID
   - Pause and resume script execution
   - Retrieve current call stack when paused

For all actions requiring element selection, you must use precise CSS selectors. When capturing screenshots, you can specify either the entire page or target specific elements. For debugging operations, you can precisely control execution flow and inspect runtime behavior.

Please provide clear instructions including:
- The specific action you want performed
- Required parameters (URLs, selectors, values, etc.)
- Any optional parameters (dimensions, conditions, etc.)
- Expected outcomes where relevant

You should confirm actions before execution when dealing with sensitive operations or destructive commands. Report back with clear status updates, success/failure indicators, and any relevant output or captured data.
`

type BrowserConfig struct {
	PromptFile           string `json:"prompt_file"` // PromptFile is the prompt file for the browser.
	prompt               string
	Headless             bool   `json:"headless"`
	Timeout              int    `json:"timeout"`
	Proxy                string `json:"proxy"`
	UserAgent            string `json:"user_agent"`
	DefaultLanguage      string `json:"default_language"`
	URLTimeout           int    `json:"url_timeout"`            // URLTimeout is the timeout for loading a URL. time.Second
	SelectorQueryTimeout int    `json:"selector_query_timeout"` // SelectorQueryTimeout is the timeout for CSS selector queries. time.Second
	DataPath             string `json:"data_path"`              // DataPath is the path to the data directory.
	BrowserDataPath      string `json:"browser_data_path"`      // BrowserDataPath is the path to the browser data directory.
}

func (cfg *BrowserConfig) Check() error {
	cfg.prompt = BrowserPromptDefault
	if cfg.Timeout <= 0 {
		return fmt.Errorf("timeout must be greater than 0")
	}
	if cfg.URLTimeout <= 0 {
		return fmt.Errorf("URL timeout must be greater than 0")
	}
	if cfg.SelectorQueryTimeout <= 0 {
		return fmt.Errorf("selector Query timeout must be greater than 0")
	}
	if cfg.PromptFile != "" {
		read, err := os.ReadFile(cfg.PromptFile)
		if err != nil {
			return fmt.Errorf("failed to read prompt file:%s, error: %w", cfg.PromptFile, err)
		}
		cfg.prompt = string(read)
	}
	return nil
}

// NewBrowserConfig creates a new BrowserConfig with default values.
func NewBrowserConfig() *BrowserConfig {
	return &BrowserConfig{
		Headless:             false,
		Timeout:              30,
		URLTimeout:           10,
		SelectorQueryTimeout: 10,
		UserAgent:            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
		DefaultLanguage:      "en-US",
		DataPath:             filepath.Join(os.TempDir(), ".moling", "data"),
	}
}

```

--------------------------------------------------------------------------------
/pkg/services/abstract/mlservice_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package abstract

import (
	"context"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
)

func TestMLService_AddResource(t *testing.T) {
	service := &MLService{}
	err := service.InitResources()
	if err != nil {
		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) {
		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				Text:     "text",
				URI:      "uri",
				MIMEType: "text/plain",
			},
		}, nil
	}

	service.AddResource(resource, handler)

	if len(service.resources) != 1 {
		t.Errorf("Expected 1 resource, got %d", len(service.resources))
	}
	if service.resources[resource] == nil {
		t.Errorf("Handler for resource not found")
	}
}

func TestMLService_AddResourceTemplate(t *testing.T) {
	service := &MLService{}
	err := service.InitResources()
	if err != nil {
		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) {
		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				Text:     "text",
				URI:      "uri",
				MIMEType: "text/plain",
			},
		}, nil
	}

	service.AddResourceTemplate(template, handler)

	if len(service.resourcesTemplates) != 1 {
		t.Errorf("Expected 1 resource template, got %d", len(service.resourcesTemplates))
	}
	if service.resourcesTemplates[template] == nil {
		t.Errorf("Handler for resource template not found")
	}
}

func TestMLService_AddPrompt(t *testing.T) {
	service := &MLService{}
	err := service.InitResources()
	if err != nil {
		t.Fatalf("Failed to initialize MLService: %s", err.Error())
	}
	prompt := "testPrompt"
	handler := func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
		pms := make([]mcp.PromptMessage, 0)
		pms = append(pms, mcp.PromptMessage{
			Role: mcp.RoleUser,
			Content: mcp.TextContent{
				Type: "text",
				Text: "Prompt response",
			},
		})
		return &mcp.GetPromptResult{
			Description: "prompt description",
			Messages:    pms,
		}, nil
	}
	pe := PromptEntry{
		PromptVar:   mcp.Prompt{Name: "testPrompt"},
		HandlerFunc: handler,
	}
	service.AddPrompt(pe)

	if len(service.prompts) != 1 {
		t.Errorf("Expected 1 prompt, got %d", len(service.prompts))
	}
	for _, p := range service.prompts {
		if p.PromptVar.Name != prompt {
			t.Errorf("Expected prompt name %s, got %s", prompt, p.PromptVar.Name)
		}
	}
}

func TestMLService_AddTool(t *testing.T) {
	service := &MLService{}
	err := service.InitResources()
	if err != nil {
		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) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Prompt response",
				},
			},
		}, nil
	}

	service.AddTool(tool, handler)

	if len(service.tools) != 1 {
		t.Errorf("Expected 1 tool, got %d", len(service.tools))
	}

	// After
	if service.tools[0].Tool.Name != tool.Name {
		t.Errorf("Tool not added correctly")
	}
}

func TestMLService_AddNotificationHandler(t *testing.T) {
	service := &MLService{}
	err := service.InitResources()
	if err != nil {
		t.Fatalf("Failed to initialize MLService: %s", err.Error())
	}
	name := "testHandler"
	handler := func(ctx context.Context, n mcp.JSONRPCNotification) {
		t.Logf("Received notification: %s", n.Method)
	}

	service.AddNotificationHandler(name, handler)

	if len(service.notificationHandlers) != 1 {
		t.Errorf("Expected 1 notification handler, got %d", len(service.notificationHandlers))
	}
	if service.notificationHandlers[name] == nil {
		t.Errorf("Handler for notification not found")
	}
}

```

--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------

```go
/*
 *
 *  Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Repository: https://github.com/gojue/moling
 *
 */

package client

import (
	"encoding/json"
	"errors"
	"os"

	"github.com/rs/zerolog"
)

var (
	// ClineConfigPath is the path to the Cline config file.
	clientLists = make(map[string]string, 3)
)

const MCPServersKey = "mcpServers"

// MCPServerConfig represents the configuration for the MCP Client.
type MCPServerConfig struct {
	Description string   `json:"description"`       // Description of the MCP Server
	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
	TimeOut     uint16   `json:"timeout,omitempty"` // Timeout for the MCP Server, default is 300 seconds
	ServerName  string
}

// NewMCPServerConfig creates a new MCPServerConfig instance.
func NewMCPServerConfig(description string, command string, srvName string) MCPServerConfig {
	return MCPServerConfig{
		Description: description,
		IsActive:    true,
		Command:     command,
		Args:        []string{"-m", "Browser"},
		BaseURL:     "",
		ServerName:  srvName,
		TimeOut:     300,
	}
}

// Manager manages the configuration of different clients.
type Manager struct {
	logger    zerolog.Logger
	clients   map[string]string
	mcpConfig MCPServerConfig
}

// NewManager creates a new ClientManager instance.
func NewManager(lger zerolog.Logger, mcpConfig MCPServerConfig) (cm *Manager) {
	cm = &Manager{
		clients:   make(map[string]string, 3),
		logger:    lger,
		mcpConfig: mcpConfig,
	}
	cm.clients = clientLists
	return cm
}

// ListClient lists all the clients and checks if they exist.
func (c *Manager) ListClient() {
	for name, path := range c.clients {
		c.logger.Debug().Msgf("Client %s: %s", name, path)
		if !c.checkExist(path) {
			// path not exists
			c.logger.Info().Str("Client Name", name).Bool("exist", false).Msg("Client is not exist")
		} else {
			c.logger.Info().Str("Client Name", name).Bool("exist", true).Msg("Client is exist")
		}
	}
	return
}

// SetupConfig sets up the configuration for the clients.
func (c *Manager) SetupConfig() {
	for name, path := range c.clients {
		c.logger.Debug().Msgf("Client %s: %s", name, path)
		if !c.checkExist(path) {
			continue
		}
		// read config file
		file, err := os.ReadFile(path)
		if err != nil {
			c.logger.Error().Str("Client Name", name).Msgf("Failed to open config file %s: %s", path, err)
			continue
		}
		c.logger.Debug().Str("Client Name", name).Str("config", string(file)).Send()
		b, err := c.appendConfig(c.mcpConfig.ServerName, file)
		if err != nil {
			c.logger.Error().Str("Client Name", name).Msgf("Failed to append config file %s: %s", path, err)
			continue
		}
		c.logger.Debug().Str("Client Name", name).Str("newConfig", string(b)).Send()
		// write config file
		err = os.WriteFile(path, b, 0644)
		if err != nil {
			c.logger.Error().Str("Client Name", name).Msgf("Failed to write config file %s: %s", path, err)
			continue
		}
		c.logger.Info().Str("Client Name", name).Msgf("Successfully added config to %s", path)
	}
}

// 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]any
	var jsonBytes []byte
	err = json.Unmarshal(payload, &jsonMap)
	if err != nil {
		return nil, err
	}
	jsonMcpServer, ok := jsonMap[MCPServersKey].(map[string]any)
	if !ok {
		return nil, errors.New("MCPServersKey not found in JSON")
	}
	jsonMcpServer[name] = c.mcpConfig
	jsonMap[MCPServersKey] = jsonMcpServer
	jsonBytes, err = json.MarshalIndent(jsonMap, "", "  ")
	if err != nil {
		return nil, err
	}
	return jsonBytes, nil
}

// checkExist checks if the file at the given path exists.
func (c *Manager) checkExist(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			c.logger.Debug().Msgf("Client config file %s does not exist", path)
			return false
		}
		c.logger.Info().Msgf("check file failed, error:%s", err.Error())
		return false
	}
	return true
}

```

--------------------------------------------------------------------------------
/pkg/services/abstract/mlservice.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

package abstract

import (
	"context"
	"sync"

	"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 {
	PromptVar   mcp.Prompt
	HandlerFunc server.PromptHandlerFunc
}

func (pe *PromptEntry) Prompt() mcp.Prompt {
	return pe.PromptVar
}

func (pe *PromptEntry) Handler() server.PromptHandlerFunc {
	return pe.HandlerFunc
}

// NewMLService creates a new MLService with the given context and logger.
func NewMLService(ctx context.Context, logger zerolog.Logger, cfg *config.MoLingConfig) MLService {
	return MLService{
		Context:  ctx,
		Logger:   logger,
		mlConfig: cfg,
	}
}

// MLService implements the Service interface and provides methods to manage resources, templates, prompts, tools, and notification handlers.
type MLService struct {
	Context context.Context
	Logger  zerolog.Logger // The logger for the service

	lock                 *sync.Mutex
	resources            map[mcp.Resource]server.ResourceHandlerFunc
	resourcesTemplates   map[mcp.ResourceTemplate]server.ResourceTemplateHandlerFunc
	prompts              []PromptEntry
	tools                []server.ServerTool
	notificationHandlers map[string]server.NotificationHandlerFunc
	mlConfig             *config.MoLingConfig // The configuration for the service
}

// InitResources initializes the MLService with empty maps and a mutex.
func (mls *MLService) InitResources() error {
	mls.lock = &sync.Mutex{}
	mls.resources = make(map[mcp.Resource]server.ResourceHandlerFunc)
	mls.resourcesTemplates = make(map[mcp.ResourceTemplate]server.ResourceTemplateHandlerFunc)
	mls.prompts = make([]PromptEntry, 0)
	mls.notificationHandlers = make(map[string]server.NotificationHandlerFunc)
	mls.tools = []server.ServerTool{}
	return nil
}

// Ctx returns the context of the MLService.
func (mls *MLService) Ctx() context.Context {
	return mls.Context
}

// AddResource adds a resource and its handler function to the service.
func (mls *MLService) AddResource(rs mcp.Resource, hr server.ResourceHandlerFunc) {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	mls.resources[rs] = hr
}

// AddResourceTemplate adds a resource template and its handler function to the service.
func (mls *MLService) AddResourceTemplate(rt mcp.ResourceTemplate, hr server.ResourceTemplateHandlerFunc) {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	mls.resourcesTemplates[rt] = hr
}

// AddPrompt adds a prompt and its handler function to the service.
func (mls *MLService) AddPrompt(pe PromptEntry) {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	mls.prompts = append(mls.prompts, pe)
}

// AddTool adds a tool and its handler function to the service.
func (mls *MLService) AddTool(tool mcp.Tool, handler server.ToolHandlerFunc) {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	mls.tools = append(mls.tools, server.ServerTool{Tool: tool, Handler: handler})
}

// AddNotificationHandler adds a notification handler to the service.
func (mls *MLService) AddNotificationHandler(name string, handler server.NotificationHandlerFunc) {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	mls.notificationHandlers[name] = handler
}

// Resources returns the map of resources and their handler functions.
func (mls *MLService) Resources() map[mcp.Resource]server.ResourceHandlerFunc {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	return mls.resources
}

// ResourceTemplates returns the map of resource templates and their handler functions.
func (mls *MLService) ResourceTemplates() map[mcp.ResourceTemplate]server.ResourceTemplateHandlerFunc {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	return mls.resourcesTemplates
}

// Prompts returns the map of prompts and their handler functions.
func (mls *MLService) Prompts() []PromptEntry {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	return mls.prompts
}

// Tools returns the slice of server tools.
func (mls *MLService) Tools() []server.ServerTool {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	return mls.tools
}

// NotificationHandlers returns the map of notification handlers.
func (mls *MLService) NotificationHandlers() map[string]server.NotificationHandlerFunc {
	mls.lock.Lock()
	defer mls.lock.Unlock()
	return mls.notificationHandlers
}

// MlConfig returns the configuration of the MoLing service.
func (mls *MLService) MlConfig() *config.MoLingConfig {
	return mls.mlConfig
}

// Config returns the configuration of the service as a string.
func (mls *MLService) Config() string {
	panic("not implemented yet") // TODO: Implement
}

// Name returns the name of the service.
func (mls *MLService) Name() string {
	panic("not implemented yet") // TODO: Implement
}

// LoadConfig loads the configuration for the service from a map.
func (mls *MLService) LoadConfig(jsonData map[string]any) error {
	//panic("not implemented yet") // TODO: Implement
	err := utils.MergeJSONToStruct(mls.mlConfig, jsonData)
	if err != nil {
		return err
	}
	return mls.mlConfig.Check()
}

```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# v0.4.0 (2025-05-31)

## What's Changed

**Full Changelog**: https://github.com/gojue/moling/compare/v0.3.2...v0.4.0
<hr>

# v0.3.2 (2025-05-20)

## What's Changed

* feat: update command arguments and add new client configurations for Trae and Trae CN by @cfc4n
  in https://github.com/gojue/moling/pull/35
* feat: update command arguments and improve logging for service initialization by @cfc4n
  in https://github.com/gojue/moling/pull/37


**Full Changelog**: https://github.com/gojue/moling/compare/v0.3.1...v0.3.2
<hr>

# v0.3.1 (2025-04-22)

## What's Changed

* fix: handle parent process exit to ensure MCP Server shutdown by @cfc4n
  in [#33](https://github.com/gojue/moling/pull/33)
* feat: update default prompts for browser, command, and filesystem modules by @cfc4n
  in [#34](https://github.com/gojue/moling/pull/34)

**Full Changelog**: https://github.com/gojue/moling/compare/v0.3.0...v0.3.1
<hr>

# v0.3.0 (2025-04-18)

## What's Changed

* feat: add client configuration paths for Trae and update README by @cfc4n
  in [#27](https://github.com/gojue/moling/pull/27)
* feat: support Cursor IDE (MCP Client) by @cfc4n in [#28](https://github.com/gojue/moling/pull/28)
* feat: Improves code consistency, adds PID management, and enhances user prompts by @cfc4n
  in [#29](https://github.com/gojue/moling/pull/29)

**Full Changelog**: https://github.com/gojue/moling/compare/v0.2.0...v0.3.0
<hr>

# v0.2.0 (2025-04-13)

## What's Changed

* feat: add support for advanced logging configuration by @cfc4n in [#25](https://github.com/gojue/moling/pull/25)
* fix: resolve issue with incorrect breakpoint handling by @cfc4n in [#26](https://github.com/gojue/moling/pull/26)
* docs: update README with debugging examples by @cfc4n in [#27](https://github.com/gojue/moling/pull/27)

**Full Changelog**: https://github.com/gojue/moling/compare/v0.1.1...v0.2.0
<hr>

# v0.1.1 (2025-04-08)
## What's Changed

* feat: update CLI descriptions for clarity and accuracy by @cfc4n in https://github.com/gojue/moling/pull/17
* refactor: change log levels from Info to Debug and Warn for improved … by @cfc4n
  in https://github.com/gojue/moling/pull/19
* fix: update README files to replace image links with direct URLs by @cfc4n in https://github.com/gojue/moling/pull/20
* feat: add MCP Client configuration to install script by @cfc4n in https://github.com/gojue/moling/pull/22

**Full Changelog**: https://github.com/gojue/moling/compare/v0.1.0...v0.1.1
<hr>

# v0.1.0 (2025-04-04)

## What's Changed

* Improve builder by @cfc4n in https://github.com/gojue/moling/pull/16

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.8...v0.1.0
<hr>

# v0.0.8 (2025-04-04)

## What's Changed

* docs: add Japanese README file by @eltociear in https://github.com/gojue/moling/pull/13
* feat: implement log file rotation and enhance logging configuration by @cfc4n
  in https://github.com/gojue/moling/pull/14
* chore: update dependencies for chromedp and mcp-go to latest versions by @cfc4n
  in https://github.com/gojue/moling/pull/15

## New Contributors

* @eltociear made their first contribution in https://github.com/gojue/moling/pull/13

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.7...v0.0.8
<hr>

# v0.0.7 (2025-03-31)

## What's Changed

* feat: add logging enhancements and listen address flag for SSE mode by @cfc4n
  in https://github.com/gojue/moling/pull/11
* feat: add client command for automated MCP Server configuration management by @cfc4n
  in https://github.com/gojue/moling/pull/12

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.6...v0.0.7
<hr>

# v0.0.6 (2025-03-30)

## What's Changed

* feat: enhance service initialization and configuration loading by @cfc4n in https://github.com/gojue/moling/pull/9
* feat: update installation script and README for improved user experie… by @cfc4n
  in https://github.com/gojue/moling/pull/10

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.5...v0.0.6
<hr>

# v0.0.5 (2025-03-29)
## What's Changed

* fix #4 invalid data path by @cfc4n in https://github.com/gojue/moling/pull/5
* feat: add automated release notes generation in release workflow by @cfc4n in https://github.com/gojue/moling/pull/6
* Improve logging & error handling, rename configs, simplify service logic by @cfc4n
  in https://github.com/gojue/moling/pull/8

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.3...v0.0.5
<hr>

# v0.0.3 (2025-03-28)
## What's Changed

* fix: build failed with GOOS=windows by @cfc4n in https://github.com/gojue/moling/pull/1
* feat: refactor server initialization and directory creation logic by @cfc4n in https://github.com/gojue/moling/pull/2
* feat: enhance configuration management and logging in the MoLing server by @cfc4n
  in https://github.com/gojue/moling/pull/3

## New Contributors

* @cfc4n made their first contribution in https://github.com/gojue/moling/pull/1

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.2...v0.0.3
<hr>

# v0.0.2 (2025-03-27)

## What's Changed
- Enhanced command validation with global configuration and support for piped commands
- Added installation scripts for Windows/Linux and updated documentation
- Implemented directory creation checks for base path and subdirectories
- Corrected spelling of "MoLing" and "macOS" in documentation
- Updated filename format for saved images
- Refactored configuration management to use BasePath and improved browser initialization
- Added listen address configuration and improved server initialization
- Enhanced configuration management and logging
- Added configuration management and service registration
- Added browser service with logging
- Updated GitHub links in README for accurate repository references

**Full Changelog**: https://github.com/gojue/moling/compare/v0.0.1...v0.0.2
<hr>

# v0.0.1 (2025-03-23)

## What's Changed
1. support `filesystem` service
2. support `command line` service
3. add GitHub action workflow


```

--------------------------------------------------------------------------------
/pkg/services/command/command.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

// Package services Description: This file contains the implementation of the CommandServer interface for macOS and  Linux.
package command

import (
	"context"
	"encoding/json"
	"fmt"
	"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"
)

var (
	// ErrCommandNotFound is returned when the command is not found.
	ErrCommandNotFound = fmt.Errorf("command not found")
	// ErrCommandNotAllowed is returned when the command is not allowed.
	ErrCommandNotAllowed = fmt.Errorf("command not allowed")
)

const (
	CommandServerName comm.MoLingServerType = "Command"
)

// CommandServer implements the Service interface and provides methods to execute named commands.
type CommandServer struct {
	abstract.MLService
	config    *CommandConfig
	osName    string
	osVersion string
}

// NewCommandServer creates a new CommandServer with the given allowed commands.
func NewCommandServer(ctx context.Context) (abstract.Service, error) {
	var err error
	cc := NewCommandConfig()
	gConf, ok := ctx.Value(comm.MoLingConfigKey).(*config.MoLingConfig)
	if !ok {
		return nil, fmt.Errorf("CommandServer: invalid config type")
	}

	lger, ok := ctx.Value(comm.MoLingLoggerKey).(zerolog.Logger)
	if !ok {
		return nil, fmt.Errorf("CommandServer: invalid logger type")
	}

	loggerNameHook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
		e.Str("Service", string(CommandServerName))
	})

	cs := &CommandServer{
		MLService: abstract.NewMLService(ctx, lger.Hook(loggerNameHook), gConf),
		config:    cc,
	}

	err = cs.InitResources()
	if err != nil {
		return nil, err
	}

	return cs, nil
}

func (cs *CommandServer) Init() error {
	var err error
	pe := abstract.PromptEntry{
		PromptVar: mcp.Prompt{
			Name:        "command_prompt",
			Description: "get command prompt",
			//Arguments:   make([]mcp.PromptArgument, 0),
		},
		HandlerFunc: cs.handlePrompt,
	}
	cs.AddPrompt(pe)
	cs.AddTool(mcp.NewTool(
		"execute_command",
		mcp.WithDescription("Execute a named command.Only support command execution on macOS and will strictly follow safety guidelines, ensuring that commands are safe and secure"),
		mcp.WithString("command",
			mcp.Description("The command to execute"),
			mcp.Required(),
		),
	), cs.handleExecuteCommand)
	return err
}

func (cs *CommandServer) handlePrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	return &mcp.GetPromptResult{
		Description: "",
		Messages: []mcp.PromptMessage{
			{
				Role: mcp.RoleUser,
				Content: mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf(cs.config.prompt, cs.MlConfig().SystemInfo),
				},
			},
		},
	}, nil
}

// handleExecuteCommand handles the execution of a named command.
func (cs *CommandServer) handleExecuteCommand(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	command, ok := args["command"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Errorf("command must be a string").Error()), nil
	}

	// Check if the command is allowed
	if !cs.isAllowedCommand(command) {
		cs.Logger.Err(ErrCommandNotAllowed).Str("command", command).Msgf("If you want to allow this command, add it to %s", filepath.Join(cs.MlConfig().BasePath, "config", cs.MlConfig().ConfigFile))
		return mcp.NewToolResultError(fmt.Sprintf("Error: Command '%s' is not allowed", command)), nil
	}

	// Execute the command
	output, err := ExecCommand(command)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("Error executing command: %v", err)), nil
	}

	return mcp.NewToolResultText(output), nil
}

// isAllowedCommand checks if the command is allowed based on the configuration.
func (cs *CommandServer) isAllowedCommand(command string) bool {
	// 检查命令是否在允许的列表中
	for _, allowed := range cs.config.allowedCommands {
		if strings.HasPrefix(command, allowed) {
			return true
		}
	}

	// 如果命令包含管道符,进一步检查每个子命令
	if strings.Contains(command, "|") {
		parts := strings.Split(command, "|")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if !cs.isAllowedCommand(part) {
				return false
			}
		}
		return true
	}

	if strings.Contains(command, "&") {
		parts := strings.Split(command, "&")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if !cs.isAllowedCommand(part) {
				return false
			}
		}
		return true
	}

	return false
}

// Config returns the configuration of the service as a string.
func (cs *CommandServer) Config() string {
	cs.config.AllowedCommand = strings.Join(cs.config.allowedCommands, ",")
	cfg, err := json.Marshal(cs.config)
	if err != nil {
		cs.Logger.Err(err).Msg("failed to marshal config")
		return "{}"
	}
	cs.Logger.Debug().Str("config", string(cfg)).Msg("CommandServer config")
	return string(cfg)
}

func (cs *CommandServer) Name() comm.MoLingServerType {
	return CommandServerName
}

func (cs *CommandServer) Close() error {
	// Cancel the context to stop the browser
	cs.Logger.Debug().Msg("CommandServer closed")
	return nil
}

// LoadConfig loads the configuration from a JSON object.
func (cs *CommandServer) LoadConfig(jsonData map[string]any) error {
	err := utils.MergeJSONToStruct(cs.config, jsonData)
	if err != nil {
		return err
	}
	// split the AllowedCommand string into a slice
	cs.config.allowedCommands = strings.Split(cs.config.AllowedCommand, ",")
	return cs.config.Check()
}

```

--------------------------------------------------------------------------------
/pkg/services/browser/browser_debugger.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

// Package services provides a set of services for the MoLing application.
package browser

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/chromedp/cdproto/target"
	"github.com/chromedp/chromedp"
	"github.com/mark3labs/mcp-go/mcp"
)

// handleDebugEnable handles the enabling and disabling of debugging in the browser.
func (bs *BrowserServer) handleDebugEnable(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	enabled, ok := args["enabled"].(bool)
	if !ok {
		return mcp.NewToolResultError("enabled must be a boolean"), nil
	}

	var err error
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()

	if enabled {
		err = chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
			t := chromedp.FromContext(ctx).Target
			// 使用Execute方法执行AttachToTarget命令
			params := target.AttachToTarget(t.TargetID).WithFlatten(true)
			return t.Execute(ctx, "Target.attachToTarget", params, nil)
		}))
	} else {
		err = chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
			t := chromedp.FromContext(ctx).Target
			// 使用Execute方法执行DetachFromTarget命令
			params := target.DetachFromTarget().WithSessionID(t.SessionID)
			return t.Execute(ctx, "Target.detachFromTarget", params, nil)
		}))
	}

	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("failed to %s debugging: %v",
			map[bool]string{true: "enable", false: "disable"}[enabled], err)), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Debugging %s",
		map[bool]string{true: "enabled", false: "disabled"}[enabled])), nil
}

// handleSetBreakpoint handles setting a breakpoint in the browser.
func (bs *BrowserServer) handleSetBreakpoint(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	url, ok := args["url"].(string)
	if !ok {
		return mcp.NewToolResultError("url must be a string"), nil
	}

	line, ok := args["line"].(float64)
	if !ok {
		return mcp.NewToolResultError("line must be a number"), nil
	}

	column, _ := args["column"].(float64)
	condition, _ := args["condition"].(string)

	var breakpointID string
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()
	err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
		t := chromedp.FromContext(ctx).Target
		params := map[string]any{
			"url":       url,
			"line":      int(line),
			"column":    int(column),
			"condition": condition,
		}

		var result map[string]any
		// 使用Execute方法执行Debugger.setBreakpoint命令
		if err := t.Execute(ctx, "Debugger.setBreakpoint", params, &result); err != nil {
			return err
		}

		breakpointID, ok = result["breakpointId"].(string)
		if !ok {
			breakpointID = ""
			return fmt.Errorf("failed to get breakpoint ID")
		}
		return nil
	}))

	if 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
}

// handleRemoveBreakpoint handles removing a breakpoint in the browser.
func (bs *BrowserServer) handleRemoveBreakpoint(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	breakpointID, ok := args["breakpointId"].(string)
	if !ok {
		return mcp.NewToolResultError("breakpointId must be a string"), nil
	}
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()
	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]any{
			"breakpointId": breakpointID,
		}, nil)
	}))

	if 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
}

// handlePause handles pausing the JavaScript execution in the browser.
func (bs *BrowserServer) handlePause(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()
	err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
		t := chromedp.FromContext(ctx).Target
		// 使用Execute方法执行Debugger.pause命令
		return t.Execute(ctx, "Debugger.pause", nil, nil)
	}))

	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("failed to pause execution: %s", err.Error())), nil
	}
	return mcp.NewToolResultText("JavaScript execution paused"), nil
}

// handleResume handles resuming the JavaScript execution in the browser.
func (bs *BrowserServer) handleResume(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()
	err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
		t := chromedp.FromContext(ctx).Target
		// 使用Execute方法执行Debugger.resume命令
		return t.Execute(ctx, "Debugger.resume", nil, nil)
	}))

	if 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 any
	rctx, cancel := context.WithCancel(bs.Context)
	defer cancel()
	err := chromedp.Run(rctx, chromedp.ActionFunc(func(ctx context.Context) error {
		t := chromedp.FromContext(ctx).Target
		// 使用Execute方法执行Debugger.getStackTrace命令
		return t.Execute(ctx, "Debugger.getStackTrace", nil, &callstack)
	}))

	if 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: %s", err.Error())), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("Current call stack: %s", string(callstackJSON))), nil
}

```

--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 800 600">
    <!-- Generator: Adobe Illustrator 29.0.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 186)  -->
    <defs>
        <style>
            .st0 {
            fill-rule: evenodd;
            }
        </style>
    </defs>
    <g id="shape_PUEPPPvLNT">
        <path class="st0"
              d="M431.5.8c-10.8,10.1-9.1,23.3,4.4,34.7,3.1,2.6,5.2,4.8,4.6,4.9-.6.1-4.2-.7-7.9-1.8-9.5-2.8-23.8-5.6-34-6.6l-8.5-.9-2.6-4.2c-3.1-4.9-8.2-9.1-13.1-10.8-3.2-1.1-3.7-1.7-4.9-5-1.5-4.2-4.6-7-9.4-8.4-2.6-.8-4-.7-8.1.3-9.3,2.4-14.1,6.4-16.5,14-1.2,3.8-1.5,4-7.4,6.8-13.7,6.6-18.5,14.5-13.8,22.7l1.8,3.2-1.6,1.6c-1.5,1.5-1.9,1.6-5,.7-1.9-.5-8.1-1-13.8-1h-10.4c0,0-.2,2-.2,2l-.2,2.1h26.2l-7.1,7.1c-8.7,8.8-13.4,15.6-17.9,26.1-6.8,15.7-8.5,22.4-11.1,45.3-1,8.4-2.1,14.8-3.2,17.6-1.6,4.1-1.6,4.5-.4,6.9,2.8,5.8,5.7,15.3,4.7,14.9-3.6-1.5-3.8-1.4-1.9.6,1.6,1.7,1.7,2.1.6,2.1s-2.2-.6-3.3-1.3c-2-1.3-2-1.3-1.4.6,1.1,3.7,3.5,5.4,7.4,5.4s3.7.3,4,1.6c.3,1.1-.3,2.1-2.3,3.6-3,2.3-2.4,3.3,1.8,3.3,1.4,0,2.5.3,2.5.6,0,.3-.9,4.2-1.9,8.7-1,4.5-1.9,10.5-1.9,13.4,0,14.1,8.9,39.5,20.8,59.2,3,4.9,6.5,11.8,7.8,15.4,6.2,16.2,14,17.5,46.7,7.9,5.9-1.7,11-2.9,11.4-2.5.8.8,8.9,18.9,9.5,21.2.4,1.4-.3,2.1-4.1,4.2-5.6,3.1-7.2,4.4-7.2,5.5s1.7,6.5,3.9,13.2c2.1,6.8,4.1,13.6,4.3,15.2l.5,2.9-7.2,5c-4,2.8-9.8,7.5-12.9,10.4l-5.7,5.4-8.9.4-8.9.4-6.8,7.3q-9.4,10-4.1,10h4.3l4.6-6.9,4.6-6.9,24.6-.4c13.5-.2,25.7-.7,27.1-.9l2.5-.5-2.6,4.5c-1.4,2.5-3.3,5.7-4.3,7.2-1.9,3.1-2.1,3.9-.7,3.9s3.4-3.4,8.2-11.6c2.5-4.2,2.6-4.3,9.4-6.5,7.7-2.5,33.8-15.1,41.9-20.3l5.3-3.4-.4-3.2c-.2-1.8-.8-4.2-1.3-5.5l-.9-2.2-7.5.4c-7.3.3-14.2,2-16.2,4-1.3,1.2,1.2,9.7,4.1,13.9,2.2,3.3,2.5,4.2.7,2.7-1.6-1.3-5.5-9.5-6.1-12.8-.3-1.4-.7-2.5-1.1-2.5-2,0-10.9,8.4-15.3,14.6-2,2.7-3.7,4.5-3.9,4-.9-2.6,10.9-16.9,16.4-19.8,1.5-.8,2.7-1.9,2.7-2.4s-.8-4.5-1.8-8.9c-1-4.4-2.8-13.2-4-19.5-1.2-6.3-2.6-11.6-3.1-11.8-1.4-.5-12.7,4.9-13.2,6.4-.9,2.4,1.2,15.2,4.3,25.4,1.7,5.5,3.2,11.6,3.5,13.5.6,4.5.2,3.5-2.1-5-1-3.8-2.8-10.1-3.9-13.9-2.8-9.7-3.4-17.7-1.7-23.2,1.1-3.5,1.2-5,.5-8.7q-.9-4.9,6.1-10.2l2.5-1.9-2.5-5c-1.4-2.8-3-5.6-3.6-6.2-.6-.7-1.1-1.6-1.1-2s-2.6-2.6-5.8-4.8-5.8-4.1-5.8-4.2,1.2-1.5,2.7-3.1c3.3-3.4,8.9-13.3,8.9-15.6,0-3,2.9-5.7,6.1-5.7s8.6,4,8.6,6.4c0,2.3-2.3,6-6.4,10.3-2.1,2.3-3.5,4.1-3.1,4.1,1.3,0,7.9-7,10-10.7,1.9-3.3,1.9-3.5.7-5.8-4.7-9-8-10.8-13.4-7.5l-2.4,1.5-2.8-5.9c-4.7-9.9-4.9-12-5.8-54.6-.5-25.8-1-38.9-1.6-39.4-.6-.6-.7,9.3-.4,35.1.3,19.8.6,37,.8,38.3.4,2.4.4,2.4-3.2,2.3-3,0-3.8.3-5.1,2-1.4,1.8-1.4,2.2-.4,4.6,1.8,4.4,4.6,5.7,8.9,4.3,1.3-.4,1.7-.3,1.7.6s1.4,4.3,3.1,8.2c2.8,6.4,3.3,9.2,1.5,8.1-.4-.2-1.1-.2-1.6.1-.6.4-.5.9.5,1.6,2.1,1.5,1,5.1-3.4,12.1-3.7,5.8-6.2,8-6.8,6.1-.2-.5-.6-.7-1.1-.5-.5.3-.4.9.2,1.5.7.8.7,1.4-.1,2.3-2,2.4-19.6,15.5-28.8,21.5-7.9,5.1-9.9,6.1-14.9,7.1-7.3,1.4-13.5,1.4-16.8,0-4.4-1.8-8.8-10.5-6.6-13.2.5-.6,2.6-1.5,4.7-2,3.7-.9,8.4-3.9,8.4-5.4s-3.4-2.4-11.8-3.4c-6.5-.8-8.9-1.4-9.6-2.4-1.1-1.5-1.3-4.8-.3-6.3,1.1-1.7,13.9-2.9,20.9-1.9,4.7.7,6.9.7,9.8,0,5.8-1.5,5.1-3.1-1.6-3.6-4.6-.4-6.6-1.1-13-4.5-6.9-3.6-8-4-12-3.8-3.1.1-5-.2-6.3-1.1-1-.7-2.1-1.3-2.4-1.3-1.2,0-2.4,4.9-2,8.1.3,1.9.3,3.5.1,3.5-.9,0-4.6-8.8-8.5-20.3-5.5-16.2-6-21.8-3.5-34,2-9.3,1.8-9.1,9.1-10.1l4.1-.5-.5,2.5c-1,4.7-4.3,12.5-7.4,17.7-4.7,7.9-4.1,10.1,4.4,18.2,5.8,5.5,5.9,5.6,5.5,8.9l-.4,3.4,3-3.1,3-3.1-.4,3.3c-.2,2,0,3.3.4,3.3s.7-1.5.4-4c-.3-3.5-.8-4.3-3.8-7-8.2-7.3-10-9.2-11.2-11.7l-1.3-2.7,4.7-9.5c7.2-14.4,8.7-22.5,5.6-31.2-1.5-4.2-8.8-15.2-13.5-20.2-2.5-2.7-7.3-5.5-9.4-5.5s-1.1-.5-.8-2.1c.2-1.2.9-5.4,1.6-9.5,3.7-24,4.6-28.8,6.3-34.1,3.6-11.4,13-22.2,22.1-25.3,10.3-3.5,24.7-2.9,37.7,1.7,8.7,3,9.8,3.8,10.4,7.5.8,4.4,4.3,9.6,7.7,11.2,2.3,1.1,3.9,1.3,7.3.9,4.3-.5,4.5-.4,6.7,2.1,1.3,1.5,5.2,7.2,8.7,12.8,5.7,9,7.5,11.2,16.6,19.8,6.1,5.8,11.8,12,14.3,15.5l4.1,5.9-1.2,7.9c-1.5,9.7-2.2,35.3-1.2,41.7.4,2.6,1.8,7.1,3.2,10l2.4,5.3-8,8.1c-4.4,4.4-7.7,8.1-7.3,8.1s4.2-3.5,8.5-7.8q7.7-7.7,9.1-6.1c.8.9,1.4,2.9,1.4,4.6s.6,3.7,1.5,4.9l1.5,2-2.5,4c-10.3,16.5-15.4,22.5-25.6,30l-6.7,4.9,2,2.4c1.1,1.3,2.8,4.1,3.7,6.1l1.6,3.6,9.7-7c5.3-3.9,11-8.2,12.7-9.7l3.1-2.7.5,9.1c.3,5,.7,10.8,1.1,12.8.8,5.1,2.3,4.8,1.7-.4-2.8-24-3.1-34.7-.9-45.3,2.1-10.2,5.3-14.9,23.9-34.6l3-3.2-2.4-6.2c-4.6-12.1-4.9-22.8-.9-33.7,5.1-13.6,16.8-18.6,24.5-10.4,4,4.2,5.5,9.6,6.5,22.7,1.4,19.2.3,24.8-6.3,32.4-3.9,4.4-8.5,6.7-13.7,6.7s-4,0-4-2.1c0-1.2.5-3.9,1.1-6,1-3.8,1-5-.2-5s-19.7,20.8-23.6,25.9c-1.6,2.1-3.6,5.3-4.4,7-1.6,3.6-3.4,13.9-4.1,23.1l-.5,6.2,2.4-2.7c1.3-1.5,4.5-5.8,7.1-9.7,2.6-3.8,4.9-7.1,5.1-7.3,1-1-.7,16.1-2.1,21.4-2,7.1-1.6,8.3.6,1.7l1.6-4.8.4,3.5c1.5,12.9,2.4,17.3,5.2,25.7,1.7,5.2,5.4,14,8.1,19.7,4.1,8.5,5.2,10.3,6.4,9.9,2.9-.9,9.8-2,12.7-2s3-.2,3-.5-.9-1.7-2-3.1c-3.3-4.3-6.5-12.1-7.7-18.5-2.1-11.2-1.9-10.7-5.8-10.7-1.9,0-5.5.2-8.1.4l-4.6.4-.9-5c-1.3-7.1-1.2-18.9.2-24.3,1.3-5.1,7.3-20.8,7.8-20.3.2.2-.3,3.2-.9,6.7-.7,3.5-1,6.6-.8,6.8.2.2,1.3-.3,2.3-1.3,1-1,2-1.4,2.2-1.1.2.4,1.2,3.2,2.1,6.4,4,13.4,11.2,23.7,19.5,27.9,4.4,2.2,4.7,2.3,6.5,1.1,3.1-2,2.1-3.9-2.1-3.9-7.7,0-16.2-10.6-21.1-26.3-2.2-7-3.9-18.8-3.2-21,1.2-3.4,1.8-3.4,8,.8,7.2,4.9,17,9.7,19.8,9.7h2.1l-.3-17.5-.3-17.5,2.3-4.7c3.5-7.2,5.9-13.9,8.3-23.2l2.2-8.5v52c0,51.4,0,52,1.6,52s1.5-.5,1.6-48.5c0-37.6.3-50.3,1.1-56.8.6-4.6,1.4-8.7,1.7-9.3.3-.5,1.2,1.8,2.1,5.6,1.3,5.9,1.5,10.9,2,49.8.4,36.3.6,43.3,1.6,43.6.9.3,1-5.6.6-41.4-.4-36.7-.7-42.8-2.1-50.8-.9-5-1.5-10.2-1.5-11.6,0-3.6,5.5-17,6.4-15.5.3.6.7,36.4.9,79.4.2,65.9.4,80.3,1.5,91,1.6,15.9,5,43.4,5.9,47.4.7,3,3.4,6.6,4.3,5.7.3-.3-.4-7.7-1.5-16.6-2.9-23.2-4.9-44.1-5.8-60.5-.8-15.3-2.1-118.1-1.4-117.4.2.2,1.8,6.2,3.6,13.4,1.8,7.1,4,14.9,5.1,17.2,1.7,3.8,2.1,4.2,4,4,3.6-.4,3.9-2.1,1.6-9.6-1.1-3.8-3.7-15.7-5.6-26.5-1.9-10.8-3.7-20.2-3.9-20.9-.2-.6,3.4,2.7,8,7.4l8.4,8.6,7.6-.2c5.4-.1,7.8-.5,8.3-1.2,1-1.6.8-2.7-.6-4.2-1.1-1.1-2.2-1.2-6.9-.7l-5.6.5-8.1-8.4c-4.5-4.6-9.6-10.2-11.3-12.4-3-3.8-3.1-4.1-2.4-6.9.4-1.6.8-6.2.8-10.1,0-9.7-1.6-17.4-5.8-27.6-.6-1.3-.3-1.3,2,.4,1.5,1,5.6,4.8,9.3,8.3,5.8,5.6,7,6.4,9.4,6.4s3.8.8,7.3,3.6c2.5,2,4.9,4,5.5,4.4.8.6,1.4.3,2.5-1.1.8-1,1.5-2.5,1.5-3.2,0-1.5-4.3-5.2-11.2-9.6-2.5-1.6-9-6-14.3-9.6s-11.7-7.8-14.2-9.2c-5.2-2.9-5.5-2.1,3.4-7.9,12.6-8.2,14.1-17.8,4.4-28.1-4.4-4.7-4.6-5-3.7-7.3,1.5-4.2,1-9.6-1.1-13.9-1.9-3.7-7.2-9.5-10.9-12-.9-.6-2.3-2.9-3-5.1-.7-2.2-2.7-5.4-4.4-7.3l-3.1-3.3h-24.7c-27.2,0-26.9,0-32.8,5-3.6,3.1-7.1,9.3-8.5,15.1l-1,4.3.4-4.2c.7-7.2,4.3-13.8,9.9-17.9l3.1-2.3h-5.6c-5.6,0-5.6,0-9.3,3.5M470.4,9.3c-1.7.5-4.2,1.5-5.6,2.2-1.4.7-2.7,1.2-2.9,1-.6-.6,3.9-3.5,6.7-4.2,3.8-1,5.5-.1,1.8,1M354.2,13.5c-1.5,1-3.3,1-2.7,0,.3-.4,1.2-.8,2.2-.8,1.4,0,1.5.2.5.8M474.3,16.2c-2.5,1-5.3,2-6.2,2.3l-1.5.6,1.5-1.3c2.3-2,5.9-3.4,8.5-3.4,1.9,0,1.5.3-2.3,1.8M479,19.6c-.8.3-2.6.8-3.9,1.1l-2.3.5,2.3-1.1c1.3-.6,3-1.1,3.9-1.1h1.5s-1.5.6-1.5.6M434.3,24.5c-.2.6-.4.4-.5-.5,0-.8.1-1.3.4-1s.3.9,0,1.5M335.8,28.6c-1.4.6-3.6,1.9-4.9,2.8-1.6,1.1-2.2,1.3-2,.6.6-1.7,5.3-4.6,7.5-4.5,1.9,0,1.9,0-.6,1.1M400.9,36.4c4.7.6,13.7,2.4,20.1,4l11.6,2.8-8.5,1.8c-4.7,1-10.5,2.5-13.1,3.5-6.1,2.2-17.1,7.8-21.7,11.1l-3.7,2.6-3.5-2.4c-4.8-3.3-15.5-8-22-9.5l-5.3-1.3,2.6-1.6c3.3-2,13.8-7.1,17-8.2,4.8-1.7,1.4-1.8-4.1,0-6.3,1.9-12.1,4.7-15.8,7.4-2.2,1.6-3.1,1.8-10,1.7-7.7-.1-15.3,1.2-16.3,2.7-.3.6,0,.6,1,.2.8-.3,5.8-.8,11.1-1l9.7-.3-3.6,2.3c-4.6,2.9-13.7,11.6-16.4,15.5l-2.1,3-7.1.5c-11.1.8-19.2,3.4-26.4,8.4q-2.8,1.9,4.8-7.3c11.5-14,31.7-27.6,48.9-33,15.2-4.8,31.6-5.7,52.9-2.8M497.5,40.1c1.5.6,2.7,1.2,2.7,1.4s-3.4.1-7.5,0c-4.8-.2-9.6.2-13.3,1-3.2.7-5.9,1.1-6,.9-.3-.3,5.1-2.4,9-3.5,4.1-1.1,12.1-1,15.1.2M495.9,45.7c.6.3-2.8.6-7.7.6s-8.8,0-8.8-.2,1.1-.5,2.5-.9c2.7-.8,12.2-.5,14,.5M449.7,49.7c1.2.7,1.2.9.3,1.1-14.2,4.1-19.6,6-26.7,9.4-16.7,8.1-29,17.1-39.4,28.7-3.2,3.6-5.9,6.5-6.1,6.6-.7.4-10.5-8.2-10.5-9.2,0-1.6,9.9-11.1,17.7-17,12.3-9.3,32.8-18.2,46.8-20.3,5.6-.9,15.9-.5,17.9.6M362.3,53.5c5.9,2.1,20.5,9.4,20.5,10.3s-1.3,1.5-2.9,2.7c-1.6,1.2-6.1,5.4-10,9.4l-7.1,7.2-8.7-4.3c-4.8-2.3-11.8-5.1-15.5-6-3.7-1-6.8-2.1-6.8-2.4s4.3-5,9.6-10.2c10.9-10.9,10-10.5,20.9-6.7M490.8,53.4l1.7,1.2v60.1c0,40.7-.3,60.9-.8,62.5-.9,2.7-2,3.3-4.3,2.5-1.4-.5-1.7-1.4-2.1-7.7-.3-3.9-.5-32.4-.5-63.4v-56.4c0,0,2.1,0,2.1,0,1.2,0,2.9.5,3.9,1.2M445.4,71.5c3.6,3.9,6.6,7.3,6.7,7.6s-2.8,2-6.6,3.9c-7.3,3.6-18,10.3-23.1,14.5-1.8,1.4-3.6,2.6-4,2.6s-1.3-.9-2.1-2c-.7-1.1-3.6-4.6-6.4-7.8l-5-5.8,2.5-2c11.2-9,26-17.9,29.6-17.9s3.8,2.3,8.2,7M480.1,80c-1.4,8.3-5.2,15.9-15.2,30.7-5.2,7.8-10.4,15.8-11.4,17.9-1.6,3.2-2.3,3.9-4.7,4.3-1.5.3-3,.4-3.2.1-.5-.5,1.9-8.7,3.4-11.7.7-1.3,5.3-8.6,10.3-16.3,10.2-15.6,17.7-28.8,19.8-35l1.4-4.1.2,4.5c.1,2.5-.2,6.8-.6,9.5M502,124.8c0,6.6-.3,12.7-.7,13.7-.5,1.1-.7-2.9-.7-12s-.2-15.6-.5-17.9l-.5-4.2,1.2,4.2c.8,2.9,1.2,8.1,1.2,16.2M453.5,137c-11.7,5.6-19.5,19.3-21.6,38.2-.4,3.6-.8,4.4-1.9,4.4-2.1,0-2.6-1.3-.8-1.9,2-.6,2-2.3,0-3.7l-1.6-1.1,1.6-.5c1.9-.6,2.2-3.3.5-4-1.7-.6-1.4-1.8.3-1.8s2.6-1.2,1.1-2.7c-1.2-1.2-1.1-1.3.4-1.7,2-.5,2.1-1.5.3-2.5-.7-.4-1-.8-.5-.8.4,0,1.5-.5,2.3-1.1,1.5-1.1,1.5-1.1,0-2.3-1.4-1-1.4-1.2-.2-1.2,1.4,0,3.6-3.1,2.2-3.1s-.8-.3-.8-.8.7-.8,1.6-.8,1.5-.4,1.2-1.6c-.7-2.8,9.2-11,15.2-12.4,5.1-1.2,5.4-.7.9,1.5M334.1,137.5c-7.7,1.6-14,4.3-16.8,7-2.1,2.1-2.5,3.1-2.5,6,0,6.8,5.9,10.7,9.5,6.2,3.8-4.7,8-8.3,11.7-10,9.1-4.2,21.9-4.3,33.6-.4,5.8,2,6.3,2,4.7.6-2.8-2.3-12.1-6.4-18.6-8.3-7.6-2.1-15.3-2.6-21.6-1.2M335.1,159.5c-1.8.9-3.7,2.4-4.2,3.5-.5,1.1-1.3,1.8-1.9,1.6-1.4-.5-1.3,1.1.2,3.2,1.1,1.5,1,2.2-.8,7.5-1.1,3.2-2,6.1-2,6.4s1.7-1.3,3.8-3.7l3.8-4.2-.4,2.7c-.2,1.5-.7,3.7-1.1,4.9-.4,1.2-.7,2.5-.7,2.8s3.2.6,7,.7c10.1.3,13.3-1,21.3-8.4,5.5-5.1,7.5-6.4,12.9-8.4,3.5-1.3,7-2.9,7.8-3.6,1.4-1.1,1.4-1.2-.8-.9-1.3.2-4.8.8-7.9,1.4-4.7.9-6.5.9-11.2,0-12.2-2.3-20.5-2-27.8,1.1-2.7,1.1-.6-1.6,2.7-3.4,5-2.8,11.8-2.9,21.4-.5,4.4,1.1,8.2,1.9,8.4,1.6.6-.6.2-.8-6-3.1-8-2.9-19.7-3.5-24.5-1.2M284.3,160.8c3.4,2.2,9.6,8,9.6,8.9s-1,0-2.1-.5c-3.6-1.9-6.7-2.1-9.8-.8-1.6.7-3,1.1-3.1,1-.4-.5-2.1-5.8-2.7-8.1-.6-2.5-.6-2.5,2.2-2.5s4,.8,5.9,2M465.6,160c-1.9.8-3,2.1-4.6,5.5-1.8,3.9-2.1,5.7-2.4,14.1-.2,7.7,0,10,.9,11.2,1.1,1.5,1.2,1.4,2.1-1.9,1.1-4.2,5.2-8.5,8-8.5s2.9.8,4.1,1.8c3.3,2.8,2.9,6.9-.9,10.4-2.2,2-2.7,2.9-2.1,3.8,1.5,2.4,3.1,2.8,5.2,1.4l2-1.3v-11.5c0-6.3-.4-13.3-.8-15.4-1.7-7.8-6.4-11.8-11.4-9.7M291.7,171.3c2.7.9,5.6,3,5,3.5-.2.2-1.5-.3-2.9-1-1.7-.9-4.4-1.4-8.2-1.4-3.1,0-5.6-.3-5.6-.6,0-1.4,7.9-1.8,11.7-.5M358.8,173.2c.8.7-4.6,6.2-7.7,8l-3.4,1.9,1.3-1.9c.8-1.1,1.3-3.3,1.3-5.6v-3.8l4.1.5c2.2.3,4.2.7,4.4.8M339.2,180.9c1.7,1.6,2.4,2.6,1.6,2.3-.8-.3-2.3-.5-3.3-.5-1.6,0-1.9-.4-1.9-2.3s.1-2.3.3-2.3,1.6,1.3,3.3,2.9M291.3,187.2c-.8.8-3.4-.7-4.2-2.5-1.7-3.8-.9-4.3,1.8-1.1,1.5,1.8,2.6,3.4,2.4,3.6M431.5,185.3c-.3.3-1.2.4-1.9.1-1.2-.5-1.2-.6,0-1.5,1-.7,1.5-.8,1.9-.1.3.5.3,1.2,0,1.5M369,184.4c-.5.5-.9,1.2-.9,1.5,0,.8,9.4,5.3,10.1,4.9,1.1-.7.8-2.2-.7-3.5-4.5-3.8-6.8-4.6-8.5-2.9M282.3,186.9c0,.2-.5.5-1.2.8-.7.3-1,.1-.7-.3.5-.7,1.9-1.1,1.9-.5M432.2,189.3c0,.6-.5,1.2-1.1,1.2-1.5,0-2.2-1.3-1-1.8,1.7-.7,2.1-.5,2.1.6M349.6,190.3c0,.3,1.1,1.1,2.5,1.7,1.4.6,4.4,3.7,6.9,7,6.5,8.6,11.5,11.4,18.1,10.5,4.6-.7,4.3-1.6-.5-1.8-4.9-.2-9.7-2.6-11.4-5.7-3.7-6.8-5.7-9.2-8.6-10.7-3.1-1.6-7-2.1-7-1M433,193.9c0,1.3-.7,1.4-2.7.7-1-.4-1.1-.6-.3-1.1,1.6-1,3-.8,3,.5M434.1,198.6c.7,1.7.7,1.7-1.5,1.3-2.1-.4-2.5-1.4-1-2,1.6-.7,1.9-.6,2.4.7M436.1,203.8c0,.8-2.9.7-3.4-.1-.2-.4.2-1.1.9-1.5,1.3-.7,2.5,0,2.5,1.6M437.6,207.8c1,1.3.8,1.4-1.1.7-.7-.3-1.2-.8-1.2-1.2,0-1.1,1.3-.8,2.3.5M440.5,212.9c1.1,1.8.4,2-1.6.5-1.6-1.2-1.7-2-.4-2s1.4.7,1.9,1.5M489.8,239.7c-.6.6-10.7-4.4-17-8.5-5.7-3.7-6-4-4.2-4.7,7.6-2.9,12.2-5.8,16.5-10.2l4.7-4.8.2,13.9c.1,7.7,0,14.1-.2,14.3M383.6,214.4c1.8.9,2,4,.3,5.4-1.7,1.4-5,.3-5.8-2-1.3-3.4,1.8-5.3,5.5-3.3M300.3,221.1c-.5,1.2,2.3,4.1,4,4.1.4,0,2.8,1.1,5.4,2.4,4.1,2.1,4.9,2.2,6.8,1.4,1.2-.5,1.9-1.1,1.6-1.4-.4-.3-1.1-1.4-1.6-2.6-1.3-2.9-6.4-5.1-11.6-5.2-3.1,0-4.2.3-4.6,1.2M300.7,260.4c-.6,1.9-1,2.4-1.5,1.7-.9-1.5-.8-3.5.3-4,1.8-.7,2-.5,1.1,2.2M387.6,267.7c3.3,2.3,5.9,4.5,6,4.8,0,1.1-15.4,8.4-23.9,11.3-8.7,3-23.1,6.7-23.6,6.1-.2-.2,4.7-3.8,10.7-8.1,6-4.3,14-10.3,17.6-13.2,3.6-3,6.8-5.3,7-5.2.2,0,3.1,2.1,6.4,4.4M402.4,287.5c.9,4.9,3.8,13.1,4.1,11.7.2-.7-.6-4.3-1.7-7.9-2.1-6.8-3.3-8.8-2.4-3.8M437.6,290.4c-6.6,2-6.1.5-4.1,12.9,1.3,8,6.3,29.9,7.2,31.6.5.8.7.7,1.1-.6.6-2,8.5-7.7,14.7-10.7,2.5-1.2,4.8-2.3,4.9-2.4.2-.1,0-.6-.5-1.2-1.6-2-8.1-15.7-11-23.4-3.4-9-3.4-9-12.3-6.3M420.2,296c-12.8,4.5-13.7,4.9-13.2,5.6.3.6,7.1,32.1,8,37.4.2,1.2.6,2.3.9,2.5.3.2,2.4-.4,4.7-1.3,3.3-1.3,6-1.6,12.5-1.7,7.1,0,8.2-.3,7.8-1.2-3.7-8.5-7.7-24.4-9.6-37.7-.5-3.5-1.1-6.5-1.4-6.6-.3-.1-4.7,1.2-9.8,3M391.3,298.1c0,8.6-.3,9.2-5.9,12-2.7,1.4-5.3,2.3-5.7,2.1-.4-.2-.9-1.5-1.2-2.8-.4-1.8,0-2.9,1.4-4.9,3.2-4.2,9.9-11.3,10.6-11.3s.7,2.2.7,4.9M486.6,319.8c-.8,2,5.1,9.9,13.1,17.8,8.1,7.9,19.5,17.2,20.3,16.4.2-.2-5.4-6.4-12.6-13.7-7.2-7.3-14.2-15.1-15.7-17.4-2.7-4.2-4.3-5.2-5.1-3.2M443.8,347c0,3.8,3.2,9,6.5,10.6,3.7,1.8,10.1,1.6,20.9-.6,9.4-1.9,11.5-2.8,10-4.3-.6-.6-2.2-.5-5.4.3-2.5.6-6.3,1.4-8.5,1.7-4.9.6-8.3-.8-16-6.5-2.9-2.2-5.8-4-6.4-4s-1.1,1-1.1,2.8M441.1,361.9c1.9,1.9,25.1,9.1,25.1,7.8s-23.5-8.5-25.1-8.5-.3.3,0,.7M500.6,362.1c-15,1.6-29.3,9.3-41.8,22.6-3.6,3.8-6.5,7.2-6.5,7.6s2,.7,4.4.7h4.4l6-5.7c7.1-6.8,13.4-10.6,22.8-13.9,6.7-2.3,7.5-2.4,19-2.4,13.7,0,17.9.9,31.1,7.1,4.1,1.9,7.8,3.3,8.1,3,.8-.8-5.2-6.6-10.6-10.1-10.9-7.1-23.7-10.2-36.7-8.8M427.6,365.3c1.6,1.6,27,12.2,28.6,11.9,1.7-.4-4.6-3.9-14.8-8.3-9.5-4.1-15.8-5.8-13.8-3.6M500.2,381.6c-4.9,1.4-8,3.1-11.2,6.3-4.6,4.6-4,4.9,6.9,3,7.9-1.4,15.9-1.1,21.2.8,3.7,1.3,10.9,1.7,10.9.5s-3.9-5.4-7.2-7.2c-6.9-4-13.9-5.2-20.7-3.3"/>
    </g>
    <g>
        <path d="M301.4,474.1v-55.1h8.7l15.8,36.3h.5l15.8-36.3h8.6v55.1h-5.3v-49.9h-.5l-16,36.4h-5.9l-16-36.4h-.5v49.9h-5.2Z"/>
        <path d="M362.8,452.7c0-5.6.7-10,2-13.2,1.3-3.3,3.4-5.6,6.2-6.9,2.8-1.4,6.4-2.1,10.8-2.1s8,.7,10.8,2.1c2.8,1.4,4.8,3.7,6.2,6.9,1.4,3.3,2.1,7.7,2.1,13.2s-.7,10-2.1,13.2c-1.4,3.3-3.4,5.6-6.2,6.9-2.8,1.4-6.4,2.1-10.8,2.1s-8-.7-10.8-2.1c-2.8-1.4-4.8-3.7-6.2-6.9-1.3-3.3-2-7.7-2-13.2ZM395.5,452.7c0-4.6-.4-8.1-1.3-10.7-.8-2.6-2.3-4.4-4.2-5.5s-4.7-1.7-8.2-1.7-6.3.6-8.3,1.7c-2,1.1-3.3,3-4.2,5.5-.8,2.6-1.2,6.1-1.2,10.7s.4,8.1,1.3,10.7c.8,2.6,2.2,4.4,4.2,5.5,2,1.1,4.7,1.7,8.2,1.7s6.4-.6,8.3-1.7c2-1.1,3.3-2.9,4.2-5.5.8-2.5,1.2-6.1,1.2-10.7Z"/>
        <path d="M418.3,469.3h24.7v4.8h-29.9v-55.1h5.2v50.2Z"/>
        <path d="M451.3,419.2c0-1.4.2-2.3.7-2.7.4-.4,1.3-.6,2.6-.6s2,.2,2.4.6c.4.4.7,1.3.7,2.7s-.2,2.3-.7,2.7c-.5.4-1.3.6-2.4.6s-2.1-.2-2.6-.6c-.5-.4-.7-1.3-.7-2.7ZM451.9,474.1v-42.9h5.1v42.9h-5.1Z"/>
        <path d="M505,444.3v29.8h-5.1v-27.5c0-2.8-.3-5-.8-6.7-.5-1.7-1.5-2.9-2.9-3.7-1.4-.8-3.4-1.2-6-1.2s-6.2.7-8.6,2-4,3.9-5,7.5v29.6h-5v-42.9h5v6.8c.9-2.6,2.7-4.6,5.3-5.8,2.6-1.2,5.6-1.8,8.8-1.8,5.2,0,8.8,1.2,11,3.5,2.1,2.4,3.2,5.8,3.2,10.3Z"/>
        <path d="M553.8,471.3c0,6-1.7,10.7-5,13.9-3.4,3.2-8.4,4.8-15.1,4.8s-7.3-.3-10-1v-4.4c2.8.6,6.1,1,10,1s9-1.2,11.5-3.7c2.5-2.4,3.7-6.3,3.7-11.5v-5c-1,2.9-2.6,5-4.9,6.5-2.3,1.5-5.6,2.2-9.8,2.2s-7.8-.8-10.3-2.5c-2.5-1.7-4.3-4.1-5.3-7.2-1-3.1-1.5-7.2-1.5-12.1s1.3-12.3,3.8-16.1c2.6-3.8,7-5.7,13.3-5.7s7.5.8,9.8,2.5c2.3,1.7,4,4,4.9,6.9v-8.7h5v40ZM549,452.3c0-5.7-1.1-10-3.2-13-2.1-3-5.8-4.5-11-4.5s-6.1.7-7.9,2.1c-1.8,1.4-3.1,3.4-3.7,5.9-.6,2.5-1,5.7-1,9.4s.3,7.1,1,9.6c.7,2.5,1.9,4.4,3.7,5.8,1.8,1.4,4.4,2.1,7.8,2.1,5.2,0,8.9-1.5,11-4.4,2.1-3,3.1-7.3,3.1-13Z"/>
    </g>
</svg>
```

--------------------------------------------------------------------------------
/pkg/services/browser/browser.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 CFC4N <[email protected]>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Repository: https://github.com/gojue/moling

// Package services provides a set of services for the MoLing application.
package browser

import (
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"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"
)

const (
	BrowserDataPath                         = "browser" // Path to store browser data
	BrowserServerName comm.MoLingServerType = "Browser"
)

// BrowserServer represents the configuration for the browser service.
type BrowserServer struct {
	abstract.MLService
	config       *BrowserConfig
	name         string // The name of the service
	cancelAlloc  context.CancelFunc
	cancelChrome context.CancelFunc
}

// NewBrowserServer creates a new BrowserServer instance with the given context and configuration.
func NewBrowserServer(ctx context.Context) (abstract.Service, error) {
	bc := NewBrowserConfig()
	globalConf := ctx.Value(comm.MoLingConfigKey).(*config.MoLingConfig)
	bc.BrowserDataPath = filepath.Join(globalConf.BasePath, BrowserDataPath)
	bc.DataPath = filepath.Join(globalConf.BasePath, "data")
	logger, ok := ctx.Value(comm.MoLingLoggerKey).(zerolog.Logger)
	if !ok {
		return nil, fmt.Errorf("BrowserServer: invalid logger type: %T", ctx.Value(comm.MoLingLoggerKey))
	}
	loggerNameHook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
		e.Str("Service", string(BrowserServerName))
	})
	bs := &BrowserServer{
		MLService: abstract.NewMLService(ctx, logger.Hook(loggerNameHook), globalConf),
		config:    bc,
	}

	err := bs.InitResources()
	if err != nil {
		return nil, err
	}

	return bs, nil
}

// Init initializes the browser server by creating a new context.
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: %w", err)
	}
	err = utils.CreateDirectory(bs.config.DataPath)
	if err != nil {
		return fmt.Errorf("failed to create data directory: %w", err)
	}

	// Create a new context for the browser
	opts := append(
		chromedp.DefaultExecAllocatorOptions[:],
		chromedp.UserAgent(bs.config.UserAgent),
		chromedp.Flag("lang", bs.config.DefaultLanguage),
		chromedp.Flag("disable-blink-features", "AutomationControlled"),
		chromedp.Flag("enable-automation", false),
		chromedp.Flag("disable-features", "Translate"),
		chromedp.Flag("hide-scrollbars", false),
		chromedp.Flag("mute-audio", true),
		//chromedp.Flag("no-sandbox", true),
		chromedp.Flag("disable-infobars", true),
		chromedp.Flag("disable-extensions", true),
		chromedp.Flag("CommandLineFlagSecurityWarningsEnabled", false),
		chromedp.Flag("disable-notifications", true),
		chromedp.Flag("disable-dev-shm-usage", true),
		chromedp.Flag("autoplay-policy", "user-gesture-required"),
		chromedp.CombinedOutput(bs.Logger),
		// (1920, 1080), (1366, 768), (1440, 900), (1280, 800)
		chromedp.WindowSize(1280, 800),
		chromedp.UserDataDir(bs.config.BrowserDataPath),
		chromedp.IgnoreCertErrors,
	)

	// headless mode
	if bs.config.Headless {
		opts = append(opts, chromedp.Flag("headless", true))
		opts = append(opts, chromedp.Flag("disable-gpu", true))
		opts = append(opts, chromedp.Flag("disable-webgl", true))
	}

	bs.Context, bs.cancelAlloc = chromedp.NewExecAllocator(context.Background(), opts...)

	bs.Context, bs.cancelChrome = chromedp.NewContext(bs.Context,
		chromedp.WithErrorf(bs.Logger.Error().Msgf),
		chromedp.WithDebugf(bs.Logger.Debug().Msgf),
	)

	pe := abstract.PromptEntry{
		PromptVar: mcp.Prompt{
			Name:        "browser_prompt",
			Description: "Get the relevant functions and prompts of the Browser MCP Server",
			//Arguments:   make([]mcp.PromptArgument, 0),
		},
		HandlerFunc: bs.handlePrompt,
	}
	bs.AddPrompt(pe)
	bs.AddTool(mcp.NewTool(
		"browser_navigate",
		mcp.WithDescription("Navigate to a URL"),
		mcp.WithString("url",
			mcp.Description("URL to navigate to"),
			mcp.Required(),
		),
	), bs.handleNavigate)
	bs.AddTool(mcp.NewTool(
		"browser_screenshot",
		mcp.WithDescription("Take a screenshot of the current page or a specific element"),
		mcp.WithString("name",
			mcp.Description("Name for the screenshot"),
			mcp.Required(),
		),
		mcp.WithString("selector",
			mcp.Description("CSS selector for element to screenshot"),
		),
		mcp.WithNumber("width",
			mcp.Description("Width in pixels (default: 1700)"),
		),
		mcp.WithNumber("height",
			mcp.Description("Height in pixels (default: 1100)"),
		),
	), bs.handleScreenshot)
	bs.AddTool(mcp.NewTool(
		"browser_click",
		mcp.WithDescription("Click an element on the page"),
		mcp.WithString("selector",
			mcp.Description("CSS selector for element to click"),
			mcp.Required(),
		),
	), bs.handleClick)
	bs.AddTool(mcp.NewTool(
		"browser_fill",
		mcp.WithDescription("Fill out an input field"),
		mcp.WithString("selector",
			mcp.Description("CSS selector for input field"),
			mcp.Required(),
		),
		mcp.WithString("value",
			mcp.Description("Value to fill"),
			mcp.Required(),
		),
	), bs.handleFill)
	bs.AddTool(mcp.NewTool(
		"browser_select",
		mcp.WithDescription("Select an element on the page with Select tag"),
		mcp.WithString("selector",
			mcp.Description("CSS selector for element to select"),
			mcp.Required(),
		),
		mcp.WithString("value",
			mcp.Description("Value to select"),
			mcp.Required(),
		),
	), bs.handleSelect)
	bs.AddTool(mcp.NewTool(
		"browser_hover",
		mcp.WithDescription("Hover an element on the page"),
		mcp.WithString("selector",
			mcp.Description("CSS selector for element to hover"),
			mcp.Required(),
		),
	), bs.handleHover)
	bs.AddTool(mcp.NewTool(
		"browser_evaluate",
		mcp.WithDescription("Execute JavaScript in the browser console"),
		mcp.WithString("script",
			mcp.Description("JavaScript code to execute"),
			mcp.Required(),
		),
	), bs.handleEvaluate)

	bs.AddTool(mcp.NewTool(
		"browser_debug_enable",
		mcp.WithDescription("Enable JavaScript debugging"),
		mcp.WithBoolean("enabled",
			mcp.Description("Enable or disable debugging"),
			mcp.Required(),
		),
	), bs.handleDebugEnable)

	bs.AddTool(mcp.NewTool(
		"browser_set_breakpoint",
		mcp.WithDescription("Set a JavaScript breakpoint"),
		mcp.WithString("url",
			mcp.Description("URL of the script"),
			mcp.Required(),
		),
		mcp.WithNumber("line",
			mcp.Description("Line number"),
			mcp.Required(),
		),
		mcp.WithNumber("column",
			mcp.Description("Column number (optional)"),
		),
		mcp.WithString("condition",
			mcp.Description("Breakpoint condition (optional)"),
		),
	), bs.handleSetBreakpoint)

	bs.AddTool(mcp.NewTool(
		"browser_remove_breakpoint",
		mcp.WithDescription("Remove a JavaScript breakpoint"),
		mcp.WithString("breakpointId",
			mcp.Description("Breakpoint ID to remove"),
			mcp.Required(),
		),
	), bs.handleRemoveBreakpoint)

	bs.AddTool(mcp.NewTool(
		"browser_pause",
		mcp.WithDescription("Pause JavaScript execution"),
	), bs.handlePause)

	bs.AddTool(mcp.NewTool(
		"browser_resume",
		mcp.WithDescription("Resume JavaScript execution"),
	), bs.handleResume)

	bs.AddTool(mcp.NewTool(
		"browser_get_callstack",
		mcp.WithDescription("Get current call stack when paused"),
	), bs.handleGetCallstack)
	return nil
}

// init initializes the browser server by creating the user data directory.
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: %w", err)
	}

	// Check if the directory exists, if it does, we can reuse it
	if err == nil {
		//  判断浏览器运行锁
		singletonLock := filepath.Join(userDataDir, "SingletonLock")
		_, err = os.Stat(singletonLock)
		if err == nil {
			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: %s", err.Error())
			}
		}
		return nil
	}
	// Create the directory
	err = os.MkdirAll(userDataDir, 0755)
	if err != nil {
		return fmt.Errorf("failed to create user data directory: %w", err)
	}
	return nil
}

func (bs *BrowserServer) handlePrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	// 处理浏览器提示
	return &mcp.GetPromptResult{
		Description: "",
		Messages: []mcp.PromptMessage{
			{
				Role: mcp.RoleUser,
				Content: mcp.TextContent{
					Type: "text",
					Text: bs.config.prompt,
				},
			},
		},
	}, nil
}

// handleNavigate handles the navigation action.
func (bs *BrowserServer) handleNavigate(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	url, ok := args["url"].(string)
	if !ok {
		return nil, fmt.Errorf("url must be a string")
	}

	err := chromedp.Run(bs.Context, chromedp.Navigate(url))
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("failed to navigate: %s", err.Error())), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Navigated to %s", url)), nil
}

// handleScreenshot handles the screenshot action.
func (bs *BrowserServer) handleScreenshot(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	name, ok := args["name"].(string)
	if !ok {
		return mcp.NewToolResultError("name must be a string"), nil
	}
	selector, _ := args["selector"].(string)
	width, _ := args["width"].(int)
	height, _ := args["height"].(int)
	if width == 0 {
		width = 1280
	}
	if height == 0 {
		height = 800
	}
	var buf []byte
	var err error
	runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second)
	defer cancelFunc()
	if selector == "" {
		err = chromedp.Run(runCtx, chromedp.FullScreenshot(&buf, 90))
	} else {
		err = chromedp.Run(bs.Context, chromedp.Screenshot(selector, &buf, chromedp.NodeVisible))
	}
	if 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: %s", err.Error())), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Screenshot saved to:%s", newName)), nil
}

// handleClick handles the click action on a specified element.
func (bs *BrowserServer) handleClick(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	selector, ok := args["selector"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("selector must be a string:%v", selector)), nil
	}
	runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second)
	defer cancelFunc()
	err := chromedp.Run(runCtx,
		chromedp.WaitReady("body", chromedp.ByQuery), // 等待页面就绪
		chromedp.WaitVisible(selector, chromedp.ByQuery),
		chromedp.Click(selector, chromedp.NodeVisible),
	)
	if err != 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
}

// handleFill handles the fill action on a specified input field.
func (bs *BrowserServer) handleFill(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	selector, ok := args["selector"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("failed to fill selector:%v", args["selector"])), nil
	}

	value, ok := args["value"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("failed to fill input field: %v, selector:%v", args["value"], selector)), nil
	}

	runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second)
	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: %s", err.Error())), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Filled input %s with value %s", selector, value)), nil
}

func (bs *BrowserServer) handleSelect(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	selector, ok := args["selector"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("failed to select selector:%v", args["selector"])), nil
	}
	value, ok := args["value"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("failed to select value:%v", args["value"])), nil
	}
	runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second)
	defer cancelFunc()
	err := chromedp.Run(runCtx, chromedp.SetValue(selector, value, chromedp.NodeVisible))
	if err != 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
}

// handleHover handles the hover action on a specified element.
func (bs *BrowserServer) handleHover(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	selector, ok := args["selector"].(string)
	if !ok {
		return mcp.NewToolResultError(fmt.Sprintf("selector must be a string:%v", selector)), nil
	}
	var res bool
	runCtx, cancelFunc := context.WithTimeout(bs.Context, time.Duration(bs.config.SelectorQueryTimeout)*time.Second)
	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: %s", err.Error()).Error()), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Hovered over element %s, result:%t", selector, res)), nil
}

func (bs *BrowserServer) handleEvaluate(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	args := request.GetArguments()
	script, ok := args["script"].(string)
	if !ok {
		return mcp.NewToolResultError("script must be a string"), nil
	}
	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: %s", err.Error()).Error()), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf("Script executed successfully: %v", result)), nil
}

func (bs *BrowserServer) Close() error {
	bs.Logger.Debug().Msg("Closing browser server")
	bs.cancelAlloc()
	bs.cancelChrome()
	// Cancel the context to stop the browser
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	return chromedp.Cancel(ctx)
}

// Config returns the configuration of the service as a string.
func (bs *BrowserServer) Config() string {
	cfg, err := json.Marshal(bs.config)
	if err != nil {
		bs.Logger.Err(err).Msg("failed to marshal config")
		return "{}"
	}
	return string(cfg)
}

func (bs *BrowserServer) Name() comm.MoLingServerType {
	return BrowserServerName
}

// LoadConfig loads the configuration from a JSON object.
func (bs *BrowserServer) LoadConfig(jsonData map[string]any) error {
	err := utils.MergeJSONToStruct(bs.config, jsonData)
	if err != nil {
		return err
	}
	return bs.config.Check()
}

```
Page 1/2FirstPrevNextLast