Spring, Kotlin를 활용하여 간단한 WebMVC MCP Server 만들기
시작하기
앞에서는 Spring으로 STDIO 방식의 MCP Server를 만들었다면, 여기서는 WebMVC 전송 방식의 서버를 만들어 보도록 하겠다.
MVP 프로토콜과 실행 모드
Spring-AI의 MCP Server 프레임워크는 다음과 같은 서버 프로토콜을 제공한다:
- STDIO
- Standard Input/Output library
- 프로세스 내부에서 표준 입출력으로 통신
- SSE
- Server-Sent Events(실시간 서버-클라이언트 통신 기술)
- 실시간 서버-클라이언트 통신
- Streamable-HTTP
- 전통적인 HTTP 통신을 확장해서 스트리밍처럼 작동하도록 만든 방식
- HTTP POST/GET 요청으로 서버에서 여러 클라이언트 연결 처리
- Stateless Streamable-HTTP
- AI 모델과 같은 애플리케이션을 위한 통신 프로토콜
- 단일 HTTP 엔드포인트를 통해 요청 전송과 응답 스트리밍을 동시에 수행
- 요청 간 세션 메모리를 유지하지 않음으로써 무상태성을 구현
- 요청 간 세션 상태를 유지하지 않아 MSA 환경에 적합
이 문서에서는 Stateless Streamable-HTTP 방식으로 구현하려고 한다.
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 추가
...
}
어플리케이션 설정 파일
application.yml 파일을 다음와 같이 변경한다.
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:
이 설정을 제거하면, 데몬 웹 프로세스가 기동되고, 콘솔에서 로그도 볼 수 있게 된다.
테스트를 위한 MCP Client 구현
MCP Server는 완성이 되었고, 이제 클라이언트를 구현해 보겠다.
신규로 HttpClient.kt 파일명으로 클라이언트 생성한다.
/src/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()
}
코드 내용을 보면 HttpClientStreamableHttpTransport 생성시 인자로 http 주소를 넣고 있다.
클라이언트 전에, MCP Sever를 먼저 실행해서 프로세스로 켜놔야 한다. 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에서 확인해 볼 수 있다.