데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      USB-C처럼 연결되는 AI 인터페이스, MCP Server를 만들어보자

      victor 25.06.04
      7,495 10 1
      DEVOTEE 요약
      MCP(Model Context Protocol)는 다양한 AI 모델과 시스템 간의 표준화된 연동을 가능하게 하는 프로토콜로, JSON-RPC 기반의 구조를 통해 간단하고 통일된 인터페이스를 제공합니다. 이를 활용하면 개발자는 LLM(대규모 언어 모델)로 IoT 디바이스 등 외부 시스템을 자연어로 제어할 수 있으며, MCP Server를 서비스 API의 Wrapper로 구현해 다양한 LLM 호스트와 연동할 수 있습니다. 본 문서에서는 Python으로 간단한 MCP Server와 Tool을 구현하고, 이를 Claude, Streamlit UI 등과 연동한 실습 과정을 소개하며 MCP의 가능성과 활용성을 보여줍니다.
      DEVOTEE 추천 블로그

      MCP(Model Context Protocol)는 현재 기준으로 사실상 AI 모델 연동의 표준처럼 사용되고 있는 것 같습니다.

      2024년 11월, Anthropic이 처음 공개한 이후 빠르게 주목을 받으며 성장세를 이어가고 있는데요.

      아직 발전 중인 단계이지만, JSON-RPC 기반의 심플한 구조와 다양한 SDK, 프레임워크 제공 덕분에 개발자들에게 큰 관심을 받고 있습니다.


      MCP는 공식 소개 문서에서도 언급되듯이, ‘AI의 USB-C’로 비유되곤 합니다.

      하나의 단일 인터페이스로 다양한 기기와 연결할 수 있는 USB-C처럼, MCP 역시 LLM이 다양한 외부 시스템(도구, 데이터 소스 등)과 일관된 방식으로 통신할 수 있게 해줍니다.

      Pasted image 20250530135550.png

      조금 다른 이야기이지만, 저는 이 개념이 기존의 운영체제(OS) 구조와도 맞닿아 있다고 생각합니다.

      우리가 사용하는 OS, 특히 리눅스는 디바이스 드라이버를 파일처럼 추상화하여 상호작용하죠.

      그리고 이 구조는 HAL(Hardware Abstraction Layer)이라는 공통 규약을 바탕으로 구성되어 있습니다.


      이 구조에서 착안하면, LLM과 MCP를 활용해 다양한 외부 시스템(디바이스, API, 내부 도구 등)을 규격화해 제어할 수 있다는 점이 중요합니다.

      예를 들어, IoT 디바이스의 경우 그동안 제조사마다 각기 다른 제어 인터페이스를 연동해야 했지만,

      MCP 기반으로 통일된 인터페이스를 제공하면 LLM을 통해 자연어로 손쉽게 제어가 가능합니다.

      제조사는 자신의 디바이스에 맞는 MCP Server만 제공하면 되는 구조인 것이죠.


      실제로 엔트로픽의 Claude는 ‘Computer Use’ 기능을 통해 브라우저를 조작하거나 파일을 열고 편집하는 작업까지 수행할 수 있고,

      OpenAI 역시 ‘Operator’ 개념을 통해 LLM이 외부 기능과 직접 연동되도록 하는 실험을 진행하고 있습니다.

      이런 흐름을 보면, 조만간 운영체제 자체를 LLM이 직접 제어하는, 완전히 새로운 형태의 OS가 등장할 수 있으리라 기대됩니다.


      앞으로 어떤 새로운 프로토콜이 등장할지는 알 수 없지만, 공통 인터페이스라는 구조적 장점만으로도 다양한 가능성을 열어줄 수 있다는 점은 분명해 보입니다.

      그런 점에서 MCP는 LLM 활용에 있어 하나의 강력한 시작점을 제시하며, 많은 개발자와 기업의 관심을 받고 있는 것 같습니다.


      이번 포스트에서는 Python 기반으로 MCP(Model Context Protocol)를 적용하는 기본적인 코드 구현 방법을 단계적으로 소개해보려 합니다.

      MCP Server 구현부터, 간단한 커맨드라인 스크립트를 통한 MCP Tool 호출,

      마지막으로 Streamlit을 활용해 자연어로 MCP Tool을 실행하는 Agent 생성까지 차근차근 따라가며 실습해보겠습니다.

      MCP Server 만들기

      MCP의 핵심은 호스트 애플리케이션이 여러 서버에 연결할 수 있는 클라이언트-서버 아키텍처를 따르는 것입니다.

      스크린샷 2025-05-30 오후 2.45.16.png

      • MCP 호스트: MCP를 통해 데이터에 액세스하려는 클로드 데스크톱, IDE 또는 AI 도구와 같은 프로그램

      • MCP 클라이언트: 서버와 1:1 연결을 유지하는 프로토콜 클라이언트

      • MCP 서버: 표준화된 모델 컨텍스트 프로토콜(

        로컬 데이터 소스)을 통해 각각 특정 기능을 노출하는 경량 프로그램: MCP 서버가 안전하게 액세스할 수 있는 내 컴퓨터의 파일, 데이터베이스 및 서비스

      • 원격 서비스: MCP 서버가 연결할 수 있는 인터넷을 통해 사용 가능한 외부 시스템(예: API를 통해)

      위 설명에서 클라이언트-서버 연동 구조라고 하여 MCP Server가 항상 어떤 원격지에 있는 것은 아닙니다.

      MCP Host, Client, Server가 모두 한 PC에서도 실행될 수 있고 Server만 원격지에 존재하는 경우도 있습니다.

      MCP Server를 로컬 PC에 설치해서 사용 가능한 도구들이 다양하게 존재합니다. (ex, Desktop Commander)


      먼저, MCP의 기반이 되는 MCP Server를 직접 만들어보겠습니다. Python 구현체인 FastMCP를 활용해 간단한 MCP Server를 구성해볼 예정인데요.

      이 포스트에서는 복잡한 내부 구조보다는 실행에 초점을 맞춰, 빠르게 테스트해볼 수 있는 최소한의 형태로 구현해보겠습니다.

      프로젝트 생성

      본 포스팅에서 python 패키지 매니저로는 uv를 사용하겠습니다.

      mkdir mcp_server
      cd mcp_server
      uv init --python 3.12

      식사 메뉴를 추천해주는 MCP Tool 입니다. FastMCP는 type hint와 docstring을 기반으로 자동으로 스키마를 생성하는데요,

      이 구조 덕분에 LLM Host가 Tool을 정확하게 이해하고 호출할 수 있습니다.

      따라서 함수의 파라미터에 명확한 타입 지정과 상세한 설명(description) 을 적어주는 것이 중요합니다.


      reco_food.py

      from mcp.server.fastmcp import FastMCP
      import random
      
      mcp = FastMCP("reco_food")
      menus = {
          "중식": ["짜장면", "마라탕"],
          "일식": ["스시", "우동"],
          "한식": ["비빔밥", "김치찌개"]
      }
      
      @mcp.tool()
      def recommend_food() -> str:
          """식사 메뉴를 추천하는 함수
      
          Returns:
              str: 오늘 메뉴
          """
          # 카테고리 무작위 선택
          category = random.choice(list(menus.keys()))
      
          # 해당 카테고리에서 음식 무작위 선택
          menu = random.choice(menus[category])
      
          return f"오늘의 메뉴는 ({category}): {menu}"
      
      
      if __name__ == "__main__":
          print("Starting MCP server...")
          mcp.run()

      이제 구현한 MCP Server를 실제로 테스트해보기 위해, Claude에 연결해보겠습니다.

      Claude Desktop을 이용해 LLM이 MCP Tool을 어떻게 호출하는지 확인해볼 수 있습니다.


      Claude -> 설정 -> 개발자 메뉴에 들어가면 MCP Tool을 등록하는 화면이 있습니다. '설정 편집'을 누르고 claude_desktop_config.json을 수정합니다.

      스크린샷 2025-05-30 오전 11.33.48.png

      해당 설정 파일에 들어갈 내용은 다음과 같습니다. command는 MCP Server를 실행할 명령입니다. args에는 실행에 필요한 여러 파라미터를 넣을 수 있습니다.

      {
        "mcpServers": {
          "reco_food": {
            "command": "/Users/victor/.local/bin/uv",
            "args": [
              "--directory",
              "/Users/victor/dev/mcp-example/mcp_server",
              "run",
              "reco_food_server.py"
            ]
          }
        }
      }

      설정을 마친 뒤에는 Claude Desktop 프로그램을 재실행합니다. 정상적으로 연동되었다면, 아래와 같이 Tool 목록에서 앞서 등록한 MCP Tool이 표시되는 것을 확인할 수 있습니다.

      스크린샷 2025-05-30 오전 11.43.40.png

      이제 "점심 메뉴 추천해줘"라고 입력해보겠습니다. 입력을 하면 다음과 같이 Tool 호출 메뉴가 보입니다.

      스크린샷 2025-05-30 오전 11.44.44.png

      Tool 호출 허용을 하면 아래와 같이 앞서 만들었던 MCP Tool이 호출되고 실행된 결과를 LLM이 해석하여 자연어로 표현해줍니다.

      스크린샷 2025-05-30 오전 11.45.06.png

      MCP Host 만들어 보기

      MCP Tool 연결

      Claude와 같은 MCP Host를 만들기 전에 Tool을 호출하는 과정부터 진행해보겠습니다.

      다음은 입력 파라미터로 mcp server 스크립트 경로를 받아 MCP Client를 동작시키는 코드입니다.

      import sys
      import asyncio
      
      from mcp import ClientSession, StdioServerParameters
      from mcp.client.stdio import stdio_client
      
      async def connect_to_server(server_script_path: str):
          """Connect to an MCP server and list available tools"""
          if not server_script_path.endswith('.py'):
              raise ValueError("Server script must be a .py file")
      
          server_params = StdioServerParameters(
              command="python",
              args=[server_script_path],
              env=None,
          )
      
          async with stdio_client(server_params) as (stdio, write):
              session = ClientSession(stdio, write)
              async with session:
                  await session.initialize()
                  response = await session.list_tools()
                  tools = response.tools
                  print("\n✅ Connected to server with tools:", [tool.name for tool in tools])
      
      
      if __name__ == "__main__":
          if len(sys.argv) < 2:
              print("Usage: python list_tools.py <path_to_server_script>")
              sys.exit(1)
      
          asyncio.run(connect_to_server(sys.argv[1]))

      다음과 같이 실행하면 입력한 경로의 mcp server 스크립트를 연결하여 Tool List를 출력하는 것을 확인할 수 있습니다.

      uv run list_tools.py ../mcp_server/reco_food_server.py

      실행결과

      [05/30/25 11:50:22] INFO     Processing request of type      server.py:551
                                   ListToolsRequest                             
      
      ✅ Connected to server with tools: ['recommend_food']

      LLM Agent 연동

      한 단계 더 나아가 이번엔 LLM Agent로 명령을 주어 Tool을 호출해보도록 하겠습니다.

      아래 코드는 "오늘 점심 메뉴 추천해줘"라는 질의가 고정되어 들어가는 python 스크립트 입니다.

      import asyncio
      from langchain_mcp_adapters.client import MultiServerMCPClient
      from langgraph.prebuilt import create_react_agent
      
      async def main():
          client = MultiServerMCPClient(
              {
                  "reco_food": {
                      "command": "uv",
                      "args": ["--directory", "/Users/victor/dev/mcp/mcp-example/mcp_server", "run", "reco_food_server.py"],  # 실제 경로로 대체하세요
                      "transport": "stdio",
                  }
              }
          )
          
          tools = await client.get_tools()
      
          # openai API key가 필요하며, openai 패키지가 설치되어 있어야 합니다
          agent = create_react_agent("openai:gpt-4.1", tools)
      
          math_response = await agent.ainvoke({"messages": "오늘 점심 메뉴 추천해줘"})
          print("Math Response:", math_response)
      
      # asyncio 루프 실행
      if __name__ == "__main__":
          asyncio.run(main())

      실행 결과

      실행 결과를 보면 Tool이 호출된 것을 알 수 있고 응답 결과 또한 의도한대로 잘 나온 것을 확인할 수 있습니다.

      Math Response: {'messages': [HumanMessage(content='오늘 점심 메뉴 추천해줘', additional_kwargs={}, response_metadata={}, id='a2aec9b0-dac9-4ec7-908a-504419fd23c6'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_xEOFbXG9yk1x5DNr3RgwCxgb', 'function': {'arguments': '{}', 'name': 'recommend_food'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 54, 'total_tokens': 65, '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-2025-04-14', 'system_fingerprint': 'fp_51e1070cf2', 'id': 'chatcmpl-BclCe00OQcs4kWBaV8ac5bWb3q5Fq', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--7f6b66d1-7fec-4d53-b589-59a05d023bb9-0', tool_calls=[{'name': 'recommend_food', 'args': {}, 'id': 'call_xEOFbXG9yk1x5DNr3RgwCxgb', 'type': 'tool_call'}], usage_metadata={'input_tokens': 54, 'output_tokens': 11, 'total_tokens': 65, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='오늘의 메뉴는 (중식): 짜장면', name='recommend_food', id='5aa1abc6-88f7-4f3b-8a23-bc0aa34f918b', tool_call_id='call_xEOFbXG9yk1x5DNr3RgwCxgb'), AIMessage(content='오늘의 점심 메뉴로 짜장면을 추천합니다! 맛있게 드세요.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 85, 'total_tokens': 106, '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-2025-04-14', 'system_fingerprint': 'fp_51e1070cf2', 'id': 'chatcmpl-BclChShCorugWoZ1etFmoDXp9WnsR', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--e36ba24f-d4b4-4f0b-8643-832e0af70bd9-0', usage_metadata={'input_tokens': 85, 'output_tokens': 21, 'total_tokens': 106, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}

      Web UI로 MCP Host 동작시키기

      이번엔 streamlit을 이용해 인터랙션이 가능한 채팅 형태의 Web UI 프로그램을 만들어보겠습니다.

      import streamlit as st
      import asyncio
      from langchain_mcp_adapters.client import MultiServerMCPClient
      from langgraph.prebuilt import create_react_agent
      from langchain_core.messages.ai import AIMessageChunk
      
      st.title("ChatGPT-like Clone with MCP (Async)")
      
      # MCP 클라이언트 설정
      client = MultiServerMCPClient(
          {
              "reco_food": {
                  "command": "uv",
                  "args": [
                      "--directory",
                      "/Users/victor/dev/mcp/mcp-example/mcp_server",
                      "run",
                      "reco_food_server.py"
                  ],
                  "transport": "stdio",
              },
              "phonebook": {
                  "command": "uv",
                  "args": [
                      "--directory",
                      "/Users/victor/dev/mcp/mcp-example/mcp_server",
                      "run",
                      "phonebook_server.py"
                  ],
                  "transport": "stdio",
              }
          }
      )
      
      # MCP 도구 비동기 로딩
      async def init_agent():
          tools = await client.get_tools()
          agent = create_react_agent(
              "openai:gpt-4o-mini",
              tools=tools,
              prompt="You are a helpful assistant."
          )
          return agent
      
      # 세션 상태 초기화
      if "messages" not in st.session_state:
          st.session_state.messages = []
      
      if "agent" not in st.session_state:
          st.session_state.agent = asyncio.run(init_agent())
      
      # LangGraph agent 호출 비동기 함수
      async def call_agent_async(agent, messages):
          return await agent.ainvoke({"messages": messages})
      
      # 이전 대화 표시
      for message in st.session_state.messages:
          with st.chat_message(message["role"]):
              st.markdown(message["content"])
      
      # 사용자 입력 처리
      if prompt := st.chat_input("What is up?"):
          st.session_state.messages.append({"role": "user", "content": prompt})
          with st.chat_message("user"):
              st.markdown(prompt)
      
          with st.chat_message("assistant"):
              response = asyncio.run(call_agent_async(st.session_state.agent, st.session_state.messages))
              new_messages = response["messages"]
              
              # 마지막 AI 메시지 (최종 사용자 응답용)
              last_ai_index = next(
                  (i for i in reversed(range(len(new_messages))) if new_messages[i].type == "ai" and new_messages[i].content),
                  None
              )
      
              if last_ai_index is not None:
                  last_ai_msg = new_messages[last_ai_index]
      
                  # 직전 tool call AIMessage 확인
                  tool_call_msg = None
                  for i in range(last_ai_index - 1, -1, -1):
                      msg = new_messages[i]
                      if msg.type == "ai" and msg.additional_kwargs.get("tool_calls"):
                          tool_call_msg = msg
                          break
                  
                  # tool_calls 출력
                  if tool_call_msg:
                      st.markdown("**[Tool 호출됨]**")
                      for call in tool_call_msg.additional_kwargs["tool_calls"]:
                          function_name = call["function"]["name"]
                          call_id = call["id"]
      
                          st.markdown(f"- `{function_name}` 함수 호출됨")
      
                          # tool 응답 출력
                          tool_result_msg = next(
                              (m for m in new_messages if m.type == "tool" and m.tool_call_id == call_id),
                              None
                          )
                          if tool_result_msg:
                              st.markdown(f"**[Tool 응답 결과]**\n- `{function_name}` 결과: {tool_result_msg.content}")
      
                  # 마지막 AI 응답 출력
                  st.markdown(last_ai_msg.content)
      
                  # 세션 상태에 저장
                  st.session_state.messages.append({
                      "role": "assistant",
                      "content": last_ai_msg.content
                  })

      실행 명령

      uv run streamlit run main.py

      스크린샷 2025-05-30 오후 1.24.51.png

      기존 서비스 기능을 MCP로 제공하는 방법

      기존에 서비스하던 기능을 Tool로 만들어 다양한 LLM Host와 연동하려는 경우 MCP Server를 Backend API의 Wrapper 형태로 구현해서 동작시킬 수 있습니다.


      Backend

      간단한 REST API를 만들어보겠습니다. CSV파일에 있는 연락처를 불러와 반환해주는 API입니다.

      from fastapi import FastAPI
      from fastapi.responses import JSONResponse
      import csv
      
      app = FastAPI()
      
      @app.get("/phonebook")
      def read_csv_data():
          data = []
          try:
              with open("phonebook.csv", newline='', encoding='utf-8') as csvfile:
                  reader = csv.DictReader(csvfile)
                  for row in reader:
                      data.append(row)
          except FileNotFoundError:
              return JSONResponse(status_code=404, content={"message": "CSV file not found"})
          
          return data

      해당 기능을 Tool로 제공하는 MCP Server는 다음과 같이 구현합니다.

      from mcp.server.fastmcp import FastMCP
      import requests
      
      mcp = FastMCP("phonebook")
      
      @mcp.tool()
      def fetch_phonebook():
          """phoneboo에서 연락처를 가져오는 함수
      
          Returns:
              json
          """
          url = "http://127.0.0.1:8000/phonebook"
          try:
              response = requests.get(url)
              response.raise_for_status()  # HTTP 오류 발생 시 예외 발생
              data = response.json()
              return data
          except requests.exceptions.RequestException as e:
              print(f"Error fetching data: {e}")
              return None
          
      if __name__ == "__main__":
          print("Starting MCP server...")
          mcp.run()

      Backend를 동작시키고 앞서 만든 Web UI에서 연락처를 묻는 질의를 하면 아래와 같이 응답하는 걸 확인할 수 있습니다.

      스크린샷 2025-05-30 오후 1.38.09.png

      위 동작은 MCP Server에서 외부 Service를 호출하고 그 결과를 MCP Host에 응답결과로 주어 나온 결과를 보여줍니다.

      Cluade Desktop, Cursor 등의 MCP Host들도 이것과 유사한 방식으로 구동됩니다.

      마치며

      이번 포스팅에서는 MCP를 활용해 LLM Agent와 어떻게 연동할 수 있는지, 간단한 예제 프로그램을 통해 빠르게 실습해보았습니다.

      MCP에는 Tool 선언 외에도 Resource, Prompt 등 다양한 구성 요소가 있지만, 이번 글에서는 핵심 흐름을 빠르게 이해하고 실행해보는 데 초점을 맞춰 필수적인 내용 위주로만 다뤘습니다.

      MCP는 아직 발전 초기 단계에 있지만, LLM과 외부 시스템을 연결하는 표준 인터페이스로서 매우 강력한 가능성을 보여주고 있습니다.

      이번 포스팅이 MCP의 개념을 이해하고, 실제로 간단한 MCP Server와 Tool을 구현해보는 데 도움이 되었기를 바랍니다.

      포스트에 소개했던 코드는 다음 github repository에 업로드하였으니 참고하시기 바랍니다.


      https://github.com/warnus/mcp-example

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      victor 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기