🎲

MCPを使ってLLM に指定した文字数の文章を出力させる

2025/04/08に公開

従来より、LLMに「◯◯文字で書いてください」などと指示を出しても、欲しい文字数で返ってこないという経験はないでしょうか。これはLLMが文章を文字単位ではなく、トークン単位で処理していることから、仕組み上どうしようもない問題でした。

そこで今回は、 文字数をカウントし返却する MCP(Model Context Protocol)サーバー を開発し、その機能を活用して LLM にピッタリ指定文字数の文章を生成させる 仕組みを作った事例ご紹介します。

↓頑張って文字数を調整するClaude

背景:なぜ文字数カウントが必要なのか

LLMは「トークン」と呼ばれる単位で文章を出力しています。日本語における1トークンは、平均しておよそ1.8〜2.5文字程度ですが、長い場合も短い場合もあります。

そしてLLM 側はあくまでトークン単位で文章を生成しているため、出力したトークンが何文字なのかを知りません。そのため正確に “見た目の文字数” を数えられる仕組みが必要になります。

近年、LLM が外部ツールを呼び出せる仕組みが増えています。そこで、カスタムのツールとして「文字数カウント機能」を組み込み、LLM が都度テキストの長さを計測・調整しながら文章を仕上げるようにしてみよう、というのが狙いです。

MCP(Model Context Protocol)とは

MCP は、LLM と外部ツール間のやりとりを統一的な形式(JSON-RPC)で扱うプロトコルです。
今回は mark3labs/mcp-go を活用して MCP サーバーを実装し、count_characters という簡単なツールを開発しました。

イメージとしては、LLM から MCP サーバーに対して “「count_characters」ツールを呼び出して、このテキストの文字数を教えて” と JSON-RPC でリクエスト → MCP サーバーが文字数を計測してレスポンス → LLM が結果を受け取り再調整、という流れです。

プロジェクト概要

実装部分は軽く紹介します。詳しくはリポジトリを参照してください。

https://github.com/Atotti/mozisu-mcp-server

MCPサーバー

func main() {
  s := server.NewMCPServer("Mozisu MCP Server", "1.0.0")

  // count_charactersツールを定義
  countCharsTool := mcp.NewTool(
    "count_characters",
    mcp.WithDescription("Count characters in text"),
    mcp.WithString("text",
      mcp.Required(),
      mcp.Description("Text to count characters in"),
    ),
  )

  // ツールのハンドラを登録
  s.AddTool(countCharsTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    text, ok := req.Params.Arguments["text"].(string)
    if !ok {
      return nil, errors.New("text must be a string")
    }
    result := charcount.Count(text)
    response := fmt.Sprintf(
      "Text: %s\nTotal characters: %d\nNon-whitespace characters: %d",
      result.Text, result.TotalCount, result.NonWhitespaceCount,
    )
    return mcp.NewToolResultText(response), nil
  })

  // サーバーを起動(stdio で待ち受け)
  if err := server.ServeStdio(s); err != nil {
    fmt.Printf("Server error: %v\n", err)
  }
}

LLM に渡したいツールは以下の 3 ステップで定義しています:

  1. mcp.NewTool でツール名や引数、説明文などを定義
  2. s.AddTool(...) でハンドラ関数(ツールの実際の処理)を登録
  3. 最後に ServeStdio でサーバープロセスを起動し、標準入出力を通して LLM からのリクエストを待ち受ける

文字数カウント部分

日本語や絵文字などのマルチバイト文字に注意して実装をしてあげます。 ルーン単位 でカウントしてあげることで目視に近い文字数を計測できます。

LLM 側での使い方:Claude Desktop との連携例

Claude Desktop の JSON 設定ファイル(例:claude_desktop_config.json)に、以下のような形で MCP サーバーとして登録します。

{
  "mcpServers": {
    "mozisu-mcp-server": {
      "command": "/path_to_repo/mozisu-mcp-server/bin/mozisu-mcp-server",
      "args": []
    }
  }
}

これで、Claude(デスクトップ版)が mozisu-mcp-server を外部ツールとして呼び出せるようになります。
詳しい実行方法はREADMEを参照してください。

これによりLLMはツールを使い自身で文字数を調整して出力を作成することが出来るようになります。すごいですね!自分が作ったツールをLLMが良い感じに使いこなしてくれるところも嬉しいポイントです!

Web インターフェース

ついでに人間の方向けのプロトコルも用意しておきました。

# ビルド済みバイナリ
./bin/webserver
# またはソースから
go run cmd/webserver/main.go

起動すると、http://localhost:8080 にフォーム付きのシンプルな Web ページが立ち上がり、入力テキストの文字数を確認できます。

まとめ

以上、Go 言語で作った文字数カウント MCP サーバーと、それを使った LLM への厳密な文字数指定 の実現例をご紹介しました。LLMの苦手な部分をツールで助けてあげるだけでAgentはどんどん進化していくだろうなと感じました。
またLLMのトークナイザーなどLLMの内部構造について少しでも知っておくとこのような場面で役立つかとと思います。LLMの仕組みについては以下の書籍がおすすめです。

https://gihyo.jp/book/2023/978-4-297-13633-8

もし同様の課題をお持ちでしたら、ぜひ本プロジェクト(Mozisu MCP Server)を試してみてください。参考になれば記事のいいねまたはGitHubにスターを頂けると喜びます。

Discussion