#
tokens: 5895/50000 13/13 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .editorconfig
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .golangci.yml
├── build
│   ├── charcount
│   ├── mozisu-mcp-server
│   └── webserver
├── cmd
│   ├── charcount
│   │   ├── charcount
│   │   ├── main.go
│   │   └── output.txt
│   ├── direct_test
│   │   └── main.go
│   ├── mcpserver
│   │   └── main.go
│   ├── test
│   │   ├── mcp_client.go
│   │   └── test_client.go
│   └── webserver
│       ├── main.go
│       └── webserver
├── configs
│   └── config.json
├── go.mod
├── go.sum
├── image-1.png
├── image.png
├── internal
│   └── server
│       └── server.go
├── LICENSE
├── main_test.go
├── main.go
├── pkg
│   └── charcount
│       ├── charcount_test.go
│       └── charcount.go
├── README.md
├── scripts
│   └── build.sh
├── Taskfile.yml
└── test
    └── integration_test.go
```

# Files

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

```
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool
*.out

# Dependency directories
vendor/

# Go workspace file
go.work

# IDE specific files
.idea/
.vscode/
*.swp
*.swo

# OS specific files
.DS_Store
Thumbs.db

# Build output
/charcount
/webserver
/mozisu-mcp-server

# Debug files
__debug_bin

```

--------------------------------------------------------------------------------
/cmd/charcount/output.txt:
--------------------------------------------------------------------------------

```
DEBUG: Starting character count

Text: hello
Total characters: 5
Non-whitespace characters: 5
----------------------------
DEBUG: Completed character count for 'hello'

```

--------------------------------------------------------------------------------
/configs/config.json:
--------------------------------------------------------------------------------

```json
{
  "server": {
    "mcp": {
      "name": "Mozisu MCP Server",
      "version": "1.0.0"
    },
    "web": {
      "port": 8080,
      "timeouts": {
        "read": 10,
        "write": 10,
        "idle": 120
      }
    }
  },
  "features": {
    "debug": false,
    "logging": true
  }
}

```

--------------------------------------------------------------------------------
/pkg/charcount/charcount.go:
--------------------------------------------------------------------------------

```go
// Package charcount provides functionality for counting characters in text
package charcount

import (
	"unicode"
)

// Result represents the result of a character count operation
type Result struct {
	Text               string
	TotalCount         int
	NonWhitespaceCount int
}

// Count counts the total and non-whitespace characters in the given text
// It properly handles multi-byte characters like Japanese text and emojis
func Count(text string) Result {
	// Count total characters (including spaces)
	totalCount := len([]rune(text))

	// Count non-whitespace characters
	nonWhitespaceCount := 0
	for _, r := range text {
		if !unicode.IsSpace(r) {
			nonWhitespaceCount++
		}
	}

	return Result{
		Text:               text,
		TotalCount:         totalCount,
		NonWhitespaceCount: nonWhitespaceCount,
	}
}

```

--------------------------------------------------------------------------------
/cmd/direct_test/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"fmt"
	"unicode"
)

func main() {
	// Test cases
	testCases := []string{
		"Hello, World!",
		"こんにちは世界!",
		"Hello 世界 😊🚀",
		"スペースを 含む 日本語 テキスト with English and 絵文字😊",
		"1234567890",
		"    Spaces at the beginning and end    ",
		"", // Empty string
	}

	fmt.Println("Character Count Test Results:")
	fmt.Println("============================")

	for _, text := range testCases {
		// Count total characters (including spaces)
		totalCount := len([]rune(text))

		// Count non-whitespace characters
		nonWhitespaceCount := 0
		for _, r := range text {
			if !unicode.IsSpace(r) {
				nonWhitespaceCount++
			}
		}

		fmt.Printf("\nText: %s\n", text)
		fmt.Printf("Total characters: %d\n", totalCount)
		fmt.Printf("Non-whitespace characters: %d\n", nonWhitespaceCount)
		fmt.Println("----------------------------")
	}

	fmt.Println("\nTest completed successfully!")
}

```

