MCP vs RAG vs AI Agent: 상세 설명 및 예제
1. 개요
MCP, RAG, AI Agent은 LLM 애플리케이션에 대해 말할 때 곳곳에서 나타나며, 이들을 “서로 경쟁하는 세 가지 선택지”로 착각하기 쉽습니다. 실제로 이들은 세 가지 다른 문제를 해결합니다: RAG는 LLM이 자체 데이터를 기반으로 올바르게 답변하도록 도와주고, AI Agent는 LLM이 스스로 계획을 세우고 행동하도록 도와주며, MCP는 LLM 앱이 도구/데이터와 통일된 방식으로 연결할 수 있게 하는 통신 표준입니다. 이 글을 읽으면 세 가지 개념을 명확히 구분할 수 있고, 각각을 언제 사용해야 하는지 알 수 있으며, 실제로 어떻게 작동하는지 이해할 수 있는 실행 가능한 Java/Spring AI 코드 몇 가지를 배우게 됩니다.
2. RAG란 무엇인가
RAG (Retrieval-Augmented Generation)는 LLM을 호출하기 전에 외부 데이터를 프롬프트에 “삽입”하는 기술로, 모델이 학습 중에 습득한 지식만 기반하지 않고 실제 데이터에 기반하여 답변하도록 합니다.
2.1. RAG의 작동 흐름
- 원본 텍스트(정책, 문서, FAQ)를 임베딩(숫자 벡터)으로 변환하여 벡터 데이터베이스에 저장합니다.
- 사용자가 질문하면 질문을 임베딩으로 변환하여 가장 유사한 텍스트 섹션을 찾습니다(유사도 검색).
- 찾은 섹션을 프롬프트에 첨부한 후 LLM으로 전송하여 답변을 생성합니다.
예: OrderService는 모델의 일반적인 지식이 아닌 회사 정책 문서를 기반으로 “온라인 주문 교환 정책이 무엇인가?“라는 질문에 답변해야 합니다.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
2.2. 간단한 RAG 코드 예제
@Service
public class ReturnPolicyAssistant {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public ReturnPolicyAssistant(VectorStore vectorStore, ChatClient.Builder builder) {
this.vectorStore = vectorStore;
this.chatClient = builder.build();
}
public String answer(String question) {
List<Document> relevant = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3));
String context = relevant.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
String prompt = """
아래 정책 문서를 기반으로 고객의 질문에 답변하세요.
정보가 없으면 "확실하지 않습니다"라고 말하세요.
문서:
%s
질문: %s
""".formatted(context, question);
return chatClient.prompt(prompt).call().content();
}
}
vectorStore.similaritySearch는 질문과 가장 의미가 유사한 3개의 문서 섹션을 반환합니다.- 프롬프트는 LLM으로 전송하기 전에 벡터 스토어에서 가져온 컨텍스트로 “증강”(augmented)됩니다.
- 문서에 관련 정보가 없으면 모델은 환각(hallucination) 대신 “확실하지 않습니다”라고 답변하도록 안내됩니다.
입력: "온라인으로 5일 전에 셔츠를 구매했는데 교환할 수 있나요?"
출력: "정책에 따르면 상품 수령 후 7일 이내에 교환할 수 있으며, 제품에 태그가 있어야 합니다. 당신의 셔츠는 여전히 유효 기간 내입니다."
참고: RAG는 모델이 “더 많은 데이터를 알도록” 도와줄 뿐입니다. 모델이 스스로 행동하도록(API 호출, 데이터베이스 업데이트) 도와주지 않습니다. 모델이 답변하는 것뿐만 아니라 무언가를 해야 한다면 Agent가 필요합니다.
3. AI Agent란 무엇인가
AI Agent는 LLM을 “뇌”로 사용하여 계획을 세우고, 적절한 도구를 선택하고, 도구를 호출하고, 결과를 관찰한 후 목표에 도달할 때까지 반복하는 시스템입니다. 이는 단순히 질문에 답변하는 것에 그치는 RAG와 다릅니다.
3.1. 이유 → 행동 → 관찰 루프
가장 일반적인 모델은 ReAct입니다: 모델이 추론(reasoning)하고, 호출할 도구를 선택하고(action), 결과를 받은 후(observation), 그 결과에 기반하여 다시 추론합니다.
public interface AgentTool {
String name();
String description();
String execute(String input);
}
@Component
public class CheckOrderStatusTool implements AgentTool {
private final OrderRepository orderRepository;
public CheckOrderStatusTool(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public String name() { return "checkOrderStatus"; }
public String description() { return "orderId로 주문 상태를 확인합니다"; }
public String execute(String orderId) {
return orderRepository.findById(orderId)
.map(o -> "주문 %s이 %s 상태입니다".formatted(orderId, o.getStatus()))
.orElse("주문을 찾을 수 없습니다 " + orderId);
}
}
3.2. Agent가 도구를 선택하고 호출합니다
@Service
public class SupportAgent {
private final ChatClient chatClient;
private final List<AgentTool> tools;
public SupportAgent(ChatClient.Builder builder, List<AgentTool> tools) {
this.chatClient = builder.build();
this.tools = tools;
}
public String handle(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.tools(tools.toArray()) // Spring AI이 자동으로 method execute()를 function-call로 매핑합니다
.call()
.content();
}
}
tools(tools.toArray())은 모델에게 이러한 도구를 호출할 수 있음을 알려줍니다. Spring AI이 자동으로 function-calling을 위한 스키마를 생성합니다.- 모델이 자동으로 결정합니다: 질문이 주문 상태와 관련이 있으면 질문에서 추론한
orderId로checkOrderStatus를 호출하고, 결과를 받은 후 자연어로 다시 설명합니다.
사용자: "내 주문 ORD-2024-891은 어디까지 왔나요?"
Agent: (checkOrderStatus("ORD-2024-891") 도구 호출)
→ "주문 ORD-2024-891이 배송 중이며, 앞으로 2일 안에 도착할 예정입니다."
3.3. 도구 선택 동작 테스트
@Test
void shouldCallCheckOrderStatusWhenAskingAboutOrder() {
AgentTool tool = new CheckOrderStatusTool(orderRepository);
when(orderRepository.findById("ORD-1")).thenReturn(Optional.of(shippedOrder));
String result = tool.execute("ORD-1");
assertThat(result)
.contains("ORD-1")
.contains("SHIPPED");
}
이 테스트는 실제 LLM을 호출하지 않고 도구 로직이 제대로 작동하는지만 확인합니다. “모델이 어떤 도구를 선택하는가”는 eval 세트로 별도로 테스트해야 하며, LLM 출력이 결정론적이지 않으므로 엄격한 단위 테스트는 피해야 합니다.
참고: Agent는 강력하지만 RAG보다 위험성이 높습니다. 도구에 쓰기 권한(환불, 업데이트)이 있으면 모델의 잘못된 추론이 실제 결과를 초래할 수 있습니다. 항상 민감한 도구에 대한 확인 단계(confirmation)를 추가하거나 권한을 제한하세요.
4. MCP란 무엇인가
MCP (Model Context Protocol)는 LLM 애플리케이션(호스트라고 함)이 개별적으로 통합 코드를 작성하는 대신 통일된 프로토콜을 통해 외부 데이터 소스 및 도구(서버라고 함)와 연결하는 방식을 정의하는 개방형 표준입니다.
4.1. MCP가 해결하는 문제
MCP 이전에는 5개의 LLM 앱이 있고 각 앱이 10개의 데이터 소스/도구(데이터베이스, CRM, 파일 시스템…)를 사용하려면 최대 50개의 개별 통합을 작성해야 했습니다. 이는 친숙한 N×M 문제입니다. MCP는 이를 N+M으로 변환합니다: 각 서버는 한 번만 작성하면 MCP 표준을 따르는 모든 호스트에서 호출할 수 있습니다.
4.2. MCP 아키텍처
- 호스트: LLM을 포함한 애플리케이션(챗봇, IDE 어시스턴트…).
- 클라이언트: 호스트 내의 구성 요소로, 하나의 MCP 서버와 1-1 연결을 유지합니다.
- 서버: 3가지 “기능”을 노출하는 프로세스:
- 리소스: 읽을 수 있는 데이터(GET 엔드포인트와 유사).
- 도구: 호출 가능한 작업(POST/action과 유사).
- 프롬프트: 재사용 가능한 프롬프트 템플릿.
4.3. 예제: Java로 MCP 서버 작성하기
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.8.0</version>
</dependency>
public class OrderMcpServer {
public static void main(String[] args) {
McpSyncServer server = McpServer.sync(StdioServerTransport.builder().build())
.serverInfo("order-service", "1.0.0")
.tool(orderLookupTool())
.build();
server.start();
}
private static McpServerFeatures.SyncToolSpecification orderLookupTool() {
var schema = """
{
"type": "object",
"properties": { "orderId": { "type": "string" } },
"required": ["orderId"]
}
""";
return new McpServerFeatures.SyncToolSpecification(
new Tool("getOrderById", "orderId로 주문을 조회합니다", schema),
(exchange, args) -> {
String orderId = (String) args.get("orderId");
String status = OrderLookup.findStatus(orderId);
return new CallToolResult(List.of(new TextContent(status)), false);
});
}
}
McpServer.sync(...)는stdio전송을 사용하는 서버를 구축합니다(호스트는 서버를 서브프로세스로 실행하고 stdin/stdout을 통해 통신합니다).tool(orderLookupTool())은 입력을 설명하는 JSON 스키마를 가진getOrderById도구를 등록하므로 MCP 표준을 따르는 모든 호스트가 “이 도구에 어떤 필드가 필요한가”를 읽을 수 있습니다.- 클라이언트가
getOrderById를 호출하면 서버가 텍스트 내용을 포함하는CallToolResult를 반환합니다.
4.4. Spring AI 클라이언트에서 MCP 서버 호출하기
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
spring:
ai:
mcp:
client:
stdio:
connections:
order-service:
command: java
args: ["-jar", "order-mcp-server.jar"]
@Service
public class McpAgentBridge {
private final SyncMcpToolCallbackProvider toolCallbackProvider;
private final ChatClient chatClient;
public McpAgentBridge(SyncMcpToolCallbackProvider toolCallbackProvider,
ChatClient.Builder builder) {
this.toolCallbackProvider = toolCallbackProvider;
this.chatClient = builder.build();
}
public String ask(String question) {
return chatClient.prompt()
.user(question)
.tools(toolCallbackProvider.getToolCallbacks())
.call()
.content();
}
}
- Spring AI이 MCP 서버(
order-mcp-server.jar)를 서브프로세스로 자동 시작하고 stdio를 통해 연결을 유지합니다. toolCallbackProvider.getToolCallbacks()은 서버가 노출하는 모든 도구(여기서는getOrderById)를 가져와 function-calling처럼 모델에 주입하며, 섹션 3에서 수동으로AgentTool을 등록하는 방식과 정확히 같습니다. 유일한 차이점은 이 도구가 공통 표준에 따라 독립적인 프로세스에서 제공된다는 것입니다.
참고: MCP는 LLM의 function-calling을 대체하지 않습니다. 이는 function-calling 아래 있는 전송/표준화 계층이며, 도구를 한 번만 작성하고 여러 호스트(Claude Desktop, IDE, 당신의 앱)에서 재사용할 수 있게 도와주며 매번 통합 코드를 수정할 필요가 없습니다.
5. MCP, RAG, AI Agent 비교
| 기준 | RAG | AI Agent | MCP |
|---|---|---|---|
| 어떤 문제를 해결하는가 | 모델이 자체 데이터/최신 데이터 부족 | 모델이 스스로 행동할 수 없고, 다단계 계획을 세울 수 없음 | 도구/데이터 통합 코드가 중복되고 표준화되지 않음 |
| 작업 단위 | 검색 + 프롬프트 증강 | 이유-행동-관찰 루프 | 호스트 ↔ 서버 연결 프로토콜 |
| 도구/작업을 호출하는가 | 아니요(읽기만) | 예 | 예(agent가 호출할 수 있도록 도구 노출) |
| 위험 수준 | 낮음(검색이 부족한 경우에만 부정확한 답변) | 높음(잘못된 조치 실행 가능) | 기본 도구에 따라 다르며, 프로토콜 자체는 중립적 |
| 상호 관계 | Agent가 사용할 수 있는 “데이터 소스”임 | RAG를 도구로 사용할 수 있으며, MCP를 사용하여 표준화된 도구 호출 | Agent가 RAG/도구를 통일된 방식으로 호출하기 위한 “파이프라인” |
간단히 말해: RAG는 모델에 지식을 제공하고, Agent는 모델에 행동 능력을 제공하며, MCP는 호스트나 도구를 바꿀 때마다 해당 행동/검색을 다시 작성할 필요가 없도록 하는 통신 표준입니다.
6. 실제 시스템에서 세 가지 모두 결합하기
실제로 대규모 프로젝트는 세 가지 중 하나를 선택하지 않고 이들을 겹겹이 쌓습니다:
사용자 → AI Agent (계획 수립, ReAct 루프)
├─ MCP 클라이언트 호출 → MCP 서버 "knowledge-base" → RAG 유사도 검색 결과 반환
├─ MCP 클라이언트 호출 → MCP 서버 "order-service" → 도구 getOrderById
└─ MCP 클라이언트 호출 → MCP 서버 "payment-service" → 도구 refundPayment (확인 필요)
이 아키텍처에서:
- RAG는 별도의 MCP 서버 내에 있습니다(예: “knowledge-base” 서버는 유사도 검색 결과를 리소스로 노출합니다).
- Agent는 추론 루프를 실행하는 오케스트레이터(orchestrator) 부분이며, 도구가 무엇으로 작성되었는지는 상관없고 도구만 MCP를 준수하면 됩니다.
- MCP는 Agent와 모든 서버 간의 통일된 통신 계층으로, 새로운 서버(예: “shipping-service”)를 추가할 때 Agent 코드를 수정할 필요가 없습니다.
7. 일반적인 오류
- 작업이 필요할 때 RAG 사용: 프롬프트에 데이터를 채우지만 모델이 자동으로 데이터베이스를 업데이트할 것으로 예상합니다. RAG는 이를 할 수 없으므로 쓰기 권한이 있는 Agent + 도구가 필요합니다.
- Agent 도구 권한 제한 없음: Agent가 인간 확인 단계 없이
deleteOrder또는refundPayment와 같은 도구를 직접 호출하도록 허용하면, 모델의 잘못된 추론으로 인해 손해가 발생합니다. - MCP를 “AI 프레임워크”로 착각: MCP는 단순한 연결 프로토콜일 뿐이며, 자체로 추론하거나 검색하지 않습니다. 서버가 유용한 도구/리소스를 노출하지 않으면 MCP가 있어도 모델을 더 똑똑하게 만들 수 없습니다.
- RAG에서 top-k가 너무 작거나 큼: 작은 top-k(1-2)는 컨텍스트가 부족하기 쉽고, 큰 top-k(10+)는 프롬프트를 길게 만들어 토큰을 낭비하고 중요한 신호를 희석시킵니다. 3-5부터 시작하여 실제 eval 세트로 다시 측정하세요.
- MCP 도구의 스키마 버전 관리 없음: 버전을 증가시키지 않고 도구의 입력 스키마를 변경하면 이전 서버를 사용 중인 다른 호스트가 잘못된 매개변수로 호출합니다.
8. 결론
RAG, AI Agent, MCP는 서로 경쟁하지 않습니다. 이들은 LLM 시스템의 세 가지 다른 계층에 있습니다: RAG는 데이터 계층, Agent는 의사결정 및 행동 능력 계층, MCP는 도구를 바꾸거나 호스트를 바꿀 때마다 다시 작성할 필요가 없도록 하는 연결 표준 계층입니다. 실제 AI 시스템을 설계할 때는 “모델이 추가로 알아야 할 것이 무엇인가”(RAG), “모델이 해야 할 것이 무엇인가”(Agent), “그 도구/데이터를 여러 곳에서 재사용하려면 어떻게 해야 하는가”(MCP) 순서로 시작해야 합니다. 이 순서는 처음부터 과도한 엔지니어링을 피하는 데 도움이 됩니다.