MCP(Model Context Protocol)가 AI 도구 연결의 표준으로 자리 잡고 있다

1. 개요

대규모 언어 모델(LLM)은 실제 데이터를 읽고 실제 시스템을 호출할 수 있을 때 진정으로 유용합니다. 예를 들어 주문을 조회하고, 티켓을 읽고, 쿼리를 실행하는 것 같은 작업이죠. 이전에는 각 AI 애플리케이션이 각 도구와의 통합을 독립적으로 작성했습니다. Model Context Protocol(MCP)은 이러한 연결 부분을 표준화하기 위해 등장했습니다.

이 글은 MCP가 무엇인지, 왜 주목받고 있는지 설명합니다. 그 후 Spring Boot로 작은 MCP 서버를 작성하고, 클라이언트를 구성하여 호출하며, MCP를 사용하면 안 되는 경우를 알아봅니다.

2. MCP는 무엇인가?

MCP는 오픈 프로토콜입니다. AI 애플리케이션이 외부 기능을 발견하고 호출하는 방식을 정의합니다. 도구, 데이터, 그리고 프롬프트 템플릿 같은 것들 말이죠. MCP를 AI를 위한 “USB 포트”로 생각하면 됩니다. 표준을 준수하는 모든 기기는 표준을 지원하는 어떤 호스트에든 연결해도 작동합니다.

2.1. 세 가지 주요 역할

  • Host: 사용자가 상호작용하는 애플리케이션입니다. 데스크톱의 AI 어시스턴트, IDE, 또는 내부 챗봇 같은 것들이죠.
  • Client: 호스트 내에 있는 컴포넌트입니다. 각 클라이언트는 하나의 서버와 일대일 연결을 유지합니다.
  • Server: AI에 기능을 제공하는 프로그램입니다. 주문 데이터베이스를 읽는 서버 또는 파일 시스템에 접근하는 서버가 예입니다.

2.2. 서버 측의 세 가지 “primitive” 유형

  • Tools: 모델이 호출할지 말지를 결정할 수 있는 함수입니다. findOrder나 cancelOrder 같은 것들이죠.
  • Resources: 파일 내용이나 구성 레코드 같은 읽기 전용 데이터이며, 컨텍스트에 포함됩니다.
  • Prompts: 사용자가 선택하여 사용할 수 있는 미리 작성된 프롬프트 템플릿입니다.

2.3. 기반 프로토콜

MCP는 JSON-RPC 2.0을 사용합니다. 가장 일반적인 두 가지 전송 방식은 stdio(호스트가 서버를 자식 프로세스로 시작할 때)와 HTTP(서버가 원격으로 실행될 때)입니다. 도구 호출은 다음과 같이 보입니다:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "findOrder",
    "arguments": { "orderId": "ORD-1001" }
  }
}

기본 워크플로우는 세 단계로 이루어집니다:

  1. 클라이언트가 initialize를 전송하여 양쪽이 프로토콜 버전과 지원하는 기능을 교환합니다.
  2. 클라이언트가 tools/list를 호출하여 도구 목록을 가져옵니다. 설명과 매개변수의 JSON Schema가 포함됩니다.
  3. 모델이 도구를 사용하기로 결정하면, 클라이언트는 위 예시처럼 tools/call을 전송한 후 결과를 모델에게 반환합니다.

중요한 점: 모델이 서버를 직접 호출하지 않습니다. 호스트가 중간에 있으므로, 민감한 도구를 실행하기 전에 사용자에게 확인을 요청할 수 있습니다.

3. MCP가 주목받고 있는 이유

3.1. N × M 문제

N개의 AI 애플리케이션과 M개의 시스템을 연결한다고 가정해봅시다. 공통 표준이 없으면 N × M개의 통합을 작성해야 합니다. 공통 프로토콜이 있으면, 각 애플리케이션은 MCP를 한 번만 지원하고, 각 시스템은 하나의 MCP 서버만 필요합니다. 문제가 N + M으로 줄어드는 거죠.

3.2. 책임의 분리

