SpringとKotlinで作成したWebMVC MCP Serverへ認証を追加する

WebMVC MCP ServerへSpring Securityを適用し、認証を追加します。

はじめに

前回はWebMVC MCP Serverを作成しました。ここでは認証を追加します。

Spring AIはSpring Securityを通じてOAuth2とAPIキー認証をサポートします。

2025-11-10時点でMCP Securityは開発中であり、安定版ではありません。

この例ではAPIキー認証を適用します。

認証を追加する

前回のWebMVCプロジェクトをコピーして名前を変更します。

% mv spring-ai-mcp-server-webmvc spring-ai-mcp-server-auth

ルートプロジェクト名を変更します。

rootProject.name = "spring-ai-mcp-server-auth"

Spring SecurityとMCP Server Securityを追加します。

dependencies {
  implementation("org.springframework.ai:spring-ai-starter-mcp-server")
  implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc")
  implementation("org.springframework.boot:spring-boot-starter-security")
  implementation("org.springaicommunity:mcp-server-security:0.0.3")
}

ログファイルを変更し、Spring SecurityのTRACEログを有効にします。

spring:
  main:
    banner-mode: off
  ai:
    mcp:
      server:
        name: my-weather-server
        version: 0.0.1
        protocol: STATELESS

logging:
  file:
    name: ./log/spring-ai-starter-mcp-server-auth.log
  level:
    org.springframework.security: TRACE

MCP Serverのセキュリティ設定を作成する

Spring SecurityへMCP APIキーを登録します。

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain =
        http.authorizeHttpRequests { it.anyRequest().authenticated() }
            .with(mcpServerApiKey()) { apiKey ->
                apiKey.apiKeyRepository(apiKeyRepository())
            }.build()

    private fun apiKeyRepository(): ApiKeyEntityRepository<ApiKeyEntityImpl> {
        val apiKey = ApiKeyEntityImpl.builder()
            .name("test api key")
            .id("api01")
            .secret("mycustomapikey")
            .build()
        return InMemoryApiKeyEntityRepository(listOf(apiKey))
    }
}

例ではID api01とsecret mycustomapikeyをメモリーへ保存します。本番環境ではデータベースや外部APIを使用してください。

テストクライアントを実装する

既存クライアントへAPIキーヘッダーを追加します。

val request = HttpRequest.newBuilder()
    .header("Content-Type", "application/json")
    .header("X-API-key", "api01.mycustomapikey")

val transport = HttpClientStreamableHttpTransport.builder("http://localhost:8080")
    .requestBuilder(request)
    .build()

val client = McpClient.sync(transport).build()
client.initialize()
client.ping()
println("Available Tools = ${client.listTools()}")
println("get_weather Response = ${client.callTool(CallToolRequest("get_weather", mapOf("city" to "seoul")))}")
client.closeGracefully()

クライアントを実行すると、利用可能なツール一覧とget_weatherのレスポンスが表示されます。

参考資料