MCP와 LangChain으로 AI 에이전트 만들기

AI 에이전트가 단순한 대화 수준을 넘어서 실제 도구(Tool)를 호출하고, API와 통신하며, 실시간 데이터를 처리하려면 어떻게 해야 할까요? 이번 글에서는 MCP(Model Context Protocol)와 LangChain ReAct 기반 에이전트를 활용해 실제로 행동할 수 있는 AI 에이전트를 구현하는 방법을 단계별로 소개합니다.

여기서 사용한 데모 코드는 mcp_langchain_demo GitHub 저장소에서 다운로드를 받을 수 있습니다.

1. MCP란 무엇인가?

AI 에이전트가 외부 시스템이나 서비스를 활용하려면, 일반적으로 각 서비스마다 서로 다른 인터페이스와 통신 규약을 다루어야 합니다. 이 과정에서 맞춤형 코드를 작성해야 하고, 유지보수와 확장성이 복잡해지는 문제가 발생합니다. MCP(Model Context Protocol)는 이러한 문제를 해결하기 위해 Anthropic에서 정의한 AI 에이전트용 오픈 표준으로, 에이전트와 외부 도구 간의 표준화된 통신 인터페이스를 제공합니다.

  • JSON-RPC 기반으로 도구와 연결
  • 다양한 통신 프로토콜 지원: HTTP, SSE, stdin/stdout 등
  • 하나의 에이전트가 여러 MCP 서버의 도구를 동시에 호출하고 병렬
  • 단순 텍스트 응답뿐만 아니라 구조화된 JSON 데이터와 이벤트 스트리밍을 지원
  • ReAct, Tool Calling, Function Calling 등 다양한 패턴과 결합하여 사용

MCP는 “AI 도구용 USB-C"와 같이, 각 도구마다 별도 코드를 만들 필요 없이 하나로 모두 연결할 수 있습니다. MCP를 사용하면 에이전트는 특정 도구나 서비스의 세부 구현에 의존하지 않고, 표준화된 방식으로 도구를 호출하고 결과를 받을 수 있습니다. 이로써 에이전트 설계는 모듈화, 확장성, 이식성 측면에서 크게 개선됩니다.


2. LangChain ReAct 패턴

ReAct(Reason + Act) 패턴은 AI가 추론(Reasoning)과 행동(Acting)을 반복하면서 문제를 단계적으로 해결하는 구조입니다.

  • Reasoning: 입력을 분석하고, 문제 해결을 위해 어떤 도구나 행동을 선택할지 결정합니다.
  • Acting: 선택한 도구를 실제로 호출하고, 그 결과를 다음 추론 과정에 반영합니다.

LangChain의 create_agent 함수는 이러한 ReAct 기반 에이전트를 쉽게 생성할 수 있도록 도와줍니다. 여기에 MCP를 연결하면 외부 시스템이나 도구를 자유롭게 호출하며, 훨씬 강력하고 확장성 있는 에이전트를 만들 수 있습니다.


3. 환경 구성 및 패키지 설치

GitHub에서 mcp_langchain_demo 저장소를 클론 후에 가상환경을 구성하고 패키지를 설치합니다.

git clone https://github.com/cnapcloud/mcp_langchain_demo.git
cd mcp_langchain_demo


python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

4. MCP 서버 구현

MCP(Model Context Protocol) 서버는 AI 에이전트와 외부 도구 간의 표준화된 통신 인터페이스를 제공합니다. 에이전트는 MCP 서버를 통해 다양한 도구를 호출하고, 도구의 결과를 받아 다음 추론에 활용할 수 있습니다. 즉, MCP 서버는 AI가 외부 시스템과 안전하고 효율적으로 상호작용하도록 돕는 중간 허브 역할을 합니다.

다음 코드는 FastMCP를 이용해 echo, add, greet 도구를 등록하고 SSE 방식으로 실행하는 MCP 서버 예제입니다.

“mcp_server.py”

from fastmcp import FastMCP

mcp = FastMCP("example-mcp")

@mcp.tool
def echo(text: str) -> str:
    return text

@mcp.tool
def add(a: float, b: float) -> float:
    return a + b

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run(transport="sse", host="0.0.0.0", port=8001)

5. MCP 도구 호출 테스트

아래 코드는 FastMCP 클라이언트를 사용해 MCP 서버에 등록된 도구를 조회하고 호출하는 예제입니다.

  • MCP 서버 접속을 위한 클라이언트 생성
  • 도구 목록 조회 (show_tools): MCP 서버에 등록된 도구 이름과 설명 조회
  • 도구 호출 (call_add_tool): MCP 서버에 등록 add 도구 호출

“fastmcp_client.py”

import asyncio
from fastmcp import Client
from functools import wraps

_client_instance = None
_client_url = None

