LangChain 챗봇5장 · LLM 외부 도구 연동11 / 21← 이전목차다음 →MetaContents

5장. LLM 외부 도구 연동

FastMCP 로 서버 만들고 LangChain 에 붙이기

가게를 열려면 간판을 달고, 메뉴판을 걸고, 문을 열면 됩니다. FastMCP 로 MCP 서버를 만드는 것도 세 걸음입니다. FastMCP("이름") 으로 간판을 달고, @mcp.tool() 로 메뉴를 걸고, mcp.run() 으로 문을 엽니다. 손님 쪽에서는 MultiServerMCPClient 가 가게를 찾아가 메뉴를 받아 오고, 어댑터가 그 메뉴를 LangChain 도구로 바꿉니다.

FastMCP 서버, MultiServerMCPClient, LangChain 도구의 세 걸음을 이은 흐름도

세 상자는 서버 파일 하나가 우리 챗봇의 도구가 되기까지입니다. FastMCP 로 만든 서버 파일(1)을 클라이언트가 띄워 이어지고(2), get_tools() 로 받은 도구는 @tool 로 만든 도구와 같은 모양이라 bind_tools 와 에이전트에 그대로 넣습니다(3). 여기서 간판이 서버 이름, 메뉴판이 @mcp.tool() 로 건 함수들, 가게를 찾아가는 손님이 클라이언트입니다. 비유와 다른 점은, 이 가게는 손님(클라이언트)이 올 때 문을 열고 손님이 가면 닫는다는 것입니다. stdio 서버는 클라이언트가 켜고 끕니다.

FastMCP 서버의 세 걸음

걸음코드뜻
간판 달기mcp = FastMCP("trip-data")서버 이름을 정한다
메뉴 걸기@mcp.tool() 을 함수 위에docstring 이 설명, 타입 힌트가 인자가 된다
문 열기if __name__ == "__main__": mcp.run()파일을 실행하면 stdio 로 요청을 기다린다

MultiServerMCPClient 의 설정

열쇠뜻이 장의 값
바깥 열쇠서버에 붙인 이름. 여럿 적으면 서버도 여럿"trip"
command서버를 켤 프로그램sys.executable(지금 쓰는 파이썬)
args그 프로그램에 넘길 인자["out_ch05_server.py"]
transport전송 방식"stdio"

sys.executable 은 지금 이 코드를 돌리는 파이썬의 경로입니다. 그냥 "python" 이라고 적으면 가상 환경(.venv)이 아닌 다른 파이썬이 켜져 mcp 패키지를 못 찾을 수 있습니다.

짧은 예시

서버 객체를 만들고 도구를 걸어, 도구 목록과 인자 스키마를 봅니다. @mcp.tool() 은 함수를 그대로 돌려주므로, 서버를 띄우지 않고 함수로 바로 시험할 수도 있습니다. 끝으로 클라이언트 설정을 만들어 봅니다. 클라이언트를 만드는 것만으로는 서버가 켜지지 않습니다.

Python
import asyncio
import sys

from langchain_mcp_adapters.client import MultiServerMCPClient
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("trip-demo")              # 간판 달기


@mcp.tool()                             # 메뉴 걸기
def count_people(adults: int, children: int = 0) -> str:
    """어른과 아동 수를 받아 일행의 인원을 알려 준다."""
    return f"일행은 모두 {adults + children}명입니다."


for t in asyncio.run(mcp.list_tools()):
    print(t.name, "|", t.description)
    print("  인자:", t.inputSchema["properties"], "| 꼭 넣을 것:", t.inputSchema["required"])

# 서버를 띄우지 않고 함수로 바로 시험한다
print("함수로 시험:", count_people(2, 1))