주문 시스템을 소유한 팀이 그 MCP 서버를 작성하고 운영합니다. 챗봇을 만드는 팀은 그 서버를 가리키기만 하면 됩니다. 비즈니스 로직, 접근 권한, 감사 로그가 올바른 위치에 있으며, 각 프롬프트나 애플리케이션에 중복되지 않습니다.

3.3. 특정 모델 제공자에 고정되지 않음

각 LLM 제공자의 function calling은 고유한 형식을 가집니다. MCP는 그 형식 위에 위치합니다. 오늘 작성한 서버는 모델을 바꾸거나 호스트를 바꿔도 계속 작동합니다.

3.4. 생태계가 빠르게 성장 중

점점 더 많은 AI 어시스턴트, IDE, 프레임워크가 MCP를 지원합니다. 여러 언어에 SDK가 있으며, Java는 공식 SDK를 갖추고 있고 Spring AI는 기본 제공합니다. 이것이 많은 사람들이 MCP를 도구 연결의 유력한 표준 후보로 보는 이유입니다. 다만 프로토콜이 계속 발전 중이므로, 장기 배포 시에는 스펙을 꾸준히 따라가야 합니다.

4. Spring Boot로 MCP 서버 작성

주문 조회와 취소 기능을 제공하는 서버를 만들어봅시다. stdio를 통해 실행됩니다.

4.1. 의존성

Spring AI는 MCP 서버용 starter를 제공합니다. Spring AI의 BOM을 가져오고 Spring Boot 3.x와 호환되는 최신 버전을 사용하세요:

<properties>
    <java.version>21</java.version>
    <spring-ai.version><!-- 현재 Spring AI 버전 --></spring-ai.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

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

spring-ai-starter-mcp-server starter는 stdio 전송을 지원합니다. HTTP를 통해 서버를 실행하려면 Spring AI에서 Web MVC와 WebFlux용 별도의 starter를 제공합니다.

참고: Spring AI의 아티팩트 이름은 마일스톤 버전 사이에 변경되어 왔습니다. 선택한 버전에 해당하는 문서를 확인하세요.

4.2. 도메인과 도구

public record Order(String id, String customerName, double total, String status) {}
@Service
public class OrderTools {

    private final Map<String, Order> orders = new ConcurrentHashMap<>(Map.of(
            "ORD-1001", new Order("ORD-1001", "Nguyen Van An", 350_000, "SHIPPING"),
            "ORD-1002", new Order("ORD-1002", "Tran Thi Binh", 120_000, "PENDING")
    ));

    @Tool(description = "주문 ID로 주문을 조회하고, 고객명, 총액, 상태를 반환합니다")
    public Order findOrder(@ToolParam(description = "주문 코드, 예: ORD-1001") String orderId) {
        Order order = orders.get(orderId);
        if (order == null) {
            throw new IllegalArgumentException("주문을 찾을 수 없음 " + orderId);
        }
        return order;
    }

    @Tool(description = "주문을 취소합니다. PENDING 상태의 주문만 취소할 수 있습니다")
    public String cancelOrder(@ToolParam(description = "취소할 주문 코드") String orderId) {
        Order order = findOrder(orderId);
        if (!"PENDING".equals(order.status())) {
            return "취소할 수 없음: 주문이 " + order.status() + " 상태입니다";
        }
        orders.put(orderId, new Order(order.id(), order.customerName(), order.total(), "CANCELLED"));
        return "주문 " + orderId + "를 취소했습니다";
    }
}

두 가지 annotation이 중요한 역할을 합니다:

  • @Tool (org.springframework.ai.tool.annotation 패키지)은 메서드를 도구로 표시합니다. description 부분이 모델이 도구를 호출할지 말지 결정할 때 읽는 내용입니다. 설명이 명확할수록 모델이 더 정확하게 선택합니다.
  • @ToolParam은 각 매개변수를 설명합니다. Spring AI는 매개변수의 타입과 설명으로부터 JSON Schema를 생성합니다.

cancelOrder가 예외를 던지지 않고 메시지를 반환하는 점에 주목하세요. 모델이 메시지를 읽고 사용자에게 자연스럽게 설명합니다.