# 데코레이터: Client 싱글톤 사용
def with_client(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        global _client_instance, _client_url
        if _client_instance is None:
            if _client_url is None:
                raise RuntimeError("Client URL is not specified. You need to set _client_url.")
            _client_instance = Client(_client_url)
            await _client_instance.__aenter__()
            print(f"Connection URL: {_client_url}\n")

        return await func(*args, **kwargs)
    return wrapper

# 도구 목록 조회 및 출력
@with_client

async def show_tools():
    tools = await _client_instance.list_tools()  # 기존 호출
    print("Available Tools:")
    for tool in tools:
        name = tool.name
        description = tool.description or "No description"
        print(f"- {name}: {description}")
    return tools

# 도구 호출 및 결과 출력
@with_client
async def call_add_tool(a=5, b=3):
    result = await _client_instance.call_tool("add", {"a": a, "b": b})
    print(f"Call Add Tool:")
    print(f"- Input: a={a}, b={b}")
    print(f"- Output: {result.content[0].text}")
    return result

if __name__ == "__main__":
    async def main():
        global _client_url
        _client_url = "http://localhost:8001/sse"

        await show_tools()
        await call_add_tool()

        # Client 종료
        await _client_instance.__aexit__(None, None, None)

    asyncio.run(main())

다음과 같이 서버와 클라이언트를 실행합니다.

python3 mcp_server.py
python3 fastmcp_client.py

6. AI 에이전트 구성을 위한 LLM 설정

LangChain AI 에이전트를 통해 MCP 서버의 도구를 활용하려면, 먼저 사용할 LLM을 준비하고 LangChain에서 해당 LLM을 지정해야 합니다.

Ollama 사용 시

Ollama는 로컬에서 LLM을 실행하고 관리할 수 있는 도구입니다.
모델을 다운로드하고 서버를 실행해야 사용할 수 있습니다.

# Ollama 서버 실행
ollama run

# 모델 다운로드 (예: qwen3:8b 모델)
ollama pull qwen3:8b

이제 Agent에서 사용할 모델을 환경변수에 설정합니다.

# 환경 변수 설정
export OLLAMA_MODEL="qwen3:8b"
  • 모델 다운로드 후 ollama list 명령으로 설치된 모델을 확인할 수 있습니다.
  • Ollama 모델은 Tool Calling(함수 호출) 기능을 지원해야 합니다.

OpenAI 사용 시

OpenAI API 기반 LLM을 사용하려면 API 키를 환경 변수에 설정해야 합니다.

# 환경 변수 설정
export LLM_TYPE="openai"
export OPENAI_API_KEY="YOUR_API_KEY"

이 설정에 대한 자세한 구성은 “conf/config.py” 파일을 참조하세요.


7. MCP 도구를 활용한 LangChain ReAct 에이전트 구현

이 코드는 MCP(Multi-Server MCP Client)와 LangChain 기반 ReAct 에이전트를 통합하여 외부 도구 호출이 가능한 LLM 에이전트를 구성합니다.

  • MCP 서버에 연결하여 사용 가능한 도구 정보를 가져옴
  • LangChain create_agent에 메시지 처리를 담당할 LLM과 도구를 전달하여 에이전트 생성
  • 비동기적으로 메시지 호출(invoke) 및 스트리밍(stream) 지원
  • 메시지를 LangChain이 처리할 수 있는 포맷으로 변환하는 체인 구성

“langchain_client.py”

import asyncio
import traceback
from typing import Any, AsyncGenerator, Dict, Generator
from langchain_core.runnables import RunnableLambda
from langchain_core.messages import AIMessage, ToolMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from llm.manager import get_llm
from utils.stream_wrapper import RunnableStreamWrapper
from conf import config

MCP_SERVER_URL = config.MCP_SERVER_URL
_agent_instance = None

# MCP 에이전트 생성
async def create_mcp_agent():
    """Create or return a singleton MCP-based ReAct agent."""
    global _agent_instance
    if _agent_instance is not None:
        return _agent_instance

    client = MultiServerMCPClient({
        "add": {"transport": "sse", "url": MCP_SERVER_URL}
    })
    tools = await client.get_tools()

    _agent_instance = create_agent(
        model=get_llm(),
        tools=tools,
        system_prompt="You are a helpful assistant."
    )
    return _agent_instance

# MCP 에이전트 호출
async def invoke_mcp_agent(data: Dict[str, Any]) -> str:
    """Invoke the MCP agent and return the final response text."""
    messages = data.get("messages")
    if not messages:
        raise ValueError("The 'messages' field is required.")
    agent = await get_mcp_agent()
    response = await agent.ainvoke({"messages": messages})
    return response["messages"]

# MCP 에이전트 스트리밍 응답에서 텍스트 청크만 추출
async def stream_text_chunks(data: Dict[str, Any]) -> AsyncGenerator[str, None]:
    """
    agent.stream()으로부터 오는 token 중 content type이 'text'인 경우만 yield
    """
    agent = await get_mcp_agent()
    async for token, metadata in agent.astream(
        data,
        stream_mode="messages",
    ):
        for block in token.content_blocks:
            if block.get("type") == "text":
                yield block.get("text", "")
                
# 질문을 LangGraph 형식으로 변환
def to_message_format(text: str) -> Dict[str, Any]:
    """Convert input text into LangGraph message format."""
    return {"messages": [("user", text)]}

# Runnable 체인 생성
mcp_chain = RunnableLambda(to_message_format) | invoke_mcp_agent
mcp_chain_stream = RunnableLambda(to_message_format) |  RunnableStreamWrapper(stream_text_chunks)

8. AI 에이전트 실행

아래 예제는 LangChain AI 에이전트와 MCP 서버를 활용하여 메시지를 처리하는 두 가지 방식을 보여줍니다.

배치 처리(ainvoke)

  • 전체 메시지를 한 번에 처리하여 결과를 모든 답변이 포함된 리스트로 반환합니다.
  • 후처리나 로그 기록 시 유용하지만, 실시간 반응을 볼 수는 없습니다.

스트리밍 처리(astream)

  • 메시지를 처리하면서 AI가 생성하는 텍스트를 토큰 단위로 실시간 출력합니다.
  • 사용자 인터페이스에서 점진적으로 답변을 보여주거나, 실시간 반응이 필요한 상황에 적합합니다.

“langchain_client.py”

async def main():
    questions = ["15와 27를 더하는 계산하세요.", "너는 뭐하는 AI야?"]

    # List messages using ainvoke api
    response = await mcp_chain.ainvoke(questions[0])
    print("> Ainvoke: full message list (batch)")
    for i, msg in enumerate(response, start=1):
        print(f"{i}. {getattr(msg, 'type', type(msg))}: {getattr(msg, 'content', str(msg))}")

    # Streaming text chunks for AI messages
    print("> Streaming: text chunks (real-time)")
    for q in questions:
        async for token in mcp_chain_stream.astream(q):
            print(token, end="", flush=True)
        print("\n")

if __name__ == "__main__":
    asyncio.run(main())

LangChain과 MCP를 결합하여 구성한 AI 에이전트를 다음과 실행하여 결과를 확인합니다.

python3 langchain-client.py

여기서 구성한 에이전트는 Reasoning을 통해 도구를 선정하는 과정에서 모델에 따라 간헐적으로 도구 호출을 위한 API 구성이 맞지 않아 오류가 발생하는 경우가 존재한다. 이 경우 다시 실행하거나, 도구 지원이 되는 다른 모델로 변경을 해볼 수 있습니다.

다음은 OpenAI gpt-4o-mini 모델과 Ollama qwen3:8b 모델을 사용하여 이 에이전트를 실행해 본 결과입니다.

dnsmasq web ui

9. VS Code에서 MCP 도구 호출

VS Code에서 MCP 도구 호출을 위해 먼저 VS Code 마켓플레이스에서 AI 개발을 위한 GitHub Copilot Extensions을 설치합니다. 이 방식은 앞에서 LangChain ReAct을 이용해 구성한 AI 에이전트와 유사하게 도구 호출이 AI 기반으로 결정되고 실행되어 그 결과를 제공합니다.

VS Code에서 Cmd + Shift + P(macOS 기준)를 눌러 명령 팔레트에서 MCP: Add Server를 선택합니다. 여기서 HTTP (HTTP or Server Sent Events)를 선택하고 이후 MCP Server URL과 MCP Server Name을 입력하면 다음과 같이 .vscode/mcp.json 파일이 자동으로 생성됩니다.

dnsmasq web ui

여기서 Server Name 위에 Start 버튼을 누르면 MCP Server와 연결되어 새로운 서버가 VS Code내에 시작이 됩니다.
오른쪽의 CHAT 윈도우 하단에서 “툴 리스트를 보여줘"라고 요청하면, MCP 서버에 등록된 도구 목록이 포함한 전체 도구 목록을 보여줍니다. 다시 “3, 5를 더해줘"고 요청하면 다음과 같이 그 결과를 보여준다.

dnsmasq web ui

10. 요약

지금까지 MCP 서버를 구성하고 LangChain의 ReAct 에이전트를 결합하여 AI 에이전트가 외부 도구를 활용하는 샘플을 만들어보고 실행해 보았습니다. 이를 통해 MCP 서버의 기본 개념과 AI 에이전트가 문제를 분석하고 도구를 활용해 결과를 도출하는 과정을 이해할 수 있었습니다.

MCP는 AI 챗봇이 가진 한계를 극복하고, 단순한 대화 도구를 넘어 실제 업무와 서비스에 적용 가능한 솔루션으로 적용이 되고 있습니다. 특히 MCP의 표준화된 도구 연동 구조는 서로 다른 시스템과 서비스를 유연하게 연결해 AI가 다양한 환경 속에서도 일관되고 확장성 있는 방식으로 동작할 수 있도록 돕습니다. 앞으로도 이러한 접근 방식은 AI 에이전트의 활용 범위를 넓히고, 사용자에게 보다 실질적이고 풍부한 경험을 제공하는 핵심 기술로 자리잡을 것입니다.