🎯

Swiftで年末ジャンボシュミレーターMCPサーバーを作ってみた

に公開

こんにちは。iOSエンジニアのTomo🍎です。

皆さんは年末ジャンボ買いましたか?今年の1等前後賞は10億円だったようですね!
私は現実的に期待値が購入金額を下回るので購入しませんでした。😭
とはいえ、「もし1万枚の宝くじを買ったら、1枚ぐらい当たるのではないか。」という疑問は湧いてきます。そこで今回、年末ジャンボ宝くじのシミュレーターを作って、実際に検証してみることにしました。
今回はMCPを使って、Claudeから直接呼び出せるツールとして実装し、会話の中でシミュレーション結果が返ってくるようしていきたいと思います。

MCPとは

LLMアプリケーションと外部データソースおよびツールとのシームレスな統合を可能にするオープンプロトコルです。
簡単に説明すると「Claudeに新しい能力を追加できるプラグインシステム」のようなものです。例えば、データベースにアクセスしたり、カレンダーを操作したり、今回のように宝くじシミュレーションを実行したりすることができ、自分で作ったプログラムをClaudeから呼び出せるようになります。
今回はMCP公式のSwiftSDKを用いて実装します。

https://github.com/modelcontextprotocol

swift-sdkのインストール

Package.swiftでswift-sdkを依存関係として追加します。
後にswift buildコマンドで実行可能バイナリに変換するのでexecutableTargetで実行可能なターゲットを定義します。

// swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "LotteryMCPServer",
    platforms: [.macOS(.v13)],
    dependencies: [
        .package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.1.0")
    ],
    targets: [
        .executableTarget(
            name: "LotteryMCPServer",
            dependencies: [
                .product(name: "MCP", package: "swift-sdk")
            ]
        ),
    ]
)

Toolsの設定

Toolsとは

「LLM(Claude)が実行できる機能のリスト」です。LLMは「ツール」を使って様々なアクションを実行できます。

ツールのできること
  • データベースへの問い合わせ: ユーザー情報の検索など
  • APIの呼び出し: 天気情報の取得、メール送信など
  • 計算の実行: 複雑な数式の計算、シミュレーションなど

実装

  1. シミュレーターの準備
  • 宝くじシミュレーション機能を初期化
  1. MCPサーバーの作成
  • サーバー名とバージョンを設定
  • ツール機能を有効化
  1. ツール一覧ハンドラーの登録
  • クライアントが利用可能なツール一覧を要求したときに、どのツールがあるかを返す
  1. ツール実行ハンドラーの登録
  • クライアントがツールを呼び出したときに、実際にシミュレーションを実行して結果を返す
  1. サーバーの起動と待機
  • 標準入出力で通信開始
  • Claude Desktopからのリクエストを待つ
   static func main() async throws {
        // 1. シミュレーターの準備
        let simulator = LotterySimulator()
        // 2. MCPサーバーの作成
        let server = Server(
            name: "lottery-simulator",
            version: "1.0.0",
            capabilities: Server.Capabilities(
                tools: Server.Capabilities.Tools()
            )
        )

        // 3. ツール一覧ハンドラーの登録
        await server.withMethodHandler(ListTools.self) { _ in
            let tool = Tool(
                name: "simulate_lottery",
                description: "年末ジャンボ宝くじのシミュレーションを実行します。指定した枚数の宝くじを購入した場合の当選結果と収支を返します。",
                inputSchema: [
                    "type": "object",
                    "properties": [
                        "tickets": [
                            "type": "integer",
                            "description": "購入する宝くじの枚数",
                            "minimum": 1
                        ]
                    ],
                    "required": ["tickets"]
                ]
            )

            return ListTools.Result(tools: [tool])
        }

        // 4. ツール実行ハンドラーの登録
        await server.withMethodHandler(CallTool.self) { params in
            guard params.name == "simulate_lottery" else {
                throw MCPError.invalidRequest("Unknown tool: \(params.name)")
            }

            guard let arguments = params.arguments else {
                throw MCPError.invalidParams("Missing arguments")
            }

            guard let ticketsValue = arguments["tickets"] else {
                throw MCPError.invalidParams("Missing tickets parameter")
            }

            let tickets: Int
            if let intValue = ticketsValue.intValue {
                tickets = intValue
            } else if let doubleValue = ticketsValue.doubleValue {
                tickets = Int(doubleValue)
            } else {
                throw MCPError.invalidParams("tickets parameter must be a number")
            }

            guard tickets > 0 else {
                throw MCPError.invalidParams("tickets must be greater than 0")
            }

            let result = await simulator.simulate(tickets: tickets)

            var output = """
            年末ジャンボ宝くじシミュレーション結果
            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            購入枚数: \(result.ticketsPurchased)
            購入金額: ¥\(formatNumber(result.totalCost))

            【当選結果】
            """

            if result.results.isEmpty {
                output += "\n残念ながら当選はありませんでした"
            } else {
                for lotteryResult in result.results {
                     output += "\n\(lotteryResult.tier)(\(formatNumber(lotteryResult.prize))円) \(lotteryResult.wins)本"
                }
            }

            output += """


            【収支】
            総当選金額: ¥\(formatNumber(result.totalWinnings))
            収支: \(result.netProfit >= 0 ? "+" : "")¥\(formatNumber(result.netProfit))
            還元率: \(String(format: "%.2f", result.returnRate))%
            """

            return CallTool.Result(
                content: [.text(output)]
            )
        }
        // 5. サーバーの起動と待機
        try await server.start(transport: StdioTransport())
        await server.waitUntilCompleted()
    }

実行手順

ビルドを作成

Swiftのソースコードをコンパイルして、.build/debug/LotteryMCPServerに実行可能バイナリを作成します。

swift build

Claude Desktopの設定

Claude DesktopのMCPサーバー設定ファイル(~/Library/Application Support/Claude/claude_desktop_config.json)に下記を追加します。

{
  "mcpServers": {
    "lottery-simulator": {
      "command": "LotteryMCPServerへのPath/.build/debug/LotteryMCPServer"
    }
  }
}

Claude Desktopで実行

実際にClaude Desktopで宝くじのシミレーションをしましょう。

こちらからClaude Desktopをインストールできます。
https://claude.com/ja-jp/download

今回1万枚宝くじを購入してみました。購入額だと300万円です💰💰💰
結果は....
シミュレーション結果のスクリーンショット

  • 総当選金額:88万200円
  • 差し引き:-211万9800円の損失
  • 還元率:29.34%

大赤字でした💸💸💸 1万枚買っても5等の1万円が最高当選額だなんて😭
なかなか現実は厳しいですね。

実装コード

Github上にコードを公開しています。
https://github.com/tomoakiWeb/LotteryMCPServer

まとめ

MCPを使って、年末ジャンボ宝くじシミュレーターを作成しました。実際に1万枚(300万円分)をシミュレーションした結果、還元率29.34%という現実を知ることができました😭

Swiftで作成できるメリットとしてiOSエンジニアが書きやすい点やSwift製のライブラリが使える点、macOSのAPIが使える点が挙げられると思います。今回の実装を参考に、ぜひ独自のMCPサーバーを作成して、Claudeをさらに強力なツールにカスタマイズしてみてください!

Money Forward Developers

Discussion