
인공지능의 급속한 발전으로 대규모 언어 모델(LLM)이 사용자와 소프트웨어 간의 상호작용 패러다임에 혁명을 일으키고 있습니다.
사용자는 복잡한 API 문서를 파헤치거나, 번거로운 양식 사이를 전환할 필요가 없습니다.
사용자는 자연어를 사용하여 시스템에 직접 직접 소통할 수 있습니다.
"2023년에 출판된 모든 책을 찾아주세요", "철수@example.com 의 이메일 주소를 사용하여
철수라는 새 사용자를 만들어주세요 "와 같이 말할 수 있습니다.
이처럼 직관적이고 원활한 상호작용 방식을 사용하면 신규 사용자의 학습 곡선을 크게 줄일 수 있을 뿐만 아니라 시스템의 교육 비용과 구현 주기를 크게 단축시켜 기업용 애플리케이션을 더 간단하고 효율적으로 만들 수 있습니다.
이는 바로 MCP(Model Context Protocol)가 애플리케이션 수준에서 제공하는 가치입니다.
여기에 공식적인 정의를 붙여 넣지는 않겠지만, 쉽게 설명드리겠습니다.
MCP는 AI 세계의 "범용 어댑터"와 같습니다.
다양한 유형의 서비스와 데이터베이스가 있고, 각각 고유한 "말하기" 방식이 있습니다.
AI가 이러한 서비스와 통신하려면 각 서비스의 '언어'를 학습해야 하므로 번거로워집니다.
MCP는 이러한 문제를 해결합니다.
마치 통합 번역기와 같아서 AI가 단 하나의 "언어"만 학습하여 모든 서비스와 통신할 수 있게 해줍니다.
이렇게 하면 개발자는 각 서비스마다 별도의 연결 방법을 개발할 필요가 없고, AI는 필요한 정보를 더 쉽게 얻을 수 있습니다.
백엔드를 공부하는 학생이라면 gRPC 에 대해 들어보거나 접해본 적이 있을 것입니다 .
gRPC는 표준화된 통신 방법을 통해 다양한 언어로 개발된 서비스 간의 통신을 가능하게 합니다.
AI 모델을 위해 특별히 설계된 MCP 의 "번역기 및 인터페이스 관리자"는 AI가 통합된 방식으로 다양한 애플리케이션이나 데이터 소스와 상호 작용할 수 있도록 해줍니다.
우리가 날씨 서비스를 개발했고 사용자가 선전의 날씨를 확인하고 싶어한다고 가정해 보겠습니다.
여기서는 기존 API 방법과 MCP 방법을 비교해보겠습니다 .

