# 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)
}
}
```