LLM 엔지니어링10장 · MCP 와 Prompt·Context Engineering15 / 22← 이전목차다음 →MetaContents

10장. MCP 와 Prompt·Context Engineering

실습 2 — 클라이언트로 도구와 리소스 쓰기

실습 1의 서버에 접속해 도구를 부르고, 리소스를 읽고, 일부러 틀린 값을 넣어 오류가 어떻게 오는지 봅니다.

1단계. 도구 부르기와 리소스 읽기

Python
import asyncio, json, os, sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

PARAMS = StdioServerParameters(command=sys.executable, args=["out_stock_server.py"])     # 실습 1에서 만든 서버

async def main():
    async with stdio_client(PARAMS, errlog=open(os.devnull, "w")) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            r = await session.call_tool("get_stock", {"code": "T-201"})        # tools/call
            print("오류인가:", r.is_error, "· 내용 조각 수:", len(r.content))
            print(r.content[0].text)
            res = await session.read_resource("stock://warehouses")             # resources/read
            print("창고:", res.contents[0].text)

asyncio.run(main())
출력
오류인가: False · 내용 조각 수: 1
{
  "품목": "검정 토너(SN-PRT 용)",
  "창고별": [
    {
      "창고": "본사 창고",
      "수량": 3,
      "최소수량": 5
    },
    {
      "창고": "물류 센터",
      "수량": 25,
      "최소수량": 10
    }
  ]
}
창고: 본사 창고, 물류 센터

2단계. 틀린 값을 넣으면

Python
async def errors():
    async with stdio_client(PARAMS, errlog=open(os.devnull, "w")) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            for name, args in [("get_stock", {"code": "X-999"}),        # 없는 코드: 도구가 오류 결과를 돌려준다
                               ("get_stock", {}),                       # 필수 인자 빠짐
                               ("no_such_tool", {"code": "P-101"})]:    # 없는 도구
                try:
                    r = await session.call_tool(name, args)
                    print(f"{name} {args} → is_error={r.is_error} · {r.content[0].text[:60]!r}")
                except Exception as e:
                    print(f"{name} {args} → 예외 {type(e).__name__}: {str(e)[:60]}")

asyncio.run(errors())
출력
get_stock {'code': 'X-999'} → is_error=False · '{\n  "오류": "X-999 라는 품목 코드는 없습니다."\n}'
get_stock {} → is_error=True · 'Error executing tool get_stock: 1 validation error for get_s'
no_such_tool {'code': 'P-101'} → is_error=True · 'Unknown tool: no_such_tool'

3단계. 결과를 프로그램에서 쓰기

Python
async def low_items():
    """모든 품목을 받아 창고별로 최소수량보다 적은 것을 고른다(도구 결과를 코드가 쓰는 예)"""
    async with stdio_client(PARAMS, errlog=open(os.devnull, "w")) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            items = json.loads((await session.call_tool("list_items", {})).content[0].text)["품목"]
            low = []
            for it in items:
                info = json.loads((await session.call_tool("get_stock", {"code": it["코드"]})).content[0].text)
                low += [(it["코드"], it["품목"], w["창고"], w["수량"]) for w in info["창고별"] if w["수량"] < w["최소수량"]]
            return len(items), low

n, low = asyncio.run(low_items())
print("품목", n, "개 가운데 모자란 것:")
for row in low:
    print(" ", row)
출력
품목 14 개 가운데 모자란 것:
  ('P-102', 'A3 복사용지(250매)', '본사 창고', 8)
  ('T-201', '검정 토너(SN-PRT 용)', '본사 창고', 3)
  ('T-202', '컬러 토너 세트', '본사 창고', 0)
  ('S-302', '형광펜 세트', '본사 창고', 12)
  ('E-403', 'HDMI 케이블(2m)', '본사 창고', 4)
  ('C-501', '종이컵(1,000개)', '본사 창고', 2)

이 실습이 뜻하는 것

