Closed9

公式チュートリアル片手にMCPサーバに関して学んでみた

おだしゅんおだしゅん

目的

今話題のMCPのうち、MCPサーバの立ち上げ方を勉強しようと思います。
公式のチュートリアルがあるので、それに従い作業を行います。
https://modelcontextprotocol.io/quickstart/server

チュートリアルの言語は以下の中から選ぶことができます。
今回は、私が業務でよく使うKotlinを選択します。

  • Python
  • Node
  • Java
  • Kotlin
  • C#
おだしゅんおだしゅん

概要

まずはMCPサーバについての概要を学ぼうと思います。
チュートリアルだけだと理解するのが難しかったので、以下の記事も参考とします。
https://qiita.com/ipeblb/items/535709fa06cbb40c400c

MCPサーバとは?

MCPサーバを大別すると、3つの機能があります。

項目 説明
リソース クライアントが読み取ることができるファイルのようなデータのこと。
例えば、Google DriveのMCPサーバの場合、Google DriveにアップロードされているファイルをLLMのコンテキストとして添付することができる。
ツール LLMから呼び出すことができる関数。
例えばSlackのMCPサーバの場合、「Publicチャンネル」を一覧表示するツールや、「新しいメッセージを投稿する」ツールなどがある。
プロンプト 事前に作成したプロンプトのテンプレートを呼び出す

チュートリアルでは、アメリカ国立気象局のAPIを呼び出し、アメリカの気象警報と天気予報の情報を取得するツールを実装します。

おだしゅんおだしゅん

開発環境構築

まずは作業ディレクトリを作成します。

odashun@ mcp % pwd
/Users/odashun/workspace/mcp

# チュートリアルに沿ってディレクトリ「weather」も作成します。
odashun@ mcp % mkdir weather
odashun@ mcp % cd weather

Gradleの初期化コマンドを入力します。
順番に質問されるので回答していきます。

odashun@ weather % gradle init
Starting a Gradle Daemon (subsequent builds will be faster)

# 1:Applicationを指定
Select type of build to generate:
  1: Application
  2: Library
  3: Gradle plugin
  4: Basic (build structure only)
Enter selection (default: Application) [1..4] 1

# 今回はKotlinなので「2:Kotlin」を指定
Select implementation language:
  1: Java
  2: Kotlin
  3: Groovy
  4: Scala
  5: C++
  6: Swift
Enter selection (default: Java) [1..6] 2

# チュートリアルの記載に従い「17」を指定
Enter target Java version (min: 7, default: 21): 17

# プロジェクトの名前なので任意の文字列でOK
Project name (default: weather): weather

# お試しプロジェクトなので、「1:Single application project」を指定
Select application structure:
  1: Single application project
  2: Application and library project
Enter selection (default: Single application project) [1..2] 1

# お好みのDSLを指定する。今回は「1:Kotlin」を指定
Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 1

# 今回テストケースを実装するわけではないので、なんでも良い。
Select test framework:
  1: kotlin.test
  2: JUnit Jupiter
Enter selection (default: kotlin.test) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] yes


> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.13/samples/sample_building_kotlin_applications.html

BUILD SUCCESSFUL in 41s
1 actionable task: 1 executed

初期化が完了すると、 作業ディレクトリ配下が以下のディレクトリ構造となります。

weather
├── app
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   ├── kotlin
│       │   │   └── org
│       │   │       └── example
│       │   │           └── App.kt
│       │   └── resources
│       └── test
│           ├── kotlin
│           │   └── org
│           │       └── example
│           │           └── AppTest.kt
│           └── resources
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
おだしゅんおだしゅん

実装1:設定値の追加

dependenciesとpluginsの追加

build.gradle.ktsに以下の依存関係とプラグインを追加します。

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

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

plugins {
    kotlin("plugin.serialization") version "1.9.0"
    id("com.github.johnrengelman.shadow") version "8.1.1"
}

追加した実装の解説

dependencies

ライブラリ名 概要
io.modelcontextprotocol:kotlin-sdk MCP の Kotlin用SDKです。
https://github.com/modelcontextprotocol/kotlin-sdk
org.slf4j:slf4j-nop SLF4J の NOP(No-Operation)実装。ログ出力を行わず、全てのログメッセージを無視します。
io.ktor:ktor-client-content-negotiation KtorでHTTPのコンテンツネゴシエーションを実装するためのライブラリです。
io.ktor:ktor-serialization-kotlinx-json KtorでJSON形式の文字列のシリアライズ/デシリアライズを行うためのライブラリです。

plugins

