Adding Authentication to a WebMVC MCP Server with Spring and Kotlin

Apply Spring Security to a WebMVC MCP Server and add authentication.

Getting Started

The previous example created a WebMVC MCP Server. This page adds authentication.

Spring AI supports OAuth2 and API key authentication through Spring Security.

As of 2025-11-10, MCP Security was still under development and not considered stable.

This example applies API key authentication.

Adding Authentication

Copy the previous WebMVC project and rename it.

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

Change the root project name.

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

Add Spring Security and 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")
}

Change the log file and enable Spring Security TRACE logging.

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

Creating MCP Server Security Configuration

Register an MCP API key in Spring Security.

@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))
    }
}

The example stores ID api01 and secret mycustomapikey in memory. Use a database or external API in production.

Implementing a Test Client

Add the API key header to the existing client.

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()

Running the client lists the available tools and returns a response from get_weather.

References