--------------------------------------------------------------------------------
/test/integration_test.go:
--------------------------------------------------------------------------------

```go
package test

import (
	"testing"

	"github.com/Atotti/mozisu-mcp-server/pkg/charcount"
)

// TestIntegration tests the integration between different components
func TestIntegration(t *testing.T) {
	// テストケース
	testCases := []struct {
		name                  string
		input                 string
		expectedTotal         int
		expectedNonWhitespace int
	}{
		{
			name:                  "Integration test - ASCII",
			input:                 "Hello, World!",
			expectedTotal:         13,
			expectedNonWhitespace: 12,
		},
		{
			name:                  "Integration test - Japanese",
			input:                 "こんにちは世界!",
			expectedTotal:         8,
			expectedNonWhitespace: 8,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// 共通パッケージを使用して文字数をカウント
			result := charcount.Count(tc.input)

			// 結果を検証
			if result.TotalCount != tc.expectedTotal {
				t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
			}
			if result.NonWhitespaceCount != tc.expectedNonWhitespace {
				t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/pkg/charcount/charcount_test.go:
--------------------------------------------------------------------------------

```go
package charcount

import (
	"testing"
)

func TestCount(t *testing.T) {
	// テストケース
	testCases := []struct {
		name                  string
		input                 string
		expectedTotal         int
		expectedNonWhitespace int
	}{
		{
			name:                  "ASCII only",
			input:                 "Hello, World!",
			expectedTotal:         13,
			expectedNonWhitespace: 12,
		},
		{
			name:                  "Japanese characters",
			input:                 "こんにちは世界!",
			expectedTotal:         8,
			expectedNonWhitespace: 8,
		},
		{
			name:                  "Mixed with emojis",
			input:                 "Hello 世界 😊🚀",
			expectedTotal:         11,
			expectedNonWhitespace: 9,
		},
		{
			name:                  "Empty string",
			input:                 "",
			expectedTotal:         0,
			expectedNonWhitespace: 0,
		},
		{
			name:                  "Whitespace only",
			input:                 "   \t\n",
			expectedTotal:         5,
			expectedNonWhitespace: 0,
		},
	}

	// 各テストケースを実行
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// 文字数をカウント
			result := Count(tc.input)

			// 結果を検証
			if result.TotalCount != tc.expectedTotal {
				t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
			}
			if result.NonWhitespaceCount != tc.expectedNonWhitespace {
				t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/cmd/charcount/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/Atotti/mozisu-mcp-server/pkg/charcount"
)

func main() {
	// Parse command-line flags
	interactive := flag.Bool("i", false, "Run in interactive mode")
	flag.Parse()

	// Get the text to count
	var text string

	if *interactive {
		// Interactive mode
		fmt.Println("Character Count Tool")
		fmt.Println("===================")
		fmt.Println("Enter text to count characters (Ctrl+D to exit):")

		scanner := bufio.NewScanner(os.Stdin)
		for {
			fmt.Print("> ")
			if !scanner.Scan() {
				break
			}

			text = scanner.Text()
			printCharacterCounts(text)
		}

		if err := scanner.Err(); err != nil {
			fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
			os.Exit(1)
		}
	} else {
		// Command-line mode
		args := flag.Args()
		if len(args) == 0 {
			fmt.Println("Please provide text to count characters.")
			fmt.Println("Usage: charcount [text] or charcount -i for interactive mode")
			os.Exit(1)
		}

		text = strings.Join(args, " ")
		printCharacterCounts(text)
	}
}

func printCharacterCounts(text string) {
	fmt.Println("DEBUG: Starting character count")

	// 共通パッケージを使用して文字数をカウント
	result := charcount.Count(text)

	fmt.Printf("\nText: %s\n", result.Text)
	fmt.Printf("Total characters: %d\n", result.TotalCount)
	fmt.Printf("Non-whitespace characters: %d\n", result.NonWhitespaceCount)
	fmt.Println("----------------------------")

	// Write to stderr as well for debugging
	os.Stderr.WriteString(fmt.Sprintf("DEBUG: Completed character count for '%s'\n", text))
}

```

--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"testing"

	"github.com/Atotti/mozisu-mcp-server/pkg/charcount"
)

// TestCountCharacters tests the character counting functionality
func TestCountCharacters(t *testing.T) {
	// テストケース
	testCases := []struct {
		name                  string
		input                 string
		expectedTotal         int
		expectedNonWhitespace int
	}{
		{
			name:                  "ASCII only",
			input:                 "Hello, World!",
			expectedTotal:         13,
			expectedNonWhitespace: 12,
		},
		{
			name:                  "Japanese characters",
			input:                 "こんにちは世界!",
			expectedTotal:         8,
			expectedNonWhitespace: 8,
		},
		{
			name:                  "Mixed with emojis",
			input:                 "Hello 世界 😊🚀",
			expectedTotal:         11,
			expectedNonWhitespace: 9,
		},
		{
			name:                  "Empty string",
			input:                 "",
			expectedTotal:         0,
			expectedNonWhitespace: 0,
		},
		{
			name:                  "Whitespace only",
			input:                 "   \t\n",
			expectedTotal:         5,
			expectedNonWhitespace: 0,
		},
	}

	// 各テストケースを実行
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// 共通パッケージを使用して文字数をカウント
			result := charcount.Count(tc.input)

			// 結果を検証
			if result.TotalCount != tc.expectedTotal {
				t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
			}
			if result.NonWhitespaceCount != tc.expectedNonWhitespace {
				t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.23'
          cache: true

      - name: Install golangci-lint
        run: |
          curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2

      - name: Install Task
        run: |
          sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Run linter
        run: task lint

  test:
    name: Test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        go-version: ['1.21', '1.22', '1.23']
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Go ${{ matrix.go-version }}
        uses: actions/setup-go@v4
        with:
          go-version: ${{ matrix.go-version }}
          cache: true

      - name: Install Task
        run: |
          sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Run tests
        run: task test

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [lint, test]
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.23'
          cache: true

      - name: Install Task
        run: |
          sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Build applications
        run: task build

      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: binaries
          path: bin/

```

--------------------------------------------------------------------------------
/cmd/test/test_client.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"encoding/json"
	"fmt"
	"os/exec"
)

