본문 바로가기
스터디/MCP

[MCP Study] 2. 다수의 MCP 서버와 클라이언트 통신하기

by 박사개구리 2025. 5. 21.
반응형

⛔️ 요즘 MCP가 엄청나게 화제라서 개인적으로 해당 내용을 스터디 하면서 정리한 자료입니다!

     혹시 잘못된 내용이나 오타, 개선할 사항이 있으시면 편하게 댓글 남겨주세요! 🙇‍♂️

⛳️ 목표!

  • sse 방식으로 날씨 서버를 추가해서 현재 날씨에 대한 질문에도 정확하게 답변하도록 해보자!

🍽️ 사전 준비 사항

  • 사전 준비 사항은 이전 글인 “1개의 MCP 서버와 클라이언트 통신하기”의 내용과 동일합니다.

🛠️ 실습 내용

  • 기존에 사용하던 stdio 방식의 MCP 서버에 sse 방식의 MCP 서버를 추가하여 서로 다른 방식으로 2개의 서버를 사용합니다.
  • 클라이언트에서 LLM이 사용자의 입력에 따라 서빙된 2개의 도구 중 적절한 도구를 스스로 선택해서 응답하는 과정을 살펴봅니다.

🔎 검색 도구만 사용했을 때의 한계점!

  • 이전 내용에서 LLM 에이전트가 검색을 하고 그 결과를 통해 응답을 하는 내용을 살펴봤습니다!
  • 한번 이 검색과 응답 과정을 통해 현재 서울의 날씨를 잘 알려주는지 확인해보겠습니다! 🌤️

  • 결과