# 클라이언트 설정만 만든다. 서버는 get_tools() 를 부를 때 켜진다
client = MultiServerMCPClient({
    "trip": {"command": sys.executable, "args": ["out_ch05_server.py"], "transport": "stdio"},
})
print("붙을 서버:", list(client.connections))
출력
count_people | 어른과 아동 수를 받아 일행의 인원을 알려 준다.
  인자: {'adults': {'title': 'Adults', 'type': 'integer'}, 'children': {'default': 0, 'title': 'Children', 'type': 'integer'}} | 꼭 넣을 것: ['adults']
함수로 시험: 일행은 모두 3명입니다.
붙을 서버: ['trip']

도구 목록에는 이름, 설명, 인자 스키마가 있고, 기본값을 준 children 은 꼭 넣을 것에서 빠졌습니다. @tool 에서 본 것과 같습니다. 함수로 바로 불러 보면 서버 없이도 도구의 계산이 맞는지 확인할 수 있습니다. 클라이언트에는 trip 이라는 서버 하나가 적혔지만, 아직 켜지지는 않았습니다. 실제로 켜고 도구를 부르는 것은 실습 4에서 합니다.

코드가 의미하는 것

이 코드는 가게를 여는 세 걸음 가운데 간판 달기와 메뉴 걸기까지만 하고, 손님(클라이언트)이 찾아갈 곳을 적어 두는 것입니다. 문 열기(mcp.run())는 하지 않습니다.

코드하는 일챗봇에서 뜻하는 것
mcp = FastMCP("trip-demo")서버 이름을 정한다간판 달기입니다. 서버 하나에 이름 하나를 붙입니다
@mcp.tool()함수를 서버의 도구로 건다메뉴 걸기입니다. docstring 이 설명, 타입 힌트가 인자 스키마가 됩니다
count_people(2, 1)데코레이터를 얹은 함수를 그냥 부른다@mcp.tool() 이 함수를 그대로 돌려주므로 서버 없이 계산만 시험할 수 있습니다. 서버를 띄우기 전에 도구 속이 맞는지 보는 방법입니다
MultiServerMCPClient({"trip": {...}})서버를 켤 명령과 전송 방식을 적은 클라이언트를 만든다손님이 들고 갈 약도입니다. 적어 두기만 하고, 서버는 get_tools() 를 부를 때 켭니다
client.connections적어 둔 서버 설정의 이름을 본다클라이언트가 어느 서버에 붙을지 확인합니다

결과에서 읽을 것은 두 가지입니다.

  • 꼭 넣을 것은 `['adults']` 하나입니다. 기본값을 준 children 은 빠졌습니다. @tool 의 꼭 넣을 인자와 같은 규칙이 MCP 도구에도 적용됩니다.
  • 서버는 아직 켜지지 않았습니다. 붙을 서버: ['trip'] 은 설정에 적은 이름을 보여 줄 뿐입니다. 서버 프로그램을 실제로 켜는 것은 실습 4의 get_tools() 입니다.

주의할 점

  • 클라이언트의 get_tools() 와 MCP 도구의 실행은 비동기 함수입니다. await 를 쓰려면 async def main(): 안에 코드를 넣고 asyncio.run(main()) 으로 시작합니다. 에이전트도 invoke 대신 await agent.ainvoke(...)(a 는 asynchronous)로 부릅니다.
  • MultiServerMCPClient 는 기본으로 도구를 부를 때마다 서버와 새로 이어졌다 끊습니다. 그래서 서버가 기억해 둔 값(변수)은 다음 호출에서 사라집니다. 기억이 필요한 서버는 파일이나 데이터베이스에 적어 둡니다.
  • VS Code 의 노트북(Jupyter)에서는 이미 이벤트 루프가 돌고 있어 asyncio.run() 이 "이미 도는 루프가 있다"는 오류를 냅니다. 이때는 asyncio.run(main()) 대신 await main() 한 줄로 부릅니다. 이 교안의 실습 코드는 python 파일.py 로 실행하는 것을 기준으로 했습니다.
← 이전 절5장 · 호스트·클라이언트·서버의 짜임다음 절 →5장 · 도구를 맡길 때의 안전