// JSON-RPC request structure
type Request struct {
	JsonRPC string      `json:"jsonrpc"`
	ID      int         `json:"id"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params"`
}

// CallToolRequest parameters
type CallToolParams struct {
	Name      string                 `json:"name"`
	Arguments map[string]interface{} `json:"arguments"`
}

func RunTest() {
	// Start the MCP server as a subprocess
	cmd := exec.Command("go", "run", "../../main.go")

	// Set up pipes for stdin and stdout
	stdin, err := cmd.StdinPipe()
	if err != nil {
		fmt.Println("Error creating stdin pipe:", err)
		return
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Println("Error creating stdout pipe:", err)
		return
	}

	// Start the server
	if err := cmd.Start(); err != nil {
		fmt.Println("Error starting server:", err)
		return
	}

	// Create a request to call the count_characters tool
	request := Request{
		JsonRPC: "2.0",
		ID:      1,
		Method:  "call_tool",
		Params: CallToolParams{
			Name: "count_characters",
			Arguments: map[string]interface{}{
				"text": "こんにちは世界!Hello World! 😊🚀",
			},
		},
	}

	// Marshal the request to JSON
	requestJSON, err := json.Marshal(request)
	if err != nil {
		fmt.Println("Error marshaling request:", err)
		return
	}

	// Add a newline to the request
	requestJSON = append(requestJSON, '\n')

	// Send the request to the server
	_, err = stdin.Write(requestJSON)
	if err != nil {
		fmt.Println("Error writing to stdin:", err)
		return
	}

	// Read the response
	buf := make([]byte, 4096)
	n, err := stdout.Read(buf)
	if err != nil {
		fmt.Println("Error reading from stdout:", err)
		return
	}

	// Print the response
	fmt.Println("Response from server:")
	fmt.Println(string(buf[:n]))

	// Test with another example
	request.ID = 2
	request.Method = "call_tool"
	request.Params = CallToolParams{
		Name: "count_characters",
		Arguments: map[string]interface{}{
			"text": "スペースを 含む 日本語 テキスト with English and 絵文字😊",
		},
	}

	// Marshal the request to JSON
	requestJSON, err = json.Marshal(request)
	if err != nil {
		fmt.Println("Error marshaling request:", err)
		return
	}

	// Add a newline to the request
	requestJSON = append(requestJSON, '\n')

	// Send the request to the server
	_, err = stdin.Write(requestJSON)
	if err != nil {
		fmt.Println("Error writing to stdin:", err)
		return
	}

	// Read the response
	n, err = stdout.Read(buf)
	if err != nil {
		fmt.Println("Error reading from stdout:", err)
		return
	}

	// Print the response
	fmt.Println("\nResponse from server (second test):")
	fmt.Println(string(buf[:n]))

	// Kill the server process
	cmd.Process.Kill()
}