---------- tool response step: 0------------
content='지금 서울 날씨 알려줘' additional_kwargs={} response_metadata={} id='98dba993-68a6-4e7c-96c6-a2852dccf084'
---------- tool response step: 1------------
content='' additional_kwargs={'tool_calls': [{'id': 'call_PAXddpLxJfgzypXe1onoJcwo', 'function': {'arguments': '{"question":"현재 서울의 날씨"}', 'name': 'search_and_answer'}, 'type': 'function'}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 57, 'total_tokens': 76, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpT0Qyr1YDor61FiuVZIxfFo76ZW', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-16c63220-a832-4e0f-88f9-ae890742d52b-0' tool_calls=[{'name': 'search_and_answer', 'args': {'question': '현재 서울의 날씨'}, 'id': 'call_PAXddpLxJfgzypXe1onoJcwo', 'type': 'tool_call'}] usage_metadata={'input_tokens': 57, 'output_tokens': 19, 'total_tokens': 76, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
---------- tool response step: 2------------
content='현재 날씨 - 기상청 날씨누리\n메인메뉴 바로가기\n본문 바로가기\n기상청 날씨누리\n홈 바로가기\nKOR\nENG\nCHN\nJPN\n날씨 \n기상특보\n특보현황\n통보문\n영향예보\n안개정보\n날씨상황판\n예보\n단기예보\n중기예보\n북한날씨\n기상방송\n바다 \n해상특보\n바다예보\n일일예보\n중기예보\n바다예측\n바다실황·예측\n해구별예측\n이안류 예측정보\n너울 예측정보\n해양일기도\n수치파랑\n폭풍해일\n해양순환\n해양기상정보포털\n영상·일기도 \n날씨지도\n종합영상\n초단기예측\n강수\n바람\n레이더\n위성\n천리안위성 2A호\nASCAT\n사례영상\n낙뢰\n일기도\n분석일기도\n예상일기도\n전문가용일기도\n바다수온\n태풍 \n상세정보\n통보문\n위험시점정보\n모델예측\n태풍감시\n과거태풍\n태풍발생통계\n기본지식\n태풍이란\n태풍의 이름\n태풍의 분류\n기후 \n기후감시예측정보\n기후예측 통보문\n1개월전망\n3개월전망\n3개월전망 해설서\n6개월전망(시범)\n계절 기후전망\n연 기후전망\n엘니뇨라니냐 전망\nWMO 장기예보선도센터\n가뭄·수문기상\n기상가뭄 1개월전망\n기상가뭄 3개월전망\n기상가뭄 6개월전망\n가뭄 예·경보(관계부처 합동)\n수문기상 가뭄정보 시스템\n기후통계\n기후변화 \n기후변화 상황지도\n종합기후변화감시정보\n기후정보포털\n지진·화산 \n실시간 지진감시\n최근발표\n지진조회\n국내\n국외\n과거지진\n지진통계\n주요지진\n역사지진\n지진연보\n지진해일\n지진해일 사례\n화산\n과거 화산 사례\n화산재해 유형\n지진·화산 통보 기준\n행동요령/진도등급별현상\n온라인 지진 과학관\n테마날씨 \n생활기상정보\n지역별 종합지수\n생활기상지수\n지수안내\n산악날씨\n공항예보\n해수욕장예보\n계절관측\n봄꽃개화현황\n유명산 단풍현황\n세계날씨\n황사 \n황사영상\n지상일기도\n위성영상\n황사모델예측\n황사관측현황\n관측값\n관측그래프\n황사관측일수\n관측 \n육상\n도시별관측\n지역별상세관측(기존)\n지역별상세관측(신규)\n과거관측\n바다\n해양기상부이\n등표\n파고부이\n아시아연안관측실황\n선박관측\n소식·지식 \n소식\n지식\n날씨누리 도움말\n기상특보 발표기준\n국민행동요령\n예보발표안내\n예보용어해설\n시간당 강수량 체감영상\n실시간 TOP5\n단기예보\n강수\n도시별관측\n레이더\n일별자료\n기상플러스\n육상날씨 \n해상날씨 \n날씨영상 \n태풍 \n지진•화산 \n생활기상정보 \n테마날씨 \n황사 \n행정\n부속사이트\n기상청소속·산하기관\n수도권기상청\n부산지방기상청\n광주지방기상청\n강원지방기상청\n대전지방기상청\n대구지방기상청\n제주지방기상청\n청주기상지청\n전주기상지청\n수치모델링센터\n기상기후인재개발원\n국가기상위성센터\n항공기상청\n국립기상과학원\n기상레이더센터\n국가기상슈퍼컴퓨터센터\n한국기상산업기술원\nAPEC기후센터\n차세대수치예보모델개발사업단\n주요행정기관\n대한민국 대통령실\n국무총리실\n감사원\n국가정보원\n방송통신위원회\n국가인권위원회\n공정거래위원회\n금융위원회\n국민권익위원회\n국회사무처\n19부\n기획재정부\n과학기술정보통신부\n교육부\n외교부\n통일부\n법무부\n국방부\n행정안전부\n국가보훈부\n문화체육관광부\n농림축산식품부\n산업통상자원부\n보건복지부\n환경부\n고용노동부\n여성가족부\n국토교통부\n해양수산부\n중소벤처기업부\n3처\n인사혁신처\n법제처\n식품의약품안전처\n20청\n국세청\n관세청\n조달청\n통계청\n재외동포청\n검찰청\n병무청\n방위사업청\n경찰청\n소방청\n국가유산청\n농촌진흥청\n산림청\n특허청\n질병관리청\n기상청\n행정중심복합도시건설청\n새만금개발청\n해양경찰청\n우주항공청\n방재유관기관\n서울종합방재센터\n국립재난안전연구원\n한강홍수통제소\n중앙재난안전대책본부\n대기오염도실시간공개\n국가수자원관리종합정보시스템\nKBS재난포털\n외국기상청\n미국\n일본\n중국\n영국\n프랑스\n세계기상기구WMO\n기상청 날씨누리\n사용자 설정\n데스크탑용 메뉴\n모바일용 메뉴\n현재 날씨\n홈 바로가기\n날씨\n날씨\n바다\n영상·일기도\n태풍\n기후\n기후변화\n지진·화산\n테마날씨\n황사\n관측\n소식·지식\n기상특보\n예보\n기상방송\n공유하기\n페이스북\n트위터\n카카오\n주소 복사\n현재날씨\n옵션보기\nOff\n구분\n종합\n기온\n이슬점\n상대습도\n일강수량\n신적설\n적설\n풍향\n풍속\n전운량\n해면기압\n선택\n지역\n전국\n서울·인천·경기도\n부산·울산·대구·경상도\n광주·전라도\n대전·충청도\n강원도\n제주도\n선택\n시간\n선택\n-3H\n-1H\n현재\n+1H\n+3H\n현재날씨 참고사항\n현재 날씨 조회시, 참고사항 안내\n- 실황표로 정리되어 있는 기상청 각 지상관측 지점의 시간별 관측 실황자료를 조회하실 수 있습니다.\n- 조회 시, 지점명을 클릭하시면 시간별 현황을 확인할 수 있습니다.\n- 해당 자료는 실시간 관측된 자료이며, 현지 사정에 의해 잘못된 값이 표출 될 수 있으므로, 증명자료로 사용 될 수 없습니다.\n- 기상증명자료는 기상청 전자민원(\nhttp://minwon.kma.go.kr\n)에서, 매분자료가 필요한 ... (너무 길어서 생략)' name='search_and_answer' id='907c01da-cc39-40e8-ab92-8c62e4a195a4' tool_call_id='call_PAXddpLxJfgzypXe1onoJcwo'
---------- tool response step: 3------------
content='현재 서울의 날씨 정보를 확인한 결과 자세한 실시간 데이터는 제공되지 않지만, 일반적으로 기상청의 최신 공식 정보를 참고하는 것이 가장 정확합니다. 오늘 서울은 맑거나 구름 조금, 온화한 기온이 예상됩니다. 만약 더 구체적인 최신 기상 상태가 필요하시면 기상청 날씨누리 홈페이지 또는 날씨 앱을 확인하시기를 추천드립니다.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 89, 'prompt_tokens': 6084, 'total_tokens': 6173, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 6016}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpT2ortDywBGj4g0F2txibuOJdoz', 'finish_reason': 'stop', 'logprobs': None} id='run-58e338d2-3b2f-4db5-b63d-fe4fd712fdb4-0' usage_metadata={'input_tokens': 6084, 'output_tokens': 89, 'total_tokens': 6173, 'input_token_details': {'audio': 0, 'cache_read': 6016}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
  • '지금 서울 날씨 알려줘'라는 요청을 했을 때 기상청 날씨누리 페이지까지는 이동했는데 텍스트의 내용이 너무 많고 관련 내용이 포함되어 있지 않아 정확하게 응답을 하지 못하고 아래와 같은 결과가 출력되었네요.. 😱
    • “현재 서울의 날씨 정보를 확인한 결과 자세한 실시간 데이터는 제공되지 않지만, 일반적으로 기상청의 최신 공식 정보를 참고하는 것이 가장 정확합니다. 오늘 서울은 맑거나 구름 조금, 온화한 기온이 예상됩니다. 만약 더 구체적인 최신 기상 상태가 필요하시면 기상청 날씨누리 홈페이지 또는 날씨 앱을 확인하시기를 추천드립니다.”
  • 이번 내용에서는 현재 날씨 정보를 받아오는 새로운 MCP 도구를 추가하는데 이전의 stdio 방식이 아니라 sse 방식으로 도구를 추가해보겠습니다! 😎

 

⛔️ 잠깐!! 먼저 stdio와 sse의 차이점을 먼저 살펴보고 넘어가겠습니다.

  • transport는 LLM이 MCP 서버의 도구를 호출할 때, 어떻게 요청을 전달하고 결과를 받는지를 결정하는 방식입니다.
  • FastMCP는 위에서 언급한대로 기본적으로 두 가지 transport를 지원합니다:
    • stdio (Standard Input/Output)
    • sse (Server-Sent Events via HTTP)
  • 먼저 stdio 방식에 대해서 살펴보겠습니다.
    • MCP 서버를 하위 프로세스 (subprocess)로 실행합니다.
    • 표준 입력 (stdin - Json 형식)으로 요청으로 보내고 표준 출력 (stdout - Json 형식)으로 응답을 받습니다.
    • 해당 방식은 다음과 같은 장단점을 가집니다
      • 장점: 웹서버 없이 동작하여 단순하고 빠름
      • 단점: 복잡한 응답 구조 처리가 어렵고 병렬성, 스트리밍이 제한적
    • CLI (Command Line Interface)환경이나 로컬 환경에서 간단하게 테스트할때 사용하면 좋습니다.
  • 다음으로 sse 방식에 대해 살펴보겠습니다.
    • MCP 서버가 내부적으로 FastAPI 웹 서버를 실행하고 클라이언트는 HTTP로 연결해 응답을 받습니다.
    • 해당 방식은 다음과 같은 장단점을 가집니다
      • 장점: 스트리밍/병렬 가능
      • 단점: 구조가 복잡하며 배포 필요
    • 서버-클라이언트 스트리밍이 필요한 경우, 복잡한 응답 구조 처리를 수행하는 경우 사용하면 좋습니다.
  • GPT를 통해 두 방식을 비교 요약한 결과가 다음과 같습니다.항목 stdio (Standard Input/Output) sse (Server-Sent Events)
전송 방식 stdin/stdout (subprocess 통신) HTTP + SSE (FastAPI 웹 서버)
서버 실행 방식 CLI 기반 단일 실행 웹 서버로 동작
실시간 스트리밍 ❌ 불가능 ✅ 가능
적합한 용도 테스트, 내부 subprocess 연동 사용자-facing 서비스, 대화 응답
LLM 통합 방식 subprocess pipe (ex. LangChain tools) HTTP API 기반 연결 (ex. OpenAI function + web UI)
배포 복잡도 낮음 높음

 

🌤️ 날씨 서버 코드 작성

  • 아래와 같이 수학 연산을 위한 서버의 코드를 작성합니다. (server_weather.py)
from mcp.server.fastmcp import FastMCP
import aiohttp

mcp = FastMCP(
    "Weather",  # MCP 서버의 이름 
    instructions="너는 주어진 지역에 대한 날씨 정보를 알려주는 인공지능 에이전트야.",  # 이 도구를 어떻게 사용할지 LLM에게 설명하는 글
    host="0.0.0.0",  # 호스트 주소
    port=9000,  # 포트 번호
)

@mcp.tool()
async def get_weather(location: str) -> str:
    """wttr.in을 사용하여 간단하게 텍스트로 날씨 정보를 받는 코드"""
    url = f"https://wttr.in/{location}?format=3"
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=5) as resp:
                return await resp.text()
    except Exception as e:
        return f"에러 발생! 날씨 정보를 찾을 수 없습니다, 에러: {e}"

if __name__ == "__main__":
    mcp.run(transport="sse")
  • 이 코드도 부분적으로 자세히 살펴보겠습니다.

 

1. 패키지 불러오기 및 MCP 서버 정의

from mcp.server.fastmcp import FastMCP
import aiohttp

mcp = FastMCP(
    "Weather",  # MCP 서버의 이름 
    instructions="너는 주어진 지역에 대한 날씨 정보를 알려주는 인공지능 에이전트야.",  # 이 도구를 어떻게 사용할지 LLM에게 설명하는 글
    host="0.0.0.0",  # 호스트 주소
    port=9000,  # 포트 번호
)
  • 여기서는 FastMCP에 몇가지 입력을 추가했습니다.
    • instructions: LLM이 이 도구를 어떻게 사용할지 설명하는 글
    • host: 호스트 주소
    • port: 포트 번호
  • 이 서버는 FastAPI를 통해 웹 서빙을 하기 때문에 호스트 주소와 포트 번호가 필요합니다.

 

2.  MCP 도구 설정

@mcp.tool()
async def get_weather(location: str) -> str:
    """wttr.in을 사용하여 간단하게 텍스트로 날씨 정보를 받는 코드"""
    url = f"https://wttr.in/{location}?format=3"
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=5) as resp:
                return await resp.text()
    except Exception as e:
        return f"에러 발생! 날씨 정보를 찾을 수 없습니다, 에러: {e}"
  • 이제는 날씨 정보를 받는 도구를 설정해보겠습니다.
  • 먼저 @mcp.tool()를 통해 mcp 도구로 설정합니다.
  • 여기서는 wttr.in을 통해서 위치에 대한 간단한 텍스트 정보를 받아옵니다!
    • wttr.in의 location에 “서울”을 입력하고 인터넷 주소 창에 입력하면 다음과 같은 결과를 얻을 수 있습니다 → 서울: ⛅️ +19°C (format의 종류를 바꾸면 결과의 형태도 달라집니다)
  • wttr.in으로 얻은 응답의 텍스트를 반환하고 만약 이 과정에서 에러가 발생하는 경우 날씨 정보를 찾을 수 없다는 내용과 에러의 내용을 반환합니다.

 

3. MCP 서버 실행 (sse)

if __name__ == "__main__":
    mcp.run(transport="sse")
  • 마지막으로 mcp.run을 통해 MCP 서버를 실행합니다.
  • 단, transport를 ‘sse’로 설정해서 sse 방식으로 서빙을 수행합니다.
  • 여기까지 코드 작성을 완료했으면 이를 server_weather.py라는 이름으로 저장하고 반드시! 터미널에서 python server_weather.py 명령을 통해 코드를 실행합니다.
    • 이를 수행하면 아래와 같이 9000번 포트로 서버가 뜬 것을 확인할 수 있습니다.
INFO:     Started server process [63713]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
  • sse 방식은 fastapi 기반의 웹 서버를 통해 MCP 서버를 띄우는 방식이므로 꼭 이렇게 코드를 실행해서 서버를 띄워놓아야합니다!

 

🕵️ 클라이언트 코드

  • 다음과 같이 클라이언트 코드를 작성합니다.
import asyncio
from dotenv import load_dotenv
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

# Load environment variables from .env
load_dotenv()

model = ChatOpenAI(model="gpt-4.1-nano")

async def main():
    async with MultiServerMCPClient(
        {
            "search": {
                "command": "python",
                "args": ["./server_search.py"],
                "transport": "stdio",
            },
            "weather": {
                "url": "http://localhost:9000/sse",
                "transport": "sse",
            }
        }
    ) as client:
        agent = create_react_agent(model, client.get_tools())

        # 도구 사용 예시
        tool_response = await agent.ainvoke({"messages": "지금 서울 날씨 알려줘"})

        for step, item in enumerate(tool_response['messages']):
            print(f'---------- tool response step: {step}------------')
            print(item)

        print(f"\nFinal response: {tool_response['messages'][-1].content}")
            
# 비동기적으로 main 함수 실행
if __name__ == "__main__":
    asyncio.run(main())
  • 여기서도 코드들의 내용을 상세하게 살펴보겠습니다.

 

1. 패키지 불러오기

import asyncio
from dotenv import load_dotenv
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
  • 먼저 필요한 패키지들을 불러옵니다.
    • asyncio: 비동기 처리를 위한 패키지
    • load_dotenv: .env 파일에서 환경 변수(예: OpenAI API Key 등)를 불러오기 위한 패키지
    • ChatOpenAI: LLM을 불러오기 위한 패키지
    • MultiServerMCPClient: 여러 MCP 서버를 동시에 연결하고 관리
    • create_react_agent: LangGraph에서 제공하는 ReAct 기반 LLM 에이전트를 생성

 

2. 환경 변수 불러오기 및 LLM 모델 설정

# .env에서 환경 변수 불러오기 
load_dotenv()

# LLM 모델 설정 
model = ChatOpenAI(model="gpt-4.1-nano")
  • 먼저 load_dotenv를 통해 현재 경로의 .env 파일의 환경 변수들을 불러옵니다.
    • 이때 .env 파일이 다른 경로에 있다면 dotenv_path을 입력에 추가하고 환경 파일의 경로를 입력합니다.
  • 그리고 LLM 모델은 gpt 4.1 nano 모델을 사용하겠습니다.

 

3. main 함수를 비동기로 수행

async def main():
  • 다음으로 main 함수는 비동기로 수행되도록 합니다. MCP에서 외부 도구를 subprocess로 실행하거나, SSE 서버에 접속할 때 비동기 통신이 필수적입니다.

 

4. MCP 클라이언트에 MCP 서버 연결

    async with MultiServerMCPClient(
        {
            "search": {
                "command": "python",
                "args": ["./server_search.py"],
                "transport": "stdio",
            },
            "weather": {
                "url": "http://localhost:9000/sse",
                "transport": "sse",
            }
        }
    ) as client:
  • 이제 MCP 클라이언트를 2개의 tool 서버에 연결합니다.
    • MultiServerMCPClient는 여러 MCP 서버에 동시에 연결하고 LLM이 필요한 도구만 호출할 수 있도록 합니다.
    • search
      • args에는 서버 파일의 경로를 입력합니다. (server_search.py) → 이 파이썬 스크립트를 하위 프로세스로 실행합니다.
      • transport는 stdio로 설정하여 stdin/stdout 기반으로 요청/응답을 수행합니다.
    • weather
      • url에는 현재 날씨 서버의 주소를 입력합니다. 현재는 로컬 호스트의 포트 9000으로 설정되어있으므로 http://localhost:9000/sse로 설정합니다.
      • transport는 sse로 설정하여 HTTP 기반 실시간 수신이 가능하도록 합니다.

 

5. 에이전트 설정 (ReAct)

agent = create_react_agent(model, client.get_tools())
  • 에이전트를 설정합니다.
    • create_react_agent를 통해 LangGraph에서 제공하는 ReAct 기반 에이전트를 생성합니다.
    • client.get_tools(): MCP에 등록된 툴들을 LangChain의 tool 포맷으로 변환해 넘깁니다.
    • LLM이 툴 호출을 포함한 행동 시퀀스를 생성하도록 합니다 → 각 MCP 도구를 자동 호출해 답을 도출

 

6. 날씨 도구 추론 결과 확인

        # 도구 사용 예시
        tool_response = await agent.ainvoke({"messages": "지금 서울 날씨 알려줘"})

        for step, item in enumerate(tool_response['messages']):
            print(f'---------- tool response step: {step}------------')
            print(item)

        print(f"\nFinal response: {tool_response['messages'][-1].content}")
  • 이제 응답을 얻고 결과를 도출해보겠습니다!! 😆
    • 에이전트에게 “지금 서울 날씨 알려줘”라는 질문을 입력합니다.
    • 이 경우 LLM이 자동적으로 weather 도구를 선택하고 응답을 수행할 것입니다.
  • tool_response의 [’messages’]를 하나씩 출력한 결과를 통해 도구 호출 및 추론 과정을 살펴보겠습니다.
---------- tool response step: 0------------
content='지금 서울 날씨 알려줘' additional_kwargs={} response_metadata={} id='ce550ad8-5a97-47b3-ba43-3dbff2ed9c1b'
---------- tool response step: 1------------
content='' additional_kwargs={'tool_calls': [{'id': 'call_QdpQBvtGRyPD8DUVIS8zQ5q2', 'function': {'arguments': '{"location":"서울"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 91, 'total_tokens': 105, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpJD9Cuz95JbrB4UchJPY2ifFKVd', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-32502f7e-b57d-402e-aa7f-1b0249d2f80b-0' tool_calls=[{'name': 'get_weather', 'args': {'location': '서울'}, 'id': 'call_QdpQBvtGRyPD8DUVIS8zQ5q2', 'type': 'tool_call'}] usage_metadata={'input_tokens': 91, 'output_tokens': 14, 'total_tokens': 105, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
---------- tool response step: 2------------
content='서울: ⛅️  +20°C\n' name='get_weather' id='dd52fcc7-4b40-4a30-8276-13f460ed51bc' tool_call_id='call_QdpQBvtGRyPD8DUVIS8zQ5q2'
---------- tool response step: 3------------
content='서울의 현재 날씨는 흐림이며, 기온은 약 20도입니다.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 124, 'total_tokens': 144, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpJERGi0g5dOvIyh1k3LhZ8FN9Lg', 'finish_reason': 'stop', 'logprobs': None} id='run-079c47ec-3235-4c19-9281-c7e644c1b78c-0' usage_metadata={'input_tokens': 124, 'output_tokens': 20, 'total_tokens': 144, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}

 

  • 위 과정을 살펴보면 아래와 같은 과정을 통해 최종 응답을 도출하는 것을 살펴볼 수 있습니다.
    • 에이전트가 사용자의 입력을 받음 (입력: 지금 서울 날씨 알려줘)
    • 도구 호출, 판단 및 계획 (이 부분에서 get_weather 도구를 잘 선택해서 이용하는 것을 확인할 수 있습니다! 😆)
    • 도구를 사용하여 날씨 정보를 받아옴 (서울: ⛅️ +20°C)
    • LLM이 최종 추론 수행 (결과: 서울의 현재 날씨는 흐림이며, 기온은 약 20도입니다.)
  • 한번 실제 현재 (5월 19일 월요일 오후 4:30) 날씨와 기온을 온라인 날씨를 통해 확인해보겠습니다.

  • 이렇게 제대로 된 결과를 응답하는 것을 확인할 수 있습니다!! ⭐️

 

7.  검색 도구 추론 결과 확인

        # 도구 사용 예시
        tool_response = await agent.ainvoke({"messages": "오늘의 주요 뉴스 알려줘"})

        for step, item in enumerate(tool_response['messages']):
            print(f'---------- tool response step: {step}------------')
            print(item)

        print(f"\nFinal response: {tool_response['messages'][-1].content}")

 

  • 그럼 이전 내용과 같이 “오늘의 주요 뉴스 알려줘”라고 물어봤을 때 도구 호출 및 추론 과정을 살펴보겠습니다.
---------- tool response step: 0------------
content='오늘의 주요 뉴스 알려줘' additional_kwargs={} response_metadata={} id='8b8014dd-9358-4c85-814f-d9fe56da635f'
---------- tool response step: 1------------
content='' additional_kwargs={'tool_calls': [{'id': 'call_3P5Jzr1QZ5Kg5LvQ8D5K0Xqr', 'function': {'arguments': '{"question":"오늘의 주요 뉴스"}', 'name': 'search_and_answer'}, 'type': 'function'}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 90, 'total_tokens': 108, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpfuVe633yTWUYUBdv1AO37rYKTW', 'finish_reason': 'tool_calls', 'logprobs': None} id='run-0bc1d5b3-211f-495f-aaef-7ea51927b4d0-0' tool_calls=[{'name': 'search_and_answer', 'args': {'question': '오늘의 주요 뉴스'}, 'id': 'call_3P5Jzr1QZ5Kg5LvQ8D5K0Xqr', 'type': 'tool_call'}] usage_metadata={'input_tokens': 90, 'output_tokens': 18, 'total_tokens': 108, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
---------- tool response step: 2------------
content='주요뉴스 | 연합뉴스\n연합뉴스\n본문 바로가기\n메뉴 바로가기\n국가기간뉴스 통신사 연합뉴스\n국가기간뉴스 통신사 연합뉴스\n21대 대선\n최신뉴스\n정치\n북한\n경제\n마켓+\n산업\n사회\n전국\n세계\n문화\n건강\n연예\n스포츠\n오피니언\n비주얼\n제보\n재난포털\n마이페이지\n로그인\n검색창 열기\n메뉴 열기\n주요뉴스 | 테마별뉴스 | 연합뉴스\n테마별뉴스\n핫뉴스\n주요뉴스\n긴급뉴스\n단독뉴스\n많이 본 뉴스\n오래 머문 뉴스\n공감 많은 뉴스\n1일 전 보기\n달력 열기\n1일 후 보기\n커지는 SKT 해킹 파장…2년여 해킹 피해 사실상 \'오리무중\'\n05-19 16:48\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'음주 뺑소니\' 김호중 징역 2년 6개월 확정…상고 취하\n05-19 16:35\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n시흥서 흉기로 4명 사상, 50대 중국동포 용의자 도주…행방 묘연\n05-19 16:16\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n정부, 전공의 복귀 길 터준다…이달 말까지 추가모집\n05-19 16:11\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\nSKT "복제폰 접속망에서 방지…피해 발생시 100% 책임"\n05-19 16:11\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'미아동 마트 흉기난동\' 김성진 사이코패스로 확인…구속기소\n05-19 16:02\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n김문수 "주한미군 분담금 일정하게 올릴 수 있어…감축이 걱정"\n05-19 15:55\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n 경찰 "시흥 흉기사건 피해자 총 4명…2명은 사망"\n05-19 15:46\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n복지부, 내일부터 이달 말까지 사직 전공의 대상 추가모집\n05-19 15:19\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n이재명 "찢어진 가짜 빅텐트 아닌 진짜 빅텐트 민주당으로"\n05-19 15:38\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n금호타이어 광주공장 내부 투입 소방대원 철수…"붕괴 우려"\n05-19 15:37\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n민주, \'지귀연 판사 접대의혹\' 사진 공개…"당장 법복 벗겨야"\n05-19 15:33\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'폭싹 속았수다\'가 韓경제에 기회?…한은 "고용시장 질적 개선"\n05-19 14:47\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'미아동 마트 흉기난동\' 김성진 구속기소\n05-19 15:29\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n이어지는 SPC 산재 사고…이번엔 삼립 제빵공장서 또 사망 사고\n05-19 15:27\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'한미 관세 실무협의\' 20일 워싱턴 개최…범정부 대표단 방미\n05-19 15:25\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n김문수 "당이 잘못해 이준석 고생"…이준석 "단일화 관심 없어"\n05-19 13:03\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n李, 서울 유세 앞두고 "부동산, 공급 늘리는 방향으로 관리"\n05-19 12:32\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n대선 재외투표 내일부터 시작…118개국 223개 투표소서 진행\n05-19 15:22\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n시흥 편의점서 흉기 휘두른 50대 도주…인근서도 유사 사건\n05-19 15:08\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'서부지법 난동 선동 혐의\' 커뮤니티 운영진·이용자 불송치\n05-19 15:01\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n민주, \'지귀연 판사 접대의혹\' 사진 공개…"당장 법복 벗겨야"\n05-19 14:28\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\nSKT, 全가입자 유심정보 유출…3년전 첫해킹·IMEI도 유출 가능성\n05-19 13:19\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n홍콩 등 코로나 재확산 조짐에…정부 "국내 특이 동향 없다"\n05-19 14:21\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n3명 숨진 70대 역주행 사고…국과수 "브레이크 아닌 액셀 밟아"\n05-19 14:20\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n초등생 살해 교사 명재완 파면…공무원 연금 절반 수령 가능\n05-19 14:14\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n시흥 편의점서 여직원에 흉기 휘두른 50대 도주…경찰 추적 중\n05-19 13:28\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n정부 "오요안나 근로자 아니지만 괴롭힘 인정"…MBC "관련자 조치"\n05-19 13:59\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n민주·국민의힘, 첫 TV 토론 결과 두고 \'아전인수\' 비난\n05-19 12:28\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n\'82세\' 바이든 퇴임 4개월만에 전립선암 진단…"뼈까지 전이"\n05-19 06:18\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n특전사 참모장 "곽종근, \'문 부수고라도 들어가겠다\' 복창"\n05-19 12:41\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다.\n북마크\n노동부 "故 오요안나 근로자 아니지만 괴롭힘은 인정"\n05-19 10:43\n공유\n공유하기\n카카오톡\n페이스북\nX\n페이스북 메신저\n네이버 밴드\nURL 복사\n닫기\nURL이 복사되었습니다. ...(너무 길어서 생략)' name='search_and_answer' id='d9c91b9a-7d7b-466e-bd53-d08a4a112085' tool_call_id='call_3P5Jzr1QZ5Kg5LvQ8D5K0Xqr'
---------- tool response step: 3------------
content='오늘 주요 뉴스에는 SKT의 해킹 피해 우려와 관련된 소식, 김호중의 징역 확정 소식, 시흥에서 발생한 흉기 사건, 정부의 전공의 추가모집, SKT 유심정보 유출 가능성, 그리고 지방에서 발생한 사고와 사건들이 보도되었습니다. 또한, 정치권의 대선 관련 동향과 국제 소식도 다뤄지고 있습니다. 더 구체적인 뉴스나 특정 분야의 소식을 원하시면 알려주세요!' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 111, 'prompt_tokens': 9137, 'total_tokens': 9248, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 9088}}, 'model_name': 'gpt-4.1-nano-2025-04-14', 'system_fingerprint': 'fp_eede8f0d45', 'id': 'chatcmpl-BYpfxjrslDyMbUQS3p8NrhBpMdVOK', 'finish_reason': 'stop', 'logprobs': None} id='run-5e05e8bc-8d25-47e8-9d85-b0d0e567cb09-0' usage_metadata={'input_tokens': 9137, 'output_tokens': 111, 'total_tokens': 9248, 'input_token_details': {'audio': 0, 'cache_read': 9088}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
  • 이렇게 오늘의 뉴스 관련 질문을 하면 “search_and_answer” 도구를 사용하고 오늘의 뉴스 관련 내용을 검색해서 다음과 같이 응답한 것을 확인할 수 있습니다!!
    • 오늘 주요 뉴스에는 SKT의 해킹 피해 우려와 관련된 소식, 김호중의 징역 확정 소식, 시흥에서 발생한 흉기 사건, 정부의 전공의 추가모집, SKT 유심정보 유출 가능성, 그리고 지방에서 발생한 사고와 사건들이 보도되었습니다. 또한, 정치권의 대선 관련 동향과 국제 소식도 다뤄지고 있습니다. 더 구체적인 뉴스나 특정 분야의 소식을 원하시면 알려주세요!

 

8. main 함수 실행

# 비동기적으로 main 함수 실행
if __name__ == "__main__":
    asyncio.run(main())
  • 마지막으로 main 함수를 비동기적으로 실행합니다!

 

👋 결론

여기까지의 내용으로 sse 방식의 MCP 서버를 구축 및 서빙하고 클라이언트가 여러 MCP 서버의 도구들 중 질문에 알맞은 도구를 사용하여 응답을 수행하는 것을 확인했습니다!

다음 내용에는 직접 MCP 서버를 구축할 필요 없이 다른 사람들이 만들어서 공유한 MCP 서버를 사용하는 내용을 구현해보겠습니다!! 🧐

반응형