Adding Authentication to a WebMVC MCP Server with Spring and Kotlin
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.