プラグイン 概要
kotlin("plugin.serialization") Kotlinでシリアライズ、デシリアライズするために必要なプラグインです。
id("com.github.johnrengelman.shadow") Fat JAR(またはUber JAR)を作成するために必要なプラグインです。
※Fat JARとは依存関係を含む全てのソースコードを一つのJARファイルにまとめたもの
おだしゅんおだしゅん

実装2:ロジック実装

チュートリアルに沿って以下のロジックを実装します。
各クラスは「weather/app/src/main/org/kotlin/example」配下に実装します。

サンプルコードは、以下Githubに載っているのでこれを元に実装していきます。
(package名を除き、一旦丸コピします)
https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/

また、各ライブラリの内容がサンプルコードのコメントだけでは理解するのが難しかったので、以下SDKの公式ドキュメントも見つつ確認してみます。
https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/docs

①:main.kt

コード

https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt

処理内容

後述する関数「run mcp server」を呼び出すだけのmainクラスです。

McpWeatherServer.kt

コード

https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt

処理内容

ツールに関する設定と、MCPサーバの起動に必要な処理ロジックが記載されています。
以下にステップごとの処理内容を記載します。

ステップ1:HttpClientの作成

APIを呼び出す際に使用するHttpClientのオブジェクトを作成します。

    // Base URL for the Weather API
    val baseUrl = "https://api.weather.gov"

    // Create an HTTP client with a default request configuration and JSON content negotiation
    val httpClient = HttpClient {
        defaultRequest {
            url(baseUrl)
            headers {
                append("Accept", "application/geo+json")
                append("User-Agent", "WeatherApiClient/1.0")
            }
            contentType(ContentType.Application.Json)
        }
        // Install content negotiation plugin for JSON serialization/deserialization
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
                prettyPrint = true
            })
        }
    }

ステップ2:MCP Server インスタンスを生成

MCP Server用のインスタンスを作成します。
※これ以降、MCPサーバを実装するための理解に重要な処理が続くので、コード内にコメントを記載していきます。

    val server = Server(
        Implementation( // MCPサーバの名前とバージョンを定義
            name = "weather",
            version = "1.0.0"
        ),
        ServerOptions( // MCPサーバの設定オプション
            // listChanged=trueにすることでツール内容に変更が入った際に通知処理を行う
                capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
        )
    )

ステップ3:ツール登録

ステップ2で作成したMCP Serverのインスタンスにツールを登録します。
今回、気象警報取得用と天気予報取得用の合計2つのツールを登録します。

ツール1:気象警報取得ツール(get_alerts)
    server.addTool(
        name = "get_alerts", // ツール名
        description = """
            Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)
        """.trimIndent(), // ツールの説明
        inputSchema = Tool.Input( // MCPクライアントから受け取る入力スキーマ(JSON)を定義する
            properties = buildJsonObject {
                putJsonObject("state") {
                    put("type", "string")
                    put("description", "Two-letter US state code (e.g. CA, NY)")
                }
            },
            required = listOf("state")
        )
    ) { request -> // ツールの実行処理を定義する
        val state = request.arguments["state"]?.jsonPrimitive?.content // リクエスト「state」パラメータを取得
        if (state == null) { // 「state」パラメータがnullの場合にエラーメッセージを返す
            return@addTool CallToolResult(
                content = listOf(TextContent("The 'state' parameter is required."))
            )
        }

        val alerts = httpClient.getAlerts(state) // APIから天気警報を取得

        CallToolResult(content = alerts.map { TextContent(it) }) // 取得した警報情報をMCPクライアントに返す
    }
ツール2:天気予報取得ツール(get_alerts)

ツール1と同様の内容で実装していきます。
(APIのリクエストが異なるだけなので、コメントは割愛します)

    server.addTool(
        name = "get_forecast",
        description = """
            Get weather forecast for a specific latitude/longitude
        """.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("The 'latitude' and 'longitude' parameters are required."))
            )
        }

        val forecast = httpClient.getForecast(latitude, longitude)

        CallToolResult(content = forecast.map { TextContent(it) })
    }

    // Create a transport using standard IO for server communication
    val transport = StdioServerTransport(
        System.`in`.asInput(),
        System.out.asSink().buffered()
    )

ステップ4:トランスポートの作成

標準入出力を使用してMCPサーバーと通信するためのトランスポートのオブジェクトを作成します。
後述するMCPサーバの起動ロジックで使用します。

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

ステップ5:MCPサーバーの起動

Kotlinのコルーチンを使ったMCPサーバの起動ロジックです。

    // MCPサーバーを起動し、クライアント接続から終了まで待機するロジック
    runBlocking {
        server.connect(transport)    // ステップ4で作成したトランスポートのオブジェクトを用いてMCPクライアントとの双方向通信を開始
        val done = Job()             // サーバー終了検知用のJobオブジェクトを作成
        server.onClose {             // 接続が切断されたタイミングで、Jobを完了状態にする
            done.complete()
        }
        done.join()                  // Job完了(=サーバー終了)までサスペンドして待機
    }

