Build a Simple Standard MCP Server (STDIO) with Spring and Kotlin

Build an MCP Server with Spring and Kotlin, then connect it to Claude.

Getting Started

An MCP Server provides a standardized interface that lets an AI model connect to external resources such as tools, data, and APIs.

For the concept of an MCP server, see the document below.
AI Terms | MCP Server

This article explains how to build a simple MCP Server with Spring and connect to it from Claude.
MCP architect

Of course, you can use AI tools other than Claude, and the MCP Server does not have to be built with Spring. This is possible because MCP, as the name implies, follows the Model Context Protocol.

Resources connected to an MCP Server can vary widely, such as databases or APIs. This article does not cover connecting those resources.

Spring AI’s MCP Server framework supports the following three starters depending on the server transport.

  • Standard MCP Server
    • Supports STDIO server transport
    • org.springframework.ai:spring-ai-starter-mcp-server
  • WebMVC Server transport
    • Supports Spring MVC-based SSE (Server-Sent Events) server transport and, optionally, STDIO transport
    • org.springframework.ai:spring-ai-starter-mcp-server-webmvc
  • WebFlux Server transport
    • Supports Spring WebFlux-based SSE (Server-Sent Events) server transport and, optionally, STDIO transport
    • org.springframework.ai:spring-ai-starter-mcp-server-webflux

Implementing an MCP Server with Spring

First, create a simple Standard MCP Server (STDIO) with Spring and Kotlin.

Create the Project

You can create the project with Spring Initializr.

Spirng ininitializr

Press the “GENERATE” button near the bottom of the screen to download the configured project files.

Alternatively, you can create the initial Spring Boot project with the following curl command.

curl https://start.spring.io/starter.tgz \
-d bootVersion=3.5.7 \
-d dependencies=spring-ai-mcp-server \
-d baseDir=spring-ai-mcp-server \
-d groupId=com.devkuma \
-d artifactId=spring-ai-mcp-server \
-d packageName=com.devkuma.ai.mcp.server \
-d applicationName=McpServerApplication \
-d packaging=jar \
-d language=kotlin \
-d javaVersion=21 \
-d description="Demo project for Spring AI MCP Server" \
-d type=gradle-project-kotlin | tar -xzvf -

Check the Generated Project

Build Script

You can see that the dependency library includes the Spring MVC Server (spring-ai-starter-mcp-server) library.

/build.gradle.kts

dependencies {
	...
	implementation("org.springframework.ai:spring-ai-starter-mcp-server")
	...
}

If you do not change the Spring AI library version separately, an older default version is configured. In this example, change it to the latest version.

extra["springAiVersion"] = "1.1.0-M4"

Check the latest version here.

Create the Service Object

Create the service object that will be called from the MCP Client.

/src/main/kotlin/com/devkuma/ai/mcp/server/WeatherService.kt

package com.devkuma.ai.mcp.server

import org.springframework.ai.tool.annotation.Tool
import org.springframework.ai.tool.annotation.ToolParam
import org.springframework.stereotype.Service

@Service
class WeatherService {

    @Tool(name = "get_weather", description = "Return the weather of a given city.")
    fun getWeather(@ToolParam(description = "The city for which to get the weather") city: String): String {
        // Add logic that retrieves the actual weather information.
        // This example simply returns "Good".
        return "The weather in $city is good."
    }
}
  • The getWeather(..) function receives the city as a parameter and returns a fixed weather state of “good”.
    • This example uses a fixed response value, but in a real application you can retrieve the information from another resource such as a database or API.
  • The tool description in @Tool and the parameter description in @ToolParam can later be shown to the MCP client when it connects to the server, so the client knows what tools exist and how to use them.

Create the MCP Server Configuration Object

Add the created service as an AI Tool in the Spring configuration.

/src/main/kotlin/com/devkuma/ai/mcp/server/McpConfig.kt

package com.devkuma.ai.mcp.server

import org.springframework.ai.tool.ToolCallbackProvider
import org.springframework.ai.tool.method.MethodToolCallbackProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class McpConfig {

    @Bean
    fun weatherTools(weatherService: WeatherService): ToolCallbackProvider {
        return MethodToolCallbackProvider.builder().toolObjects(weatherService).build()
    }
}

Application Configuration File

Write application.yml as follows. If application.properties already exists, change it to a YAML file.

/src/main/resources/application.yml