4.3. MCP 서버에 도구 등록

@SpringBootApplication
public class OrderMcpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderMcpServerApplication.class, args);
    }

    @Bean
    ToolCallbackProvider orderToolProvider(OrderTools orderTools) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(orderTools)
                .build();
    }
}

MethodToolCallbackProvider는 orderTools에서 @Tool을 가진 메서드를 스캔합니다. MCP 서버의 자동 설정이 모든 ToolCallbackProvider 빈을 찾아 tools/list를 통해 공개합니다.

4.4. stdio 설정

spring.ai.mcp.server.name=order-mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.stdio=true

spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/order-mcp-server.log

stdio를 사용할 때, stdout은 JSON-RPC 메시지 채널입니다. stdout에 출력되는 모든 것이 프로토콜을 손상시킵니다. 배너, 로그 모두요. 위의 설정은 배너를 비활성화하고, 콘솔 로깅을 비활성화하고, 로그를 파일로 리디렉션합니다.

참고: stdio를 통해 MCP 서버를 처음 구현할 때 가장 흔한 실수입니다. 디버그용 System.out.println 한 줄도 클라이언트에서 파싱 오류나 연결 끊김을 유발할 수 있습니다. 파일로 출력하는 로거를 사용하세요.

jar 파일로 빌드합니다:

./mvnw clean package -DskipTests

5. 클라이언트에서 연결

많은 호스트는 mcpServers 키를 포함하는 JSON 파일을 통해 서버를 구성합니다. 파일 이름과 위치는 호스트마다 다릅니다:

{
  "mcpServers": {
    "orders": {
      "command": "java",
      "args": ["-jar", "/opt/mcp/order-mcp-server.jar"]
    }
  }
}

시작할 때, 호스트는 java -jar ... 명령을 자식 프로세스로 실행하고, initialize를 전송한 후 tools/list를 전송합니다. 당신이 “주문 ORD-1002를 취소할 수 있나요? 가능하면 취소해 주세요”라고 요청합니다. 모델은 findOrder를 호출한 다음 cancelOrder를 호출합니다. 두 번째 호출의 반환 결과는 다음과 같습니다:

{"content":[{"type":"text","text":"주문 ORD-1002를 취소했습니다"}],"isError":false}

일반적으로 호스트는 도구를 실행하기 전에 허가 대화 상자를 표시합니다. cancelOrder 같은 부작용이 있는 도구는 이 동작을 유지해야 합니다.

Spring 애플리케이션이 클라이언트 역할을 하고 싶다면, Spring AI도 MCP 클라이언트 starter를 제공합니다. 이 starter는 외부 서버의 도구를 ChatClient에서 사용할 수 있는 도구로 만들어줍니다.

6. 도구 동작 테스트

도구는 단순한 Java 메서드이므로, MCP를 시작하지 않고도 테스트할 수 있습니다:

class OrderToolsTest {

    private final OrderTools tools = new OrderTools();

    @Test
    void shouldCancelPendingOrder() {
        String result = tools.cancelOrder("ORD-1002");

        assertThat(result).isEqualTo("주문 ORD-1002를 취소했습니다");
        assertThat(tools.findOrder("ORD-1002").status()).isEqualTo("CANCELLED");
    }

    @Test
    void shouldRefuseToCancelShippingOrder() {
        assertThat(tools.cancelOrder("ORD-1001"))
                .contains("취소할 수 없음")
                .contains("SHIPPING");
    }

    @Test
    void shouldFailWhenOrderNotFound() {
        assertThatThrownBy(() -> tools.findOrder("ORD-9999"))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("ORD-9999");
    }
}

세 가지 테스트가 성공 경로, 비즈니스 규칙, 주문을 찾을 수 없는 경우를 다룹니다. 프로토콜 부분은 SDK에서 처리합니다. 확인해야 할 가장 중요한 것은 비즈니스 로직이 올바른 결과를 반환하고 모델이 이해할 수 있는 명확한 메시지를 제공한다는 점입니다.

