Spring, Kotlin를 활용하여 간단한 WebMVC MCP Server 만들기
앞에서는 Spring으로 STDIO 방식의 MCP Server를 만들었다면, 여기서는 WebMVC 전송 방식의 서버를 만들어 보도록 하겠다.
MCP Server을 WebMVC 방식으로 변경
기존 만들었던 STDIO 방식의 MCP Server 코드를 그대로 사용하도록 하겠다.
기존 코드 복사
기존에 작성한 코드가 있다면 디렉토리 통채로 복사하여 사용하도록 하겠다.
작성한 코드가 없다면, GitHub를 통채로 복사해서 사용해도 된다.
코드를 복사하고, 디렉토리도 다음과 같이 변경한다.
% mv spring-ai-mcp-server spring-ai-mcp-server-webmvc
빌드 스크립트
복사한 프로젝트의 settings.gradle.kts 파일에세 root 프로젝트 이름도 spring-ai-mcp-server-webmvc 로 변경한다.
settings.gradle.kts
rootProject.name = "spring-ai-mcp-server-webmvc"
의존성 라이브러리에 Spring WebMVC MCP Sever(spring-ai-starter-mcp-server-webmvc) 라이브러리도 추가한다.
/build.gradle.kts
dependencies {
...
implementation("org.springframework.ai:spring-ai-starter-mcp-server")
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc") // webmvc 추가
...
}
어플리케이션 설정 파일
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
protocol: STATELESS # webmvc 추가
logging:
# pattern:
# console:
file:
name: ./log/spring-ai-starter-mcp-server-webmvc.log
변경된 설정 내용을 확인해 보면, server 설정으로 protocol: STATELESS가 추가되었다.
그리고, yml 파일을 설정을 자세히 보면 다음 로그 설정도 코멘트 처리하여 제거한 것을 볼 수 있다.
spring:
main:
web-application-type: none
logging
pattern
console:
이 설정을 제거하게 되면, 데몬 웹 프로세스가 기동되고, 콘솔에서 로그도 보이게 설정한 것이다.
테스트용 클라이언트 생성
서버 코드는 완성이 되었고, 이제 클라이언트를 전송을 시도해 보겠다.
/test/kotlin/com/devkuma/ai/mcp/client/HttpClient.kt 파일명으로 클라이언트 생성한다.
package com.devkuma.ai.mcp.client
import io.modelcontextprotocol.client.McpClient
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest
fun main() {
val transport = HttpClientStreamableHttpTransport.builder("http://localhost:8080")
.build()
val client = McpClient.sync(transport)
.build()
client.initialize()
client.ping()
// 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()
}
Output:
... 생략 ...
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]
... 생략 ...
- STDIO와 동일하게, 사용 가능한 Tool 목록이 표시되고,
get_weather도구를 호출하여 응답을 받은 값을 표시되는 것을 확인할 수 있다.
참고
위에 예제 코드는 GitHub에서 확인해 볼 수 있다.