본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 24. 18:58

MCP와 Spring AI를 활용한 AI 채팅 에이전트 구축

요약

Model Context Protocol(MCP)과 Spring AI, Google Gemini를 결합하여 도구 호출이 가능한 AI 채팅 에이전트를 구축하는 방법을 소개합니다. MCP를 통해 표준화된 인터페이스로 날씨 및 지오코딩 도구를 연결하는 아키텍처를 구현합니다.

핵심 포인트

  • MCP를 AI 모델과 도구를 연결하는 표준 인터페이스(USB-C)로 활용
  • Spring AI의 @McpTool 어노테이션을 통한 간편한 도구 정의
  • MCP 서버, AI 에이전트, React UI로 구성된 확장 가능한 아키텍처
  • 실제 도구 호출을 통해 AI의 환각 현상을 방지하고 정확도 향상

Model Context Protocol (MCP)는 AI 앱을 도구 및 데이터 소스에 연결하기 위한 개방형 표준입니다. 이를 이해하는 유용한 방법은 AI를 위한 USB-C 포트로 생각하는 것입니다. 즉, 모든 통합마다 별도의 커스텀 글루 코드 (glue code)를 작성할 필요 없이, 서로 다른 모델이 다양한 기능에 연결될 수 있도록 하는 하나의 표준 인터페이스입니다.

이 프로젝트에서는 MCP, Spring AI, 그리고 Google Gemini를 결합하여, 환각 (hallucination) 현상을 일으키는 대신 실제 도구를 사용하여 날씨 질문에 답할 수 있는 채팅 앱을 구축합니다. 이 시스템은 세 가지 부분으로 구성됩니다:

  • MCP 도구 서버 (MCP tool server) - 날씨 및 지오코딩 (geocoding) 도구를 노출하는 Spring Boot 서비스
  • AI 채팅 에이전트 (AI chat agent) - Spring AI + Gemini를 사용하며 필요할 때 MCP 도구를 호출하는 Spring Boot 서비스
  • React 채팅 UI (React chat UI) - 메시지를 전송하고 응답을 렌더링하는 가벼운 프론트엔드

그 결과, 프로덕션 어시스턴트로 확장 가능한 작지만 현실적인 아키텍처가 완성되었습니다.

아키텍처 (Architecture)

User (Browser:3000)
    | POST /api/chat
    v
...

전체 소스 코드는 GitHub에서 확인할 수 있습니다.

1. MCP 도구 서버 (The MCP Tool Server)

도구 서버는 Spring AI의 어노테이션 스캐너 (annotation scanner)를 통해 MCP 도구를 노출하는 Spring Boot 애플리케이션입니다. 이 서버는 7170 포트에서 실행되며 전송을 위해 Streamable HTTP를 사용합니다.

의존성 (Dependencies)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
...

도구 정의하기 (Defining tools)

Spring AI를 사용하면, 도구는 단순히 @McpTool 어노테이션이 붙은 Spring bean 메서드일 뿐입니다:

