Spring Boot에서 MCP 서버를 만드는 이유
MCP(Model Context Protocol)는 AI가 외부 도구를 호출하는 표준 프로토콜입니다. Python 생태계에서는 FastMCP가 널리 쓰이지만, 기존 Spring Boot 기반 서비스를 운영 중이라면 Spring AI의 MCP 스타터를 사용하는 것이 자연스럽습니다.
기존 Spring Bean의 메서드에 어노테이션을 추가하는 것만으로 MCP 서버가 됩니다. 별도 인프라 없이 기존 비즈니스 로직을 AI에게 바로 노출할 수 있습니다.
의존성 설정
Spring AI BOM과 MCP Server 스타터를 추가합니다.
Maven
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- STDIO 전송용 (Claude Desktop 연결) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
<!-- HTTP/SSE 전송용 (원격 서버) -->
<!--
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
-->
</dependencies>
Gradle
dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:1.1.4"
}
}
dependencies {
implementation "org.springframework.ai:spring-ai-starter-mcp-server"
}
application.properties
STDIO 전송 시 콘솔 출력을 반드시 비활성화해야 합니다. Spring 배너나 로그가 stdout에 출력되면 JSON-RPC 메시지가 깨집니다.
spring.application.name=my-mcp-server
# MCP 서버 설정
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
# STDIO 전송 시 필수 — 콘솔 출력 차단
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/mcp-server.log
HTTP/SSE 전송을 사용할 때는 web-application-type과 banner-mode 설정을 제거하고, spring-ai-starter-mcp-server-webmvc 의존성을 사용하세요.
Tool 구현
@Tool 어노테이션으로 Spring Bean의 메서드를 MCP Tool로 노출합니다.
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class TodoService {
public record TodoItem(String id, String title, boolean done, String createdAt) {}
private final Map<String, TodoItem> todos = new ConcurrentHashMap<>();
private int nextId = 1;
@Tool(name = "createTodo", description = "새 할 일을 생성합니다")
public String createTodo(
@ToolParam(description = "할 일 제목") String title) {
String id = String.valueOf(nextId++);
String now = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
todos.put(id, new TodoItem(id, title, false, now));
return "할 일 #" + id + " '" + title + "' 생성 완료";
}
@Tool(name = "listTodos", description = "모든 할 일 목록을 조회합니다")
public List<TodoItem> listTodos() {
return new ArrayList<>(todos.values());
}
@Tool(name = "completeTodo", description = "할 일을 완료 처리합니다")
public String completeTodo(
@ToolParam(description = "완료할 할 일 ID") String id) {
TodoItem item = todos.get(id);
if (item == null) {
return "할 일 #" + id + "를 찾을 수 없습니다";
}
todos.put(id, new TodoItem(item.id(), item.title(), true, item.createdAt()));
return "할 일 #" + id + " '" + item.title() + "' 완료 처리됨";
}
@Tool(name = "deleteTodo", description = "할 일을 삭제합니다")
public String deleteTodo(
@ToolParam(description = "삭제할 할 일 ID") String id) {
TodoItem item = todos.remove(id);
if (item == null) {
return "할 일 #" + id + "를 찾을 수 없습니다";
}
return "할 일 #" + id + " '" + item.title() + "' 삭제 완료";
}
}
@ToolParam의 description은 AI에게 전달되므로, 파라미터가 무엇을 의미하는지 명확하게 작성하세요.
Tool을 MCP에 등록
Spring Boot 메인 클래스에서 ToolCallbacks.from()으로 Bean의 Tool 메서드를 자동 등록합니다.
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> todoTools(TodoService todoService) {
return ToolCallbacks.from(todoService);
}
}
ToolCallbacks.from(bean)은 Bean에서 @Tool 어노테이션이 붙은 모든 메서드를 스캔하여 MCP Tool로 등록합니다. 여러 Service Bean이 있다면 각각 등록하면 됩니다.
@Bean
public List<ToolCallback> allTools(
TodoService todoService,
WeatherService weatherService) {
var tools = new ArrayList<ToolCallback>();
tools.addAll(ToolCallbacks.from(todoService));
tools.addAll(ToolCallbacks.from(weatherService));
return tools;
}
빌드와 실행
# Maven으로 JAR 빌드
./mvnw clean package -DskipTests
# 실행 확인 (STDIO 모드라 즉시 종료되지 않고 입력 대기)
java -jar target/mcp-server-0.0.1-SNAPSHOT.jar
# Gradle
./gradlew bootJar
java -jar build/libs/mcp-server-0.0.1-SNAPSHOT.jar
Claude Desktop에 연결
빌드한 JAR을 Claude Desktop 설정에 등록합니다.
설정 파일 위치:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"todo-server": {
"command": "java",
"args": [
"-jar",
"/path/to/mcp-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
Claude Code에서 연결:
claude mcp add todo-server -- java -jar /path/to/mcp-server-0.0.1-SNAPSHOT.jar
claude mcp list
설정 후 Claude Desktop을 재시작하면 AI가 createTodo, listTodos, completeTodo, deleteTodo Tool을 인식합니다.
HTTP/SSE 전송으로 변경
원격 서버로 운영하려면 WebMVC 스타터로 전환합니다.
<!-- pom.xml: STDIO 스타터 대신 WebMVC 스타터 사용 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
# application.properties
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.version=1.0.0
# web-application-type=none 제거 (웹 서버 활성화)
# banner-mode=off 제거
# logging.pattern.console= 제거
server.port=8080
Claude Desktop에서 원격 서버 연결:
{
"mcpServers": {
"todo-server-remote": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8080/sse"]
}
}
}
STDIO vs HTTP 비교
| 항목 | STDIO | HTTP/SSE |
|---|---|---|
| 전송 방식 | 표준 입출력 스트림 | HTTP POST + Server-Sent Events |
| 네트워크 | 불필요 (로컬 프로세스) | 필요 (원격 가능) |
| 동시 접속 | 단일 클라이언트 | 다수 클라이언트 |
| 인증 | 없음 (로컬이므로) | OAuth, API Key 등 가능 |
| 배포 | JAR 파일 직접 실행 | 일반 Spring Boot 서버로 배포 |
| 용도 | 로컬 개발, Claude Desktop | 프로덕션, 팀 공유 서버 |
정리
| 단계 | 내용 |
|---|---|
| 1. 의존성 | spring-ai-starter-mcp-server (STDIO) 또는 -webmvc (HTTP) |
| 2. 설정 | application.properties에서 서버 이름, 전송 타입 설정 |
| 3. Tool 구현 | @Service Bean에 @Tool 어노테이션 |
| 4. 등록 | @Bean으로 ToolCallbacks.from(service) 반환 |
| 5. 빌드 | mvnw package → JAR 생성 |
| 6. 연결 | Claude Desktop config에 JAR 경로 등록 |
기존 Spring Boot 서비스에 MCP를 추가하는 것은 @Tool 어노테이션과 ToolCallbacks.from() Bean 등록 두 가지만 하면 됩니다. 기존 비즈니스 로직을 수정할 필요 없이, Service 메서드 위에 어노테이션을 붙이는 것만으로 AI가 호출할 수 있는 도구가 됩니다.