🧩

Kotlin でローカルに MCP サーバーを構築し、Claude Desktop と連携してみた

に公開

はじめに

2025年に入ってから急激に「MCP」という単語を目にするようになりました。少し前にキャッチアップのために公式のKotlin MCPクイックスタートを実践したところ理解を深めることができたので、やや遅くなりましたが、本記事ではその実践内容とMCPの概要について解説します。

MCPの技術概要

Model Context Protocol (MCP)は、LLM(大規模言語モデル)と外部ツールを連携させるための標準化された通信プロトコルです。技術的には以下の特徴があります。

MCP の主要用語

  • ツール(Tool): LLMが呼び出せる機能のこと。例えば「天気予報を取得する」「カレンダーを検索する」などの機能単位。各ツールは名前、説明、入力スキーマ、処理ロジックで構成される。
  • サーバー(Server): ツールの集合を管理し、LLMからのリクエストを受け付けて処理する役割を担うプログラム。
  • MCPクライアント(Client): LLMとユーザーの間に立ち、ユーザーの質問をLLMに伝え、必要に応じてLLMからのツール呼び出しリクエストをサーバーに転送する役割を担うプログラム(Claude Desktopなど)。
  • トランスポート(Transport): LLMとサーバー間の通信経路。MCPでは標準入出力(stdin/stdout)が使われる。
  • メッセージ(Message): LLMとサーバー間でやり取りされるJSON形式のデータ。「hello」「listTools」「callTool」「callToolResult」などの種類がある。
  • 入力スキーマ(Input Schema): 各ツールが受け付ける入力パラメータの定義。JSON Schemaベースで型や制約を指定する。

通信の流れ

Kotlin MCP クイックスタートで MCP を使った実際の通信フローを図示すると、以下のようになります。

この図からわかるように、MCPは単にAIモデルと外部ツールを繋ぐだけではなく、AIが「大谷翔平の所属チームは?」→「その本拠地は?」→「その場所の天気は?」という複数のステップを含む推論を行い、適切なタイミングで適切なツールを呼び出してくれます。

Kotlin MCP SDKについて

Kotlin MCP SDKは公式ライブラリで、特徴は以下です。

  • io.modelcontextprotocol:kotlin-sdkというライブラリ名でMavenリポジトリから利用可能
  • DSL風の構文でツール定義が可能
  • 2025年4月時点ではバージョン0.4.0が最新

実装検証:気象情報ツールの構築

公式チュートリアルに沿って、National Weather Service(アメリカ国立気象局)のAPIを利用した気象情報ツールを構築しました。

環境構築

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.1.10"
    kotlin("plugin.serialization") version "2.1.10"
    id("com.github.johnrengelman.shadow") version "8.1.1"
    application
}

val mcpVersion = "0.4.0"
val slf4jVersion = "2.0.9"
val ktorVersion = "3.1.1"

dependencies {
    /* Kotlin MCP SDK */
    implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
    implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
    implementation("org.slf4j:slf4j-nop:$slf4jVersion")
    testImplementation(kotlin("test"))
}

MCPサーバーの初期化

MCPサーバーインスタンスを作成し、機能を設定する関数です。

// MCPサーバーを実行するメイン関数
fun `run mcp server`() {
    // MCPサーバーのインスタンスを作成
    val server = Server(
        // 実装名とバージョンを指定
        Implementation(
            name = "kotlin-mcp-quickstart", // ツール名は「kotlin-mcp-quickstart」
            version = "1.0.0" // 実装のバージョン
        ),
        // サーバーオプションを設定
        ServerOptions(
            capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
        )
    )

通信の確立

標準入出力を使ってAIモデルと通信するための設定です:

// 標準入出力を使用したサーバー通信用のトランスポートを作成
val transport = StdioServerTransport(
    System.`in`.asInput(), 
    System.out.asSink().buffered()
)

// コルーチンスコープ内でサーバーを実行
runBlocking {
    server.connect(transport)
    val done = Job()
    server.onClose {
        done.complete()
    }
    done.join()
}

ツール定義と登録

緯度・軽度から天気予報を取得するツールを登録します。

// 緯度経度による天気予報取得ツールを登録
server.addTool(
    // ツール名
    name = "get_forecast",
    // ツールの説明
    description =
        """
        特定の緯度/経度の天気予報を取得します。
        """.trimIndent(),
    // 入力スキーマの定義
    inputSchema = Tool.Input(
        // パラメータの定義
        properties = buildJsonObject {
            putJsonObject("latitude") { put("type", "number") }
            putJsonObject("longitude") { put("type", "number") }
        },
        // 必須パラメータの指定
        required = listOf("latitude", "longitude")
    )
) { request ->
    // リクエストから緯度経度を取得
    val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull
    val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull
    // 緯度経度が提供されていない場合はエラーを返す
    if (latitude == null || longitude == null) {
        return@addTool CallToolResult(
            content = listOf(TextContent("'latitude'と'longitude'パラメータが必要です。"))
        )
    }

    // 天気予報を取得する処理
    val forecast = httpClient.getForecast(latitude, longitude)

    // 結果を返す
    CallToolResult(content = forecast.map { TextContent(it) })
}

同様に、公式クイックスタートに記載のコードを実装していきます(コードは省略)。

MCP通信プロトコルの詳細

MCPは以下のようなフェーズとメッセージ構造を持っています。

初期化フェーズ

  • helloメッセージ: バージョン情報、実装名、機能セットの交換
  • listTools/tools: 利用可能なツールとそのスキーマ情報の交換

ツール実行フェーズ

AIからのツール呼び出しリクエスト例:

{
  "type": "callTool",
  "id": "request-123",
  "tool": "get_forecast",
  "arguments": {
    "latitude": 34.0522,
    "longitude": -118.2437
  }
}

ツールからの実行結果レスポンス例:

{
  "type": "callToolResult",
  "id": "response-123",
  "requestId": "request-123",
  "content": [
    {
      "type": "text",
      "text": "Tonight:\nTemperature: 58 F\nWind: 5 mph W\nForecast: Mostly clear, with a low around 58. West wind around 5 mph becoming calm after midnight."
    },
    {
      "type": "text",
      "text": "Sunday:\nTemperature: 75 F\nWind: 5 mph W\nForecast: Sunny, with a high near 75. Calm wind becoming west around 5 mph in the afternoon."
    }
    // ... more forecast periods ...
  ]
}

Claude Desktopとの連携

実装したMCPサーバーは、公式ドキュメントにあるとおりにClaude Desktopと連携できます。
https://modelcontextprotocol.io/quickstart/server#testing-your-server-with-claude-for-desktop

基本的には、生成された実行可能JARファイルや実行コマンドをClaude Desktopの設定画面で指定します。

動作確認

実装したMCPサーバーをClaude Desktopと連携し、いくつかの入力を試してみました。以下はその結果の例です。

入力 結果
大谷翔平の本拠地の天気は? LLMが推論 (大谷->LA->座標) し、get_forecastツールで天気予報を取得
ロサンゼルスの天気は? LLMが地名を認識し、get_forecastツールで天気予報を取得
カリフォルニアの気象警報は? LLMがツールを使用し、警報データを取得
不正な州コード "ZZ" の警報は? ツールがエラーメッセージを返却
東京の天気は? ツールが対応範囲外(米国のみ)のため、情報取得できず

おわりに

公式チュートリアルから始めてツールを構築してみると、MCPの可能性をより深く理解できるかと思います。チュートリアルは時間もそれほどかからず、MCPの基本的な仕組みや定義を理解するのに適しているので、キャッチアップしてみたい方はオススメです。


参考リソース

GitHubで編集を提案

Discussion