spring:
  main:
    web-application-type: none
    # NOTE: You must disable the banner and the console logging to allow the STDIO transport to work !!!
    banner-mode: off
  ai:
    mcp:
      server:
        name: my-weather-server
        version: 0.0.1

logging:
  pattern:
    console:
  file:
    name: ./log/spring-ai-starter-mcp-server.log
  • The important point here is that banner-mode must be set to off.

Build and Create the Executable File

After writing the code, build it and create a jar file. In IDEA, run bootJar with the Gradle build.

Or run the following command from the terminal.

% ./gradlew clean bootJar

Then the jar file is created at the following path.

build/libs/spring-ai-mcp-server-0.0.1-SNAPSHOT.jar

Implementing an MCP Client for Testing

The MCP Server is complete. Now implement a client.

Create a new StdioClient.kt file.

/test/kotlin/com/devkuma/ai/mcp/client/StdioClient.kt

package com.devkuma.ai.mcp.client

import io.modelcontextprotocol.client.McpClient
import io.modelcontextprotocol.client.transport.ServerParameters
import io.modelcontextprotocol.client.transport.StdioClientTransport
import io.modelcontextprotocol.json.McpJsonMapper
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest

fun main() {

    val stdioParams = ServerParameters.builder("java")
        .args(
            "-Dspring.ai.mcp.server.stdio=true",
            "-Dspring.main.web-application-type=none",
            "-Dlogging.pattern.console=",
            "-jar",
            "build/libs/spring-ai-mcp-server-0.0.1-SNAPSHOT.jar"
        )
        .build()

    val transport = StdioClientTransport(stdioParams, McpJsonMapper.createDefault())
    val client = McpClient.sync(transport).build()

    client.initialize()

    // List and demonstrate tools
    val toolsList = client.listTools()
    println("Available Tools = $toolsList")

    val alertResult = client.callTool(CallToolRequest("get_weather", mapOf("city" to "seoul")))
    println("get_weather Response = $alertResult")

    client.closeGracefully()
}
  • In the code, stdioParams is configured to run the jar with the java command and is passed as an argument when creating StdioClientTransport.
  • When you run the client, the MCP Server starts over STDIO.

Output:

... omitted ...

Available Tools = ListToolsResult[tools=[Tool[name=get_weather, title=null, description=Return the weather of a given city., inputSchema=JsonSchema[type=object, properties={city={type=string, description=The city for which to get the weather}}, required=[city], additionalProperties=false, defs=null, definitions=null], outputSchema=null, annotations=null, meta=null]], nextCursor=null, meta=null]
get_weather Response = CallToolResult[content=[TextContent[annotations=null, text="The weather in seoul is good.", meta=null]], isError=false, structuredContent=null, meta=null]

... omitted ...
  • The execution output shows the list of available tools, and you can confirm that the get_weather tool was called and returned a response.

Connect the MCP Server from Claude

After completing the build, connect the MCP Server to Claude Desktop and verify that it is applied correctly.

Connection Configuration

The MCP Server connection settings must be registered in a JSON file.

On macOS, configure ~/Library/Application Support/Claude/claude_desktop_config.json. If the file does not exist, create it. If it already exists, add the following entry.

{
  "mcpServers": {
    "spring-ai-mcp-weather": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-Dspring.main.web-application-type=none",
        "-Dlogging.pattern.console=",
        "-jar",
        "{{path to the project created earlier}}/spring-ai-mcp-server/build/libs/spring-ai-mcp-server-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

The created application runs with the Java command, and the running server connects to Claude Desktop through stdio.

Verify the Connection Configuration

Run Claude and check “Claude > Settings > Developer” from the menu. You can confirm that the local MCP server has been added.
(If an error occurs here, a log button appears. Click that button to check the details.)
Spring AI MCP Server Claude

In the chat window, press the settings button and you can see that “spring-ai-mcp-wearther” is enabled. Spring AI MCP Server Claude

Run Check

Now that the connection has been confirmed, enter a prompt.

In the window below, the prompt “What is the weather in Seoul?” was entered. Spring AI MCP Server Claude

Then the message “Claude wants to use Get weather from spring-ai-mcp-weather” appears and asks whether to allow it. Spring AI MCP Server Claude

When you press the allow button, you can see the response “The weather in Seoul is good.”
If you press “Get weather”, you can also check the request and response. Spring AI MCP Server Claude

References

The example code above is available on GitHub.