시연 목적으로 먼저 도서 관리 서비스를 준비하였습니다.
책 엔티티는 다음과 같습니다.
import jakarta.persistence.*
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.PastOrPresent
import java.time.LocalDate
@Entity
@Table(name = "books")
data class Book(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@NotBlank(message = "책 제목을 입력해주세요.")
@Column(nullable = false)
val title: String,
@NotBlank(message = "카테고리를 입력해주세요.")
@Column(nullable = false)
val category: String,
@NotBlank(message = "저자를 입력해주세요.")
@Column(nullable = false)
val author: String,
@NotNull(message = "출판일을 입력해주세요.")
@PastOrPresent(message = "출판일은 미래 날짜일 수 없습니다.")
@Column(nullable = false)
val publicationDate: LocalDate,
@NotBlank(message = "ISBN 코드를 입력해주세요.")
@Column(nullable = false, unique = true)
val isbn: String
)
이 서비스에 대해 두가지 테스트 방법이 작성되었습니다.
import com.example.entity.Book
interface BookService {
// 저자에 따른 책 조회
fun findBooksByAuthor(author: String): List<Book>
// 카테고리에 따른 책 조회
fun findBooksByCategory(category: String): List<Book>
}
이제 이 SpringBoot 서비스를 MCP 서비스로 변환해야 합니다.
다음 단계가 필요합니다.
pom.xml 에 관련 종속성을 도입합니다 .
인류학적 접근에는 프록시가 필요하다는 점을 알려드립니다. 그렇지 않으면 403 프롬프트가 표시됩니다.
<!-- Spring AI 핵심 의존성 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
</dependency>
<!-- Anthropic 모델 지원 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>
<!-- MCP 서버 지원 - WebMVC 버전 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>
이러한 종속성은 아직 미리보기 버전이기 때문에 Maven 중앙 저장소 에서 찾을 수 없으므로 저장소 주소를 별도로 가져와야 합니다.
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
프로젝트의 프록시 구성에 대해서는 다음 구성을 참조할 수 있습니다.
import jakarta.annotation.PostConstruct
import org.springframework.context.annotation.Configuration
@Configuration
class ProxyConfig {
// 프록시 설정
private val PROXY_HOST = "127.0.0.1"
private val PROXY_PORT = 10080
@PostConstruct
fun setSystemProxy() {
// 시스템 프록시 속성 설정, 이는 Spring Boot의 자동 구성 HTTP 클라이언트에 영향을 미침
System.setProperty("http.proxyHost", PROXY_HOST)
System.setProperty("http.proxyPort", PROXY_PORT.toString())
System.setProperty("https.proxyHost", PROXY_HOST)
System.setProperty("https.proxyPort", PROXY_PORT.toString())
println("System proxy configured: http://$PROXY_HOST:$PROXY_PORT")
}
}
우리의 목표는 Spring 서비스를 MCP 서비스로 변환하는 것이므로 클라이언트 측을 구성할 필요가 없습니다.
마찬가지로, 종속성을 도입할 때 클라이언트 측 종속성을 도입할 필요가 없습니다.
# Spring AI API 키
spring.ai.anthropic.api-key=API 키를 입력
# MCP 서버 활성화
spring.ai.mcp.server.enabled=true
# MCP 서버 설정
spring.ai.mcp.server.name=book-management-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.sse-message-endpoint=/mcp/message
서비스 변환에는 도구 구성과 함수 빈이라는 두 가지 접근 방식이 있습니다.
다음은 두 가지 접근 방식에 대한 간략한 설명입니다.
도구 구성 방식에서 구현 클래스의 메서드에 @Tool 및 @ToolParam 주석을 추가하여
각각 메서드와 매개변수를 표시하도록 변환해야 합니다.
import com.example.entity.Book
import com.example.repository.BookRepository
import com.example.service.BookService
import jakarta.annotation.Resource
import org.springframework.ai.tool.annotation.Tool
import org.springframework.ai.tool.annotation.ToolParam
import org.springframework.stereotype.Service
@Service
class BookServiceImpl(
@Resource private val bookRepository: BookRepository
) : BookService {
@Tool(name = "findBooksByTitle", description = "책 제목을 기준으로 책을 검색, 부분 제목 매지원합니다.")
override fun findBooksByTitle(@ToolParam(description = "책 제목 키워드") title: String): List<Book> {
return bookRepository.findByTitleContaining(title)
}
@Tool(name = "findBooksByAuthor", description = "저자를 기준으로 정확하게 책을 검색 합니다.")
override fun findBooksByAuthor(@ToolParam(description = "저자 이름") author: String): List<Book> {
return bookRepository.findByAuthor(author)
}
@Tool(name = "findBooksByCategory", description = "책 카테고리를 기준으로 정확하게 책을 검색 합니다.")
override fun findBooksByCategory(@ToolParam(description = "책 카테고리") category: String): List<Book> {
return bookRepository.findByCategory(category)
}
}
그런 다음 이 구현 클래스를 MCP 서버 구성에 등록합니다.
import com.example.service.BookService
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
/**
* MCP 서버 설정 클래스, MCP 도구를 등록하는 역할을 합니다.
*/
@Configuration
class McpServerConfig {
/**
* 도구 콜백 제공자를 등록하여, BookService에서
* @Tool 메서드를 MCP 도구로 노출합니다.
*
* @param bookService 책 서비스
* @return 도구 콜백 제공자
*/
@Bean
fun bookToolCallbackProvider(bookService: BookService): ToolCallbackProvider {
return MethodToolCallbackProvider.builder()
.toolObjects(bookService)
.build()
}
}
이 시점에서 채팅 클라이언트에서 등록 도구를 구성하고 소개할 수 있습니다.
import org.springframework.ai.chat.client.ChatClient
import org.springframework.ai.tool.ToolCallbackProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 채팅 클라이언트 설정 클래스
*/
@Configuration
class ChatClientConfig {
@Autowired
private lateinit var toolCallbackProvider: ToolCallbackProvider
/**
* ChatClient 구성, 시스템 명령과 도구 함수 등록
*/
@Bean
fun chatClient(builder: ChatClient.Builder): ChatClient {
return builder
.defaultSystem("당신은 책 관리 도우미입니다. 사용자가 책 정보를 조회하는 데 도움을 줄 수 있습니다." +
"책 제목을 기준으로 책을 검색하거나, 저자나 카테고리로 책을 검색할 수 있습니다." +
"답변은 간결하고 친절하게 하며, 책 정보를 읽기 쉬운 형식으로 정리해서 제공합니다.")
// 도구 함수 등록
.defaultTools(toolCallbackProvider)
.build()
}
}
위의 방법 외에도 별도의 클래스를 선언하여 쿼리 방법을 함수 빈으로 내보낼 수 있습니다.
import com.example.entity.Book
import com.example.service.BookService
import jakarta.annotation.Resource
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Service
import java.util.function.Function
/**
* 책 조회 서비스, 조회 방법을 함수 Bean으로 내보냄
*/
@Service
class BookQueryService {
@Resource
private lateinit var bookService: BookService
/**
* 책 제목으로 책을 조회하는 함수 Bean
*/
@Bean
fun findBooksByTitle(): Function<String, List<Book>> {
return Function { title -> bookService.findBooksByTitle(title) }
}
/**
* 저자 이름으로 책을 조회하는 함수 Bean
*/
@Bean
fun findBooksByAuthor(): Function<String, List<Book>> {
return Function { author -> bookService.findBooksByAuthor(author) }
}
/**
* 카테고리로 책을 조회하는 함수 Bean
*/
@Bean
fun findBooksByCategory(): Function<String, List<Book>> {
return Function { category -> bookService.findBooksByCategory(category) }
}
}
이러한 접근 방식에서는 AI 채팅 클라이언트를 정의할 때 명시적인 선언이 필요합니다.
import org.springframework.ai.chat.client.ChatClient
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* 채팅 클라이언트 설정 클래스
*/
@Configuration
class ChatClientConfig {
/**
* ChatClient 구성, 시스템 명령과 도구 함수 등록
*/
@Bean
fun chatClient(builder: ChatClient.Builder): ChatClient {
return builder
.defaultSystem("당신은 책 관리 도우미입니다. 사용자가 책 정보를 조회하는 데 도움을 줄 수 있습니다." +
"책 제목을 기준으로 책을 검색하거나, 저자나 카테고리로 책을 검색할 수 있습니다." +
"답변은 간결하고 친절하게 하며, 책 정보를 읽기 쉬운 형식으로 정리해서 제공합니다.")
// 도구 함수 등록, 여기서 메서드 이름을 사용하여 Spring 컨텍스트의 함수 Bean을 참조합니다
.defaultTools(
"findBooksByTitle",
"findBooksByAuthor",
"findBooksByCategory"
)
.build()
}
}
서비스 개발을 완료한 후에는 컨트롤러를 선언하여 외부에 공개하여 호출할 수 있습니다.
import com.example.model.ChatRequest
import com.example.model.ChatResponse
import jakarta.annotation.Resource
import org.springframework.ai.chat.client.ChatClient
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
/**
* 채팅 컨트롤러, AI 채팅 요청 처리
*/
@RestController
@RequestMapping("/api/chat")
class ChatController {
@Resource
private lateinit var chatClient: ChatClient
/**
* 채팅 요청 처리, AI와 MCP 도구로 응답
*
* @param request 채팅 요청
* @return AI 응답이 포함된 응답
*/
@PostMapping
fun chat(@RequestBody request: ChatRequest): ResponseEntity<ChatResponse> {
return try {
// 사용자 메시지 생성
val userMessage = request.message
// 스트리밍 API를 사용하여 채팅 호출
val content = chatClient.prompt()
.user(userMessage)
.call()
.content()
ResponseEntity.ok(ChatResponse(content))
} catch (e: Exception) {
e.printStackTrace()
ResponseEntity.ok(ChatResponse("요청 처리 중 오류 발생: ${e.message}"))
}
}
}
CommandLineRunner테스트를 용이하게 하기 위해,
우리는 인터페이스를 구현하여 애플리케이션이 시작될 때 테스트 데이터를 자동으로 데이터베이스에
로드하는 데이터 초기화 프로그램을 개발합니다.
import com.example.entity.Book
import com.example.repository.BookRepository
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
import java.time.LocalDate
@Component
class DataInitializer(
private val bookRepository: BookRepository
) : CommandLineRunner {
@Throws(Exception::class)
override fun run(vararg args: String?) {
// 예시 데이터 준비
val sampleBooks = listOf(
Book(null, "객체지향의 사실과 오해 역할, 책임, 협력 관점에서 본 객체지향", "프로그래밍", "조영호", LocalDate.of(2015, 6, 17), "9787115582247"),
Book(null, "요즘 우아한 AI 개발 머신러닝에서 GPT, LLM, 생성형 AI, MLOps까지, 배달의민족 실제 프로젝트로 엿보는 AI 활용 이야기", "프로그래밍", "우아한형제들", LocalDate.of(2025, 4, 1), "9787111641247"),
Book(null, "J랭체인 입문 RAG 챗봇부터 에이전트까지", "프로그래밍", "오승환", LocalDate.of(2025, 3, 1), "9787111213826"),
Book(null, "알고리즘 (4판)", "컴퓨터 과학", "Robert Sedgewick", LocalDate.of(2012, 10, 1), "9787115293800"),
Book(null, "교과서에 나오는 고사성어: 익힘책 1~3권 세트", "자기계발", "김광남", LocalDate.of(2025, 4, 29), "9781234567890"),
Book(null, "모조는 낮잠 잘 곳을 찾아요", "유아", "아델 벨린든", LocalDate.of(2025, 5, 25), "9789876543210"),
Book(null, "실리콘밸리 길들이기 폭주하는 빅테크 기업에 브레이크를 걸다", "마케팅", "개리 마커스, 김동환", LocalDate.of(2025, 4, 23), "9787111214748"),
Book(null, "39세 부자 아빠의 레버리지 ETF 투자 노트 불황에도 월급만으로 10배 불리는 고수익 복리 시스템", "경제 경영", "제이투", LocalDate.of(2025, 4, 18), "9787111464747"),
Book(null, "신세기 에반게리온 애장판 5", "만화", "사다모토 요시유키", LocalDate.of(2025, 3, 28), "9787115419378"),
Book(null, "BREATHE (격월간) : 2025년 no.72", "서양잡지", "BREATHE", LocalDate.of(2025, 4, 15), "9787123456789")
)
// 예시 데이터 저장
bookRepository.saveAll(sampleBooks)
println("데이터 초기화 완료, 총 ${sampleBooks.size}권의 책이 로드되었습니다.")
}
}
다음으로, 요청 인터페이스를 통해 다음 테스트를 수행합니다.


이때 반환된 결과는 데이터베이스의 테스트 데이터 내용임을 알 수 있습니다.
여기에서는 사용자가 입력한 질문을 토대로 큰 모델이 개방형 도구 방법과 일치하는 항목이 있는지 여부를
판별합니다.
그렇다면 호출하고 반환할 것입니다.
Spring Boot를 MCP와 통합하여 기존 CRUD 시스템을 지능형 AI 어시스턴트로 쉽게 전환할 수 있었습니다. MCP는 AI와 서비스 간의 연결 역할을 하여 통합 작업을 크게 단순화합니다.
앞으로 MCP 생태계가 발전함에 따라 서비스로서의 대화가 애플리케이션의 개발 패러다임이 되어 복잡한 시스템을 사용하기 쉽게 만들 수도 있습니다.