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

5장. LLM 외부 도구 연동

호스트·클라이언트·서버의 짜임

큰 건물의 관리실을 떠올려 보세요. 관리실(호스트)에는 직원이 여럿 있고, 직원마다 맡은 업체가 하나씩 있습니다. 한 직원은 청소 업체만, 다른 직원은 승강기 업체만 상대합니다. 업체(서버)는 자기 일만 하고, 관리실 안의 사정은 모릅니다. MCP 도 이렇게 나뉩니다. 우리 챗봇(호스트) 안에 클라이언트가 서버마다 하나씩 붙어서 도구를 빌려 옵니다.

호스트(우리 챗봇 앱) 안의 에이전트와 클라이언트 둘, 클라이언트마다 stdio 와 HTTP 로 이어진 서버 하나, 서버 뒤의 자료를 그린 구조도

보라 점선 틀이 호스트, 곧 프로그램 하나입니다. 그 안의 에이전트가 도구가 필요하면 클라이언트에게 부탁하고, 클라이언트는 파랑 선으로 이어진 자기 서버 하나에만 요청을 보냅니다. 위쪽의 자료 서버는 같은 컴퓨터에서 stdio 로, 아래쪽의 다른 서버는 네트워크 너머에서 HTTP 로 이어진 예입니다. 회청 점선 뒤의 자료(data 폴더)는 서버만 읽습니다. 여기서 관리실이 호스트, 직원이 클라이언트, 업체가 서버입니다. 비유와 다른 점은, 관리실 직원은 사람이라 업체를 알아서 고르지만 MCP 클라이언트는 우리가 설정에 적어 준 서버에만 붙는다는 것입니다.

세 역할

역할하는 일이 장에서
호스트(host)사용자와 만나고 모델을 부른다. 클라이언트를 품는다우리 챗봇 프로그램(실습 코드)
클라이언트(client)서버 하나와 이어져 도구 목록을 받고 도구를 부른다MultiServerMCPClient 가 서버마다 만든다
서버(server)도구와 자료를 내놓고, 요청이 오면 실행한다out_ch05_server.py(FastMCP)

두 가지 전송 방식

전송 방식원래 말어떻게 잇나어울리는 때
stdiostandard input/output클라이언트가 서버 프로그램을 직접 켜고, 표준 입력과 표준 출력으로 글을 주고받는다같은 컴퓨터의 서버. 이 장의 실습
streamable_http흘려보낼 수 있는 HTTP이미 떠 있는 서버의 주소(URL)로 HTTP 요청을 보낸다다른 컴퓨터나 회사 서버
클라이언트가 서버 프로그램 띄우기, 표준 입력으로 JSON-RPC 요청 쓰기, 서버가 tools/call 로 함수 실행, 표준 출력으로 결과 읽기의 네 걸음을 이은 흐름도

stdio 의 네 걸음입니다. 클라이언트가 python out_ch05_server.py 처럼 서버 프로그램을 켜고(1), 서버의 표준 입력에 요청을 한 줄 쓰면(2), 서버가 그 요청대로 함수를 실행하고(3), 결과를 표준 출력에 써서 클라이언트가 읽습니다(4). 두 프로그램이 같은 컴퓨터 안에서 글을 주고받을 뿐이라 네트워크 설정이 필요 없습니다.

짧은 예시

stdio 로 오가는 글은 JSON-RPC 형식입니다. MCP 규격에 적힌 요청과 답의 생김새를 사전으로 만들어 찍어 봅니다. 서버는 띄우지 않습니다.

Python
import json

# 1) 도구 목록을 달라는 요청. id 는 요청 번호로, 답에도 같은 번호가 붙는다
list_request = {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}

# 2) 도구 하나를 불러 달라는 요청. params 에 도구 이름과 인자를 담는다
call_request = {
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {"name": "find_products", "arguments": {"region": "부산"}},
}

# 3) 서버의 답. 같은 id 와 함께 결과(content)를 글 조각의 목록으로 돌려준다
call_answer = {
    "jsonrpc": "2.0",
    "id": 2,
    "result": {"content": [{"type": "text", "text": "P03 부산 야경과 시장 1박 2일 | 18만 원"}], "isError": False},
}

for name, msg in [("목록 요청", list_request), ("호출 요청", call_request), ("호출 답", call_answer)]:
    # ensure_ascii=False: 한글을 \uXXXX 로 바꾸지 않고 그대로 적는다
    print(name, json.dumps(msg, ensure_ascii=False))
출력
목록 요청 {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
호출 요청 {"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "find_products", "arguments": {"region": "부산"}}}
호출 답 {"jsonrpc": "2.0", "id": 2, "result": {"content": [{"type": "text", "text": "P03 부산 야경과 시장 1박 2일 | 18만 원"}], "isError": false}}

요청에는 method(무엇을 할지)가, 답에는 result(결과)가 있고, 둘은 같은 id 로 짝을 맞춥니다. ToolMessage 의 tool_call_id 와 같은 생각입니다. 결과는 글 조각(type: "text")의 목록으로 오므로, 실습 4에서 LangChain 이 받아 온 결과도 목록 모양입니다. 이 글을 우리가 직접 쓸 일은 없습니다. 클라이언트와 서버의 SDK 가 대신 쓰고 읽습니다.

코드가 의미하는 것

이 코드는 관리실 직원과 업체가 주고받는 쪽지 세 장을 펼쳐 보는 것입니다. "할 수 있는 일 목록을 주세요", "이 일을 해 주세요", "했습니다"의 세 장입니다.

코드하는 일챗봇에서 뜻하는 것
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}도구 목록을 달라는 요청을 만든다클라이언트가 도구 목록을 받아 올 때 서버에 보내는 쪽지의 생김새입니다
"method": "tools/call" 과 "params": {...}도구 이름과 인자를 담은 호출 요청을 만든다모델의 tool_calls 한 줄이 MCP 서버로 갈 때의 꼴입니다. 인자 칸의 이름이 args 대신 arguments 입니다
"result": {"content": [...], "isError": False}같은 id 를 단 답을 만든다서버가 도구를 실행한 결과입니다. 결과가 글 조각의 목록이라는 점이 실습 4의 text_of() 로 이어집니다
json.dumps(msg, ensure_ascii=False)사전을 한 줄 JSON 글로 찍는다stdio 로 오가는 것이 이런 한 줄 글입니다

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

  • id 2 가 요청과 답을 잇습니다. 호출 요청과 호출 답이 모두 "id": 2 입니다. tool_call_id 와 같은 번호표가 MCP 에도 있습니다.
  • 파이썬의 `False` 가 JSON 의 `false` 가 됐습니다. 출력의 마지막 줄을 보면 사전의 False 가 소문자 false 로 적혔습니다. 오가는 것이 파이썬 객체가 아니라 JSON 글이므로, 다른 언어로 짠 클라이언트와 서버도 같은 규격으로 이어집니다.

주의할 점

  • stdio 서버는 표준 출력을 MCP 의 글을 주고받는 데 씁니다. 서버 코드에서 print() 로 아무 글이나 찍으면 클라이언트가 그 글을 요청의 답으로 잘못 읽어 연결이 깨질 수 있습니다. 서버에서 무엇을 찍어 보고 싶으면 표준 오류(stderr)로 찍습니다.
  • 클라이언트 하나는 서버 하나만 맡습니다. 서버가 둘이면 클라이언트도 둘입니다. MultiServerMCPClient 는 설정에 적힌 서버마다 클라이언트를 만들어 줍니다.
← 이전 절5장 · MCP 란 무엇인가다음 절 →5장 · FastMCP 로 서버 만들고 LangChain 에 붙이기