```

--------------------------------------------------------------------------------
/cmd/test/mcp_client.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os/exec"
	"time"
)

// MCP protocol request
type MCPRequest struct {
	JsonRPC string      `json:"jsonrpc"`
	ID      int         `json:"id"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params"`
}

// MCP protocol response
type MCPResponse struct {
	JsonRPC string          `json:"jsonrpc"`
	ID      int             `json:"id"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	} `json:"error,omitempty"`
}

func main() {
	fmt.Println("Starting MCP protocol test...")

	// Start the MCP server
	cmd := exec.Command("go", "run", "../../main.go")

	stdin, _ := cmd.StdinPipe()
	stdout, _ := cmd.StdoutPipe()

	cmd.Start()

	// Give the server a moment to start up
	time.Sleep(500 * time.Millisecond)

	// Create a reader for the stdout
	reader := bufio.NewReader(stdout)

	// First, try to list available tools
	listToolsRequest := MCPRequest{
		JsonRPC: "2.0",
		ID:      1,
		Method:  "listTools",
		Params:  struct{}{},
	}

	// Send the request
	requestJSON, _ := json.Marshal(listToolsRequest)
	fmt.Printf("Request: %s\n", requestJSON)
	io.WriteString(stdin, string(requestJSON)+"\n")

	// Read the response
	responseStr, _ := reader.ReadString('\n')
	fmt.Printf("Response: %s\n\n", responseStr)

	// Try to parse the response
	var response MCPResponse
	json.Unmarshal([]byte(responseStr), &response)

	// If we got an error, try some other common method names
	if response.Error != nil {
		methods := []string{
			"list_tools",
			"tools.list",
			"get_tools",
			"tools",
		}

		for i, method := range methods {
			listToolsRequest.ID = i + 2
			listToolsRequest.Method = method

			requestJSON, _ := json.Marshal(listToolsRequest)
			fmt.Printf("Trying method: %s\n", method)
			fmt.Printf("Request: %s\n", requestJSON)

			io.WriteString(stdin, string(requestJSON)+"\n")
			responseStr, _ := reader.ReadString('\n')
			fmt.Printf("Response: %s\n\n", responseStr)
		}
	}

	// Now try to call our count_characters tool directly
	callToolRequest := MCPRequest{
		JsonRPC: "2.0",
		ID:      10,
		Method:  "callTool",
		Params: map[string]interface{}{
			"name": "count_characters",
			"arguments": map[string]interface{}{
				"text": "こんにちは世界!Hello World! 😊🚀",
			},
		},
	}

	// Try different method names for calling a tool
	callMethods := []string{
		"callTool",
		"call_tool",
		"tool.call",
		"execute",
		"run",
	}

	for i, method := range callMethods {
		callToolRequest.ID = 10 + i
		callToolRequest.Method = method

		requestJSON, _ := json.Marshal(callToolRequest)
		fmt.Printf("Trying method: %s\n", method)
		fmt.Printf("Request: %s\n", requestJSON)

		io.WriteString(stdin, string(requestJSON)+"\n")
		responseStr, _ := reader.ReadString('\n')
		fmt.Printf("Response: %s\n\n", responseStr)
	}

	// Kill the server
	cmd.Process.Kill()
	fmt.Println("Test completed.")
}

```

--------------------------------------------------------------------------------
/cmd/webserver/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/Atotti/mozisu-mcp-server/internal/server"
	"github.com/Atotti/mozisu-mcp-server/pkg/charcount"
)