@Component
public class WeatherTool {

...

Spring은 해당 메서드를 MCP 도구 정의로 변환하고, 파라미터 메타데이터를 스키마 (schema)의 일부로 게시합니다. 이는 모델이 도구를 발견하고, 입력을 이해하며, 언제 호출할지 결정할 수 있음을 의미합니다.

이 프로젝트에는 도시 이름을 좌표로 변환하는 지오코딩 (geocoding) 도구도 포함되어 있습니다:

@McpTool(name = "geocode_city",
         description = "OpenStreetMap Nominatim을 사용하여 도시 이름을 위도와 경도로 변환합니다")
public Map<String, Object> geocodeCity(
...

서비스 계층 (The service layer)

도구(tools)는 유효성 검사(validation), 캐싱(caching), 그리고 외부 API 호출을 처리하는 서비스로 실제 작업을 위임합니다:

@Service
public class WeatherToolService {

...

주요 설계 선택 사항은 다음과 같이 명확합니다:

  • 별도의 TTL 캐시 (Separate TTL caches): station-id 및 좌표 조회용
  • 구조화된 응답 (Structured responses): success, error_code, error_message 포함
  • 캐시 메타데이터 (Cache metadata): 각 응답에 포함되어 결과가 캐시에서 왔는지 아니면 상위 시스템(upstream)에서 왔는지 확인할 수 있음

서버 설정 (Server configuration)

server:
  port: 7170

...

STREAMABLE 프로토콜은 에이전트에게 경량화된 MCP 전송(transport) 방식을 제공하며, 공유 API 키를 사용하여 전체 인증 인프라를 추가하지 않고도 데모를 단순하게 유지합니다.

2. 데모를 위한 보안 (Security for the Demo)

MCP 서버와 에이전트는 MCP_API_KEY를 공유합니다. 에이전트는 이를 X-API-Key 헤더로 자동 추가하며, 서버는 들어오는 MCP 요청에서 이를 검증합니다.

이는 로컬 개발 및 샘플 프로젝트에는 충분합니다. 외부로 공개되는 서비스의 경우, Spring Security, OAuth2 또는 JWT, 속도 제한(rate limiting), 그리고 MCP 엔드포인트 앞단의 게이트웨이(gateway)로 전환해야 합니다.

3. AI 채팅 에이전트 (The AI Chat Agent)

에이전트는 도구를 언제 사용할지 결정하고, Gemini를 호출하며, 대화 상태를 유지(stateful)하는 역할을 담당합니다.

의존성 (Dependencies)

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-google-genai</artifactId>
...

MCP 클라이언트 설정 (MCP client configuration)

에이전트는 커스텀 HTTP 요청 커스터마이저(custom HTTP request customizer)를 통해 공유 API 키를 주입합니다:

@Configuration
public class AgentConfiguration {

...

핵심 채팅 흐름 (Core chat flow)

에이전트는 작은 규모의 인메모리(in-memory) 대화 기록을 유지하며, 사용자 메시지가 도구 요청처럼 보이는지 확인한 후, 일반 Gemini 클라이언트 또는 도구 사용이 활성화된 클라이언트를 통해 프롬프트를 라우팅합니다.

지연 초기화(lazy initialization)는 의도적입니다. 에이전트는 MCP 서버가 다운되어 있어도 시작할 수 있으며, 도구 요청이 실제로 도착했을 때만 MCP 클라이언트를 초기화합니다.

도구 트리거는 의도적으로 간단하게 설계되었습니다:

private static boolean shouldUseTools(String userMessage) {
    String normalized = userMessage.toLowerCase(Locale.ROOT);
    for (String keyword : TOOL_KEYWORDS) {
...

이 휴리스틱(heuristic)만으로도 데모를 진행하기에 충분하고 설명하기도 쉽습니다. 더 큰 시스템에서는 라우터 모델이나 의도 분류기(intent classifier)로 대체할 수 있습니다.

가상 스레드 및 타임아웃 처리

모델 호출은 설정 가능한 타임아웃이 있는 가상 스레드(virtual thread)에서 실행되므로, Gemini가 느리거나 연결할 수 없을 때 요청이 영원히 멈추는 것을 방지합니다:

private String invokeModel(ChatClient client, String prompt) {
    var executor = Executors.newVirtualThreadPerTaskExecutor();
    try {
...

세션 메모리

대화 기록은 작은 세션별 턴(turn) 창을 가진 인메모리 LRU 스토어에 저장됩니다. 이렇게 하면

5. 종합하기

애플리케이션 실행하기

  1. 환경 변수를 설정합니다:
export GEMINI_API_KEY=your_gemini_api_key
export MCP_API_KEY=a_shared_secret
  1. MCP 서버를 시작합니다:
cd mcp-server-spring
mvn spring-boot:run
  1. 에이전트 (Agent)를 시작합니다:
cd mcp-spring-agent
mvn spring-boot:run
  1. UI를 시작합니다:
cd mcp-ui
npm install
npm run dev

질문을 했을 때 일어나는 일

사용자가 "베를린 날씨 어때?"라고 질문하면, 흐름은 다음과 같습니다:

  1. 에이전트가 "날씨"라는 단어를 감지하고 도구 사용 가능 모드 (tool-enabled mode)로 전환합니다.
  2. Gemini가 좌표를 얻기 위해 geocode_city("Berlin")를 호출합니다.
  3. 에이전트가 get_current_weather(lat=52.52, lon=13.41)를 호출합니다.
  4. Gemini가 원시 데이터 (raw data)를 읽기 쉬운 응답으로 변환합니다.
  5. UI가 답변을 렌더링합니다.

6. 이 아키텍처가 효과적인 이유

MCP는 모델과 도구를 분리합니다. 에이전트는 어떤 도구가 존재하고 어떻게 호출해야 하는지는 알지만, 해당 도구들이 어떻게 구현되었는지는 알 필요가 없습니다. 이는 시스템을 더 쉽게 발전시킬 수 있게 합니다.

동일한 서버가 서로 다른 모델을 서비스할 수 있습니다. 이 데모에서 Gemini는 단지 하나의 모델일 뿐입니다. MCP 서버 자체는 호환 가능한 어떤 클라이언트와도 작동할 수 있습니다.

지연 초기화 (Lazy initialization)가 앱의 회복탄력성을 유지합니다. MCP 서버를 일시적으로 사용할 수 없는 경우에도 에이전트는 부팅될 수 있으며, 도구 지원은 실제로 필요할 때만 활성화됩니다.

7. 다음 단계

이 샘플은 견고한 시작점입니다. 자연스러운 다음 단계는 다음과 같습니다:

  • Docker Compose - 모든 서비스를 함께 실행
  • PostgreSQL 영속성 (Persistence) - 내구성이 있는 채팅 기록 및 더 풍부한 메모리
  • OAuth2 - 인증된 다중 사용자 액세스
  • WebSocket 스트리밍 (Streaming) - 토큰 단위 (token-by-token) 응답
  • Kubernetes - 에이전트와 도구 서버를 독립적으로 확장

리소스 (Resources)

MCP와 Spring AI를 사용하여 무언가를 구축해 보셨나요? 여러분이 어떻게 접근했는지 정말 궁금합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0