WeatherApi.kt

アメリカ国立気象局のAPIを呼び出しているロジックです。
APIを呼び出す処理であり、MCPサーバの文脈では重要ではないので、説明は割愛します。
https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt

おだしゅんおだしゅん

ビルドする

Gradleビルドします。

odashun@ weather % ./gradlew build
Reusing configuration cache.

[Incubating] Problems report is available at: file:///Users/odashun/workspace/mcp/weather/build/reports/problems/problems-report.html

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.13/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 2s
11 actionable tasks: 4 executed, 7 up-to-date
Configuration cache entry reused.

ビルドが成功すると、app/build/libs/配下にJARファイルが作成されます。

odashun@ weather % ls app/build/libs/app-all.jar
app/build/libs/app-all.jar

odashun@ weather % ls /Users/odashun/workspace/mcp/weather/app/build/libs/app-all.jar
/Users/odashun/workspace/mcp/weather/app/build/libs/app-all.jar
おだしゅんおだしゅん

ClaudeにMCPサーバの設定をする

MCPサーバの準備ができたので、いよいよClaudeに作成したMCPサーバを登録します。

MCPサーバの登録ができるのは、PC上で動くClaude for Desktopになります。
Claude for Desktopをインストールしていない人は、インストールからお願いします。

Claude for Desktopを起動し「Claude>設定」を押下する

「開発者」>「構成を編集」を押下する

「claude_desktop_config.json」を任意のテキストエディタで開き編集

JSONファイルは以下の内容を記載します。
argsの第二引数のみ、Gradleビルドで作成されたJARファイルのパスに置き換えてください。

{
    "mcpServers": {
        "weather": {
            "command": "java",
            "args": [
                "-jar",
                "/Users/odashun/workspace/mcp/weather/app/build/libs/app-all.jar"
            ]
        }
    }
}

再起動

Claude for Desktopを再起動します。

動作確認

参考:失敗してしまった場合

以下のようなエラーを伝えるバナーが表示されます。
原因解析を行う必要があるので、まずは「MCP設定を開く」を押下します。

エラーの原因が「Server disconnected」と表示されています。
ただ、この記述だけだと原因を解析することができないためログを読んでみます。
「ログフォルダ」を開くを押下します。

以下のディレクトリとログファイルが表示されます。

各ログファイルの役割については、以下クイックガイドの中の「Troubleshooting」に記載されていました。
https://modelcontextprotocol.io/quickstart/user#troubleshooting

ファイル名 ファイル内容
mcp.log Claude アプリ側の MCP 接続全般に関するログ
mcp-server-$SERVERNAME.log MCPサーバ側のログ(今回の場合「$SERVERNAME」はweather」となる)

「mcp-server-filesystem.log」は調べてみましたが、役割がわからず。。

MCPサーバ側の問題なはずなので、mcp-server-weather.log の中身をみてみます。

2025-04-21T03:22:30.169Z [weather] [info] Message from client: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}
エラー: メイン・クラスorg.example.AppKtを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: org.example.AppKt

MCPツールの実装に問題がありそう。

確認すると、build.gradle.ktsのMainクラス指定に問題がありましたので修正したところ解決しました。

成功時

MCPサーバの読み込みに成功すると、チャット欄にトングのマークが出てきます。

それを押下すると、設定されているMCPサーバの一覧が表示されます。
確認すると、今回追加したツール「get_alerts」と「get_forecast」が表示されているのでOKとします。

おだしゅんおだしゅん

実際に使ってみる

MCPサーバの追加に成功したので、実際に試してみます。

get_alerts

「Claude」で「アメリカのハワイ州で出ている気象警報を教えてほしい」と尋ねてみます。
すると、ツールの実行して良いか聞かれるので許可します。

許可すると、MCPツール経由でAPIを実行し、そのレスポンス結果元に気象警報について回答してくれます。

get_forecast

同様にハワイ州の天気予報についても尋ねてみます。
ハワイ州の天気予報は?と聞くと、ホノルルの緯度経度を用いて、APIを呼び出してレスポンスの内容から天気情報について回答してくれました。

おだしゅんおだしゅん

終わり

これにてチュートリアルは終了です。
MCPサーバという名前なので、サーバを起動した上でClaudeのAppを立ち上げる必要があるのかと思っていましたが、やってみるとその必要はなく、ブラウザの拡張機能に近い印象を受けました。

MCPについてはまだまだ初学者なので、今後も勉強していきます。

このスクラップは5ヶ月前にクローズされました