엔드 투 엔드를 확인하려면, MCP용 inspector 도구를 사용할 수 있습니다. 도구를 나열하고 직접 호출해볼 수 있게 해줍니다.

7. 실제 적용: 주의사항

  • 도구는 비즈니스에 따라, 테이블에 따라 설계하지 마세요. findOrder가 만능의 executeSql 도구보다 낫습니다. 좁은 도구는 권한 관리가 쉽고 모델이 올바르게 선택하는 데 도움이 됩니다.
  • 최소 권한 원칙을 적용합니다. 서버는 필요한 정확한 권한만 가진 계정을 사용해야 합니다. 읽기 도구와 쓰기 도구를 분리하고, 쓰기 도구는 특정 환경에서만 활성화하는 것을 고려하세요.
  • 프롬프트 injection에 주의합니다. 도구가 반환하는 데이터(예: 고객이 입력한 메모)에는 모델을 제어하려는 명령이 포함될 수 있습니다. 한 도구의 결과가 사용자 확인 없이 위험한 작업을 자동으로 실행하지 않도록 하세요.
  • 신뢰할 수 있는 서버만 사용합니다. stdio를 통한 타사 MCP 서버는 당신의 기계에서 당신의 권한으로 실행되는 프로세스입니다. 도구 설명도 악의적인 내용을 포함할 수 있습니다. 소스 코드를 읽거나 신뢰할 수 있는 출처에서만 사용하세요.
  • 원격 서버에는 인증을 설정합니다. HTTP를 통해 실행할 때, 서버를 적절한 인증 메커니즘 뒤에 두고 모든 도구 호출에 대한 감사 로그를 기록하세요.
  • 도구의 개수를 적절히 유지합니다. 각 도구는 설명을 위해 컨텍스트를 사용합니다. 도구가 너무 많으면 모델이 잘못된 도구를 선택하기 쉬워집니다.

8. MCP를 사용하면 안 되는 경우

  • 애플리케이션이 작고, 모델이 하나이며, 함수가 몇 개뿐입니다. 프레임워크의 직접 function calling(예: ChatClient에서 직접 사용하는 @Tool)이 더 간단합니다. 추가 프로세스와 프로토콜 계층은 필요 없습니다.
  • 처리 흐름이 고정되어 있고 모델이 결정할 필요가 없습니다. 단계 1이 항상 API A를 호출하고 단계 2가 항상 API B를 호출하는 경우, 일반 코드를 작성하세요. if 문으로 충분한 일을 LLM에 맡기지 마세요.
  • 매우 낮은 레이턴시 또는 높은 결정성이 필요합니다. 모델 → 클라이언트 → 서버의 각 왕복은 레이턴시를 더합니다. 모델의 결과도 완전히 결정적이지 않습니다.
  • 되돌릴 수 없는 작업인데 사람이 검토하지 않습니다. 송금이나 대량 데이터 삭제는 자동 도구로 노출되면 안 됩니다. 꼭 필요하다면, 항상 확인 단계를 포함하세요.
  • 다른 시스템이 호출할 API가 필요합니다. MCP는 AI에 기능을 제공하기 위해 만들어졌습니다. 서비스 간에는 REST, gRPC, 메시징이 여전히 올바른 선택입니다.

9. 결론

MCP는 매우 현실적인 문제를 해결합니다. 각 애플리케이션이 자체 방식을 작성하는 대신, 일반적인 방식으로 AI를 도구와 데이터에 연결합니다. host–client–server 모델, tools/resources/prompts의 세 가지 primitive, JSON-RPC 기반 덕분에 작성한 서버는 여러 호스트에서 재사용할 수 있습니다.

Spring AI를 사용하면, @Tool annotation, ToolCallbackProvider 빈, stdio 설정만으로 MCP 서버를 작성할 수 있습니다. 어려운 부분은 다른 곳에 있습니다. 좁고 명확한 도구 설계, 엄격한 권한 관리, 프롬프트 injection 예방. 그리고 평범한 코드가 여전히 더 나은 솔루션인 경우를 인식하는 것이 중요합니다.

mcpaispring-aispring-boottech-trends