func main() {
	// Define HTTP handlers
	mux := http.NewServeMux()
	mux.HandleFunc("/", handleHome)
	mux.HandleFunc("/count", handleCount)

	// サーバー設定
	config := server.DefaultConfig()
	config.Port = 8080

	// サーバーを起動
	log.Println("Starting character count web server...")
	if err := server.RunHTTPServer(mux, config); err != nil {
		log.Fatalf("Server error: %v\n", err)
	}
}

// handleHome serves the home page with a simple form
func handleHome(w http.ResponseWriter, r *http.Request) {
	html := `
<!DOCTYPE html>
<html>
<head>
    <title>Character Count Tool</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        textarea {
            width: 100%;
            height: 150px;
            margin-bottom: 10px;
        }
        button {
            padding: 8px 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        #result {
            margin-top: 20px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            display: none;
        }
    </style>
</head>
<body>
    <h1>Character Count Tool</h1>
    <p>Enter text below to count characters:</p>

    <textarea id="text" placeholder="Enter text here..."></textarea>
    <button onclick="countCharacters()">Count Characters</button>

    <div id="result"></div>

    <script>
        function countCharacters() {
            const text = document.getElementById('text').value;

            fetch('/count', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ text: text }),
            })
            .then(response => response.json())
            .then(data => {
                const resultDiv = document.getElementById('result');
                resultDiv.innerHTML =
                    '<h2>Results:</h2>' +
                    '<p><strong>Text:</strong> ' + data.text + '</p>' +
                    '<p><strong>Total characters:</strong> ' + data.totalCount + '</p>' +
                    '<p><strong>Non-whitespace characters:</strong> ' + data.nonWhitespaceCount + '</p>';
                resultDiv.style.display = 'block';
            })
            .catch(error => {
                console.error('Error:', error);
                alert('An error occurred while counting characters.');
            });
        }
    </script>
</body>
</html>
`
	w.Header().Set("Content-Type", "text/html")
	if _, err := w.Write([]byte(html)); err != nil {
		log.Printf("Error writing response: %v", err)
	}
}

// handleCount processes the character count request
func handleCount(w http.ResponseWriter, r *http.Request) {
	// Only allow POST requests
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Parse the request body
	var request struct {
		Text string `json:"text"`
	}

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&request); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	// 共通パッケージを使用して文字数をカウント
	result := charcount.Count(request.Text)

	// Create the response
	response := struct {
		Text               string `json:"text"`
		TotalCount         int    `json:"totalCount"`
		NonWhitespaceCount int    `json:"nonWhitespaceCount"`
	}{
		Text:               result.Text,
		TotalCount:         result.TotalCount,
		NonWhitespaceCount: result.NonWhitespaceCount,
	}

	// Send the response
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(response); err != nil {
		log.Printf("Error encoding JSON response: %v", err)
	}
}

```