이 실습은 창구에 가서 실제로 주문해 보는 일과 같습니다. 정상 주문을 넣고 가게의 창고 목록 게시판을 읽어 보고(1단계), 일부러 없는 상품 번호, 빈 주문서, 없는 메뉴로 주문해 어떤 답이 오는지 보고(2단계), 마지막으로 사람이 아니라 계산 프로그램이 창구에 차례로 물어 모자란 물건을 골라냅니다(3단계). 여기서 주문이 도구 호출(call_tool), 게시판이 리소스(read_resource), 계산 프로그램이 LLM 없이 MCP 를 쓰는 코드입니다.

단계출력뜻
1. 도구 부르기오류인가: False · 내용 조각 수: 1호출이 성공했고, 답이 한 덩어리(내용 조각 하나)로 왔습니다
1. 결과본사 창고 수량 3, 최소수량 5 / 물류 센터 25, 10검정 토너가 본사 창고에서는 최소 기준보다 2개 모자라고, 물류 센터에는 넉넉합니다
1. 리소스창고: 본사 창고, 물류 센터리소스는 부르는 기능이 아니라 읽기만 하는 자료입니다. 창고가 두 곳이라는 것을 알려 줍니다
2. 없는 코드is_error=False, "X-999 라는 품목 코드는 없습니다."호출 자체는 정상으로 처리되고, 도구가 "그런 품목이 없다"는 업무상의 답을 돌려줬습니다
2. 인자 빠짐is_error=True, "1 validation error"꼭 넣어야 할 코드가 빠져서 MCP 가 도구를 실행하기 전에 막았습니다
2. 없는 도구is_error=True, "Unknown tool"서버에 없는 도구 이름이라 거절되었습니다
3. 모자란 것품목 14 개 가운데 모자란 것: 아래 6줄모든 품목을 차례로 물어, 본사 창고에서 최소수량보다 적은 6가지(A3 용지, 검정 토너, 컬러 토너, 형광펜, HDMI 케이블, 종이컵)를 찾았습니다

한 줄로 줄이면, MCP 서버의 도구는 부르면 숫자가 담긴 답을 주고, 잘못 부르면 두 갈래(업무 오류는 결과로, 형식 오류는 is_error=True 로)로 알려 준다는 것을 확인한 실습입니다. 3단계처럼 LLM 없이 코드만으로도 같은 서버를 쓸 수 있습니다. 실습 3에서는 어느 도구를 부를지 고르는 일을 LLM 에게 맡깁니다.

코드 설명

  • get_stock("T-201") 은 오류가 아니고(is_error=False) 내용 조각 하나에 JSON 글이 담겨 왔습니다. 본사 창고 3개(최소 5개), 물류 센터 25개입니다. 리소스는 "본사 창고, 물류 센터"를 돌려줬습니다.
  • 없는 코드(X-999)는 도구가 {"오류": …} 결과를 돌려주므로 is_error=False 이고 까닭이 그대로 보입니다. 필수 인자가 빠지면 "Error executing tool get_stock: 1 validation error …", 없는 도구는 "Unknown tool"로 is_error=True 가 옵니다. 업무 오류는 결과로, 형식 오류는 MCP 가 막아 준다는 차이를 볼 수 있습니다.
  • 품목 14개를 차례로 불러, 본사 창고에서 최소수량보다 적은 6개(A3 용지, 검정 토너, 컬러 토너, 형광펜, HDMI 케이블, 종이컵)를 골랐습니다. 물류 센터는 모두 넉넉합니다.
  • 3단계는 LLM 없이 코드가 MCP 도구를 부르는 예입니다. MCP 서버는 LLM 전용이 아니라, 어떤 프로그램이든 같은 방법으로 쓸 수 있습니다.
← 이전 절10장 · 실습 1 — MCP 서버 만들기다음 절 →10장 · 실습 3 — MCP 도구를 LLM 에 잇기