우리 서비스와 연결된 MCP Server 빠르게 구현해보기: MCP 해커톤 후기

9 hours ago 2

LLM, 그리고 AI Agent에 관한 관심이 뜨거운 요즈음입니다.

우아한형제들에서도 사내 지원으로 LLM ChatBot이나 AI Code Generate 도구를 지원하고, 구성원들이 자발적으로 사내 메신저에 AI TIL (Today I Learned) 채널을 만들어 정보를 교류하는 등 변화하는 흐름에 주목하고 있습니다.

이러한 분위기 속에 AI에 관심을 가진 개발자 몇몇이 모여 MCP(Model Context Protocol) 서버 해커톤을 진행했는데요. 이 행사에 참여한 경험과 MCP에 대해 배운 내용을 공유하고자 합니다.

본 글은 MCP를 들어만 봤거나 낯선 개발자가 읽어보기에 적절합니다. MCP에 대해 아무것도 모르던 개발자들이 데모 MCP 서버를 띄우는 여정이 담겨 있습니다.

때는 2025년 4월 17일, 사내 메신저 팀 채팅방에 하나의 스레드가 생성됩니다.


그림 1. 해커톤 계기가 된 스레드 | 출처 : 팀 메신저

10여 명 남짓한 팀 채팅방에서 최근의 AI에 관한 열기를 반영하듯 댓글이 201개나 달릴 정도로 반응이 뜨거웠습니다. 열화와 같은 성원 속에 논의는 빠르게 진행되어서, 1주일 후 4월 25일 금요일 저녁으로 해커톤 날짜가 결정되었습니다.

불타는 금요일 오후를 해커톤에 바칠 수 있는, 열정적인 퀵 커머스 서버개발자 6인과 기획자 2인이 모집되었습니다.

참여자들의 공통점은 아래와 같았는데요.

  1. AI Agent가 우리를 편리하게 해줄 것이라는 강력한 확신
  2. MCP 정말 처음 봄 (아무것도 모름)

아는 것은 없지만 새로운 것에 도전해 보고자 하는 열정과 패기만큼은 양손에 꼭 쥔 참가자들과 함께 시작합니다.

MCP란 무엇인가

사전 학습으로 MCP에 대해 이해해 오기로 했습니다.

MCP, Model Context Protocol은 2024년 11월 Claude로 유명한 Anthropic에서 발표한, LLM Agent가 다른 프로그램과 연동하기 위한 프로토콜입니다.

(Introducing the Model Context Protocol – Anthropic)

Model Context Protocol은 직역하자면 ‘모형 문맥 규약’인데, 직관적으로 와닿지 않습니다.
이런 프로토콜이 왜 필요했는지 알아보겠습니다.

지금은 AI가 단순히 프롬프트를 주고 받는 챗봇을 넘어, 인간의 지시를 받아 자율적으로 작업을 수행하는 Agent 단계에 접어들었다고 합니다.

그런데 AI가 사람의 명령을 자율적으로 수행하려면 로봇 팔 다리와 같은 작업을 할 수단이 필요하겠죠.


그림 2. 지시만 해봤지 해본 적은 없는 AI | 출처 : ChatGPT 생성 이미지

그래도 PC 환경에선 사람이 하는 일을 대체하기 위해 꼭 로봇 팔다리가 있어야 하는 건 아닙니다.
대신 오피스 프로그램, 터미널, 웹/앱 같은 프로그램을 AI가 활용할 수 있도록 개발해 준다면 충분할겁니다. 하지만 업계 표준이 없다면 세상의 프로그램들은 ChatGPT, HyperCLOVA X, Claude, Grok처럼 LLM마다 따로 개발 해야 할 텐데요.

MCP는 LLM이 프로그램을 이용할 수 있는 프로토콜을 정립하고자 하는 시도입니다.
프로그램 개발자는 MCP에 맞추어, 한 번 개발해 두면 새로운 LLM이 나올 때마다 대응할 필요 없고, LLM 제공자는 직접 작업하지 않고도 AI가 프로그램을 활용할 수 있도록 지원할 수 있습니다.

MCP 개발 가이드의 introduction에서는 USB 포트라는 좋은 비유로 이를 설명합니다.

MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools.

MCP는 애플리케이션이 LLM에 컨텍스트를 제공하는 방법을 표준화하는 개방형 프로토콜입니다. MCP를 AI 애플리케이션을 위한 USB-C 포트처럼 생각해 보세요. USB-C가 다양한 주변 장치와 액세서리에 장치를 연결하는 표준화된 방법을 제공하는 것처럼, MCP는 AI 모델을 다양한 데이터 소스와 도구에 연결하는 표준화된 방법을 제공합니다.
출처 : modelcontextprotocol.io

여기까지 읽고 나니 Model Context Protocol이라는 말이 이렇게 느껴졌습니다.


그림 3. 새롭게 해석해 본 MCP

처음에 직역했던 ‘모델 문맥 규약’보다는 이해가 잘 갑니다.

그러면 MCP의 의미를 정의했으니, MCP에서 말하는 MCP 서버와 MCP 클라이언트를 알아보겠습니다.

MCP 서버와 MCP 클라이언트

MCP에서는 서버와 클라이언트라는 두 가지 역할이 등장합니다.

  • MCP 클라이언트: LLM(AI 모델)이 실제로 실행되는 측, 즉 AI가 다양한 도구나 서비스를 사용할 수 있도록 ‘요청’을 보내는 쪽입니다.
    글이 작성된 시점에 대표적인 MCP Client들은 프로토콜을 만든 회사인 Claude Desktop을 비롯하여 Cursor, Cursor Editor가 있습니다.
    (OpenAI ChatGPT와 Google Gemini는 웹과 데스크톱 UI에서는 MCP 연결을 지원하지는 않으나, SDK에서는 지원하고 있습니다)
    OpenAI Platform / Google AI For Developer
  • MCP 서버: LLM이 활용할 수 있도록 실제 기능이나 데이터를 ‘제공’하는 쪽입니다. 즉, 우리가 만든 웹 서비스나 특정 기능을 외부 AI가 사용할 수 있게 MCP 프로토콜에 맞추어 서버를 구현합니다.
    공식적으로 MCP server를 지원하는 회사는 Notion, Figma 등이 있습니다.

사용자는 MCP Client를 먼저 다운로드하고, AI와 연결하고 싶은 MCP Server를 골라 등록하면 되는데요,
이렇게 연결된 MCP Client와 MCP Server의 흐름을 간략히 보면 아래와 같습니다.


그림 4. MCP client – server flow | 출처 : 필자

실제 MCP들이 어떻게 동작하는지 궁금하다면 Awesome MCP Servers에 접속해 본인이 아는 서비스를 선택하고, 데모 영상을 확인해 보세요.

여기까지 알고 나니 MCP Server를 구현하면 AI가 우리 서비스를 이용할 수 있겠다는 기대감이 듭니다. 두근거림을 안고 해커톤을 시작합니다.

MCP와 친해지기

4월25일 금요일, 저녁 8시에 해커톤 참가자가 모였습니다.


사진 5. PPT도 준비했답니다 | 출처 : 우아한형제들 커머스프로덕트실 해커톤

MCP가 무엇인지 다 같이 숙지한 후,
해커톤의 목표는 Cursor IDE에서 Prompt를 통해 배달의민족 장보기쇼핑과 관련된 기능을 사용할 수 있도록 해보는 것으로 결정되었습니다. (MCP Server 구현)

모두 MCP와 관련한 개발을 해 본 경험이 없어 사전 지식이 부족한 상태였기에 샘플 코드를 기준으로 발전해보는 방식으로 진행했습니다.

MCP 개발 가이드의 Quick Start에는 언어별 서버 구현 방식을 안내합니다.
For Server Developers – modelcontextprotocol.io

Python MCP 서버를 구현하는 샘플 코드입니다. 미국 기상청 API를 통해 특정 지역의 날씨를 조회할 수 있는 코드입니다. 지면 관계상 일부를 생략했습니다.

from typing import Any import httpx from mcp.server.fastmcp import FastMCP # 1. MCP 서버의 이름과 함께 서버 생성 mcp = FastMCP("weather") NWS_API_BASE = "https://api.weather.gov" USER_AGENT = "weather-app/1.0" # 2. mcp.tool이라는 데코레이터와 메서드 선언 @mcp.tool() async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ # ... 기상청 API 호출 및 포맷팅 return "\n---\n".join(forecasts) if __name__ == "__main__": # 3. 서버 시작 mcp.run(transport='stdio')

코드 6. Python MCP Server sample code | 출처 : https://modelcontextprotocol.io/quickstart/server

  1. 서버에 이름 변수를 담아 선언합니다.
  2. 데코레이터를 선언한 위·경도를 받아 지역별 날씨를 응답하는 메서드를 구현합니다.
  3. 서버 입출력을 stdio(시스템 표준 입출력)로 설정하고 시작합니다.

아주 간단한데, 이것만으로 Cursor가 프롬프트를 통해 기상청 날씨를 조회할 수 있다니 놀랍습니다.

이제 미국 기상청 대신 우리 서비스를 이용하도록 수정해야 합니다.
Cursor를 통해 해커톤을 진행하고 있던 만큼, generate code 기능을 적극적으로 활용했습니다.

먼저 테스트 환경에서 장보기쇼핑 서비스를 연결하기 위해서, 팀 내 API 문서화 도구인 Swagger를 활용했습니다. 프롬프트에 전달하기 좋도록 api-docs의 JSON data를 취득하여 프롬프트로 전달합니다.


그림 7. Swagger를 통해 간편하게 서버 API 정보를 취합 | 출처 : 필자


그림 8. API 스펙을 전달하고, tool 선언 메서드를 수정해달라고 요청 | 출처 : 필자

다음은 위 결과물로 생성된 Tool 코드입니다. (파라미터, URL 등은 변조)

@mcp.tool() async def get_products(product_ids: List[str], storeId: str = None, regionId: str = None) -> str: """상품 목록을 조회합니다. Args: product_ids: 상품 ID 목록 storeId: 가게 ID regionId: 지역 ID """ url = f"{API_BASE}/v1/product/list" request_data = { "product_ids" : product_ids, "storeId": storeId, "regionId": regionId } data, error = await make_api_request(url, "POST", request_data) if error: return f"상품 조회 실패: {error}" if not data or "data" not in data: return "상품 데이터를 가져올 수 없습니다." try: products = data["data"]["products"] if not products: return f"조회된 상품이 없습니다. (status: {data.get('status', 'UNKNOWN')}, message: {data.get('message', 'UNKNOWN')})" result = [] for product in products: formatted = f""" 상품ID: {product.get('productId', '정보 없음')} 상품명: {product.get('name', '정보 없음')} 판매가: {product.get('price', '정보 없음')} 판매상태: {product.get('status', '정보 없음')} """ result.append(formatted) return f"상품 목록 ({len(result)}개):\n\n" + "\n---\n".join(result) except Exception as e: print(f"상품 데이터 처리 중 예외 발생: {str(e)}") return "상품 데이터 처리 중 오류가 발생했습니다."

코드 9. Python MCP Server dummy code | 출처 : 필자

이제 상품을 조회할 수 있는 tool이 마련되었습니다.

해당 코드를 cursor Editor가 실행할 수 있도록, 내 MCP Server의 환경을 입력합니다. Python의 패키지 매니저인 uv 명령어로 실행하면 Cursor에 MCP UI에 서버가 인식되는 걸 볼 수 있습니다.


그림 10. Cursor에 MCP 환경설정에 커맨드 추가 | 출처 : 필자


그림 11. local stdio 기반 MCP 서버가 등록된 모습 | 출처 : 필자

이제 Cursor는 등록한 서버를 인식했고, 이 서버가 제공하는 tool 또한 알고 있습니다.
서버가 제공하는 tool은 위 코드에서 정의된 get_products입니다.
이 tool을 통해 상품을 조회해달라고 요청하겠습니다.


그림 12. 프롬프트를 통해 B마트 상품을 조회 | 출처 : 필자

cursor가 B마트 상품을 조회했습니다! MCP client는 LLM이 판단에 따라 MCP tool 사용 여부를 결정하기 때문에, 명확히 "MCP tool을 사용할 것"이라는 프롬프팅이 필요할 때도 있습니다.

간단한 MCP server를 기동해 봤으니 이제 더 고도화된 기능을 구현할 수 있습니다. 하지만 잠시 쉬어갑시다.

쉬는 시간 – MCP Tool에 관하여

쉬는 시간 동안 샘플 서버를 구동할 때 궁금했던 점에 대해 알아봅시다.

@mcp.tool이라는 데코레이터에 메서드를 작성하니, cursor가 인식하고 해당 메서드를 실행하는 과정이 있었습니다. MCP tool은 무엇이며, 어떤 과정을 통해 실행되는 걸까요?

먼저 코드에서 생성했던 라이브러리 코드인 FastMCP 내부 코드를 살펴보았습니다. (python SDK 버전 1.6.0)
부팅 과정에서 handler에 자기의 tools, resources, prompts, templates를 등록하는 코드를 볼 수 있습니다.

// FastMCP 라이브러리 코드 내부 def _setup_handlers(self) -> None: self._mcp_server.list_tools()(self.list_tools) self._mcp_server.call_tool()(self.call_tool) self._mcp_server.list_resources()(self.list_resources) self._mcp_server.read_resource()(self.read_resource) self._mcp_server.list_prompts()(self.list_prompts) self._mcp_server.get_prompt()(self.get_prompt) self._mcp_server.list_resource_templates()(self.list_resource_templates)

코드 13. FastMCP 부팅 시 핸들러 등록 코드 | 출처 : https://github.com/modelcontextprotocol/python-sdk

tool과 관련된 코드는 list_tools와 call_tool이 보입니다. 서버 핸들러에 등록하는 이 메서드들의 역할은 무엇일까요?

우선 tool은 LLM이 호출할 수 있는 "도구"입니다. (API라고 보아도 무방합니다) 서버는 자기가 수행할 수 있는 tool을 clients에 제공해야 하고, 이 기능이 바로 list_tools() 입니다.

처음에 MCP를 설명할 때 사용했던 개략도에 list_tool과 call_tool의 내용을 보강해 보겠습니다.


그림 14. list_tool 등록 및 유저 요청 시 call_tool 호출 흐름 | 출처 : 필자

  1. 서버 부팅 및 서버 연결: Python FastMCP의 구현에서는 서버가 부팅할 때 @mcp.tool 데코레이터가 정의된 메서드의 내용을 분석해 "도구"로 등록합니다. MCP client는 MCP server가 연결될 때 규약에 따라 MCP server의 list_tools를 호출하여 활용할 수 있는 도구가 어떤 것들이 있는지 파악합니다. (client 구현에 따라 캐싱 하거나 풀링을 할 수 있습니다)

  2. 사용자 요청: 사용자는 프롬프트를 입력합니다. "B마트 상품을 찾아줘"

  3. LLM 분석: LLM은 프롬프트를 분석하고 필요하다고 판단되면 MCP 도구를 호출합니다. 이때 장보기쇼핑 서비스의 상품 조회는 get_products를 통해 가능하다는 걸 알고 있으므로, tool의 description과 parameter 정보 등을 분석해 추가로 사용자에게 입력해야 할 내용을 안내합니다.

  4. call_tool 호출: 사용자 입력을 분석하여 call_tool을 호출합니다. tool의 데이터 객체는 파라미터 정보를 포함하기 때문에 어떤 값을 넣어 호출해야 할지 알 수 있습니다.

  5. MCP server 로직 실행 1: MCP server에서 tool에 매핑된 메서드를 실행합니다. get_products는 API 호출이었으므로, 장보기쇼핑 서버에 API 호출합니다.

  6. MCP server 로직 실행 2: 메서드 호출 응답을 받고, get_products 구현에 따라 후처리(포매팅) 로직을 수행합니다.

  7. call_tool 반환: get_products의 응답 형식의 문자열이었으므로, MCP Client는 call_tool의 결과물로 문자열을 받습니다.

  8. 처리결과 분석: LLM은 결과를 분석하여 사용자에게 응답할 내용을 정리하고 응답합니다.

달리 보였던 핸들러인 resources와 prompts를 짧게 언급하자면 resources는 PDF 파일과 같은 정적 자원 제공, prompts는 ‘이력서 서식’ 같은 형식화 작업을 처리할 수 있는 규격입니다

tools, prompts, resources, templates와 관련된 공식 문서의 내용은 Core architecture – Model Context Protocol에서 찾아볼 수 있습니다.

주제 정하기

쉬는 시간을 마치고, 다시 해커튼으로 돌아오겠습니다.

해커톤을 시작한 지 약 30분이 지난 지금, 우리 서버와 연결된 tool을 만드는 데 성공했고 더 어려운 도전을 해볼 때입니다. 무엇을 구현하고 싶은지 토의해 보기로 했습니다.

tool 하나하나가 LLM이 활용할 수 있는 도구임을 알았으니, 각 tool을 하나의 use case로 접근할 수 있습니다.

여러 주제가 오갔고 나왔던 주제 중 가장 호응이 좋았던 use case는 아래와 같았습니다.

Agent는 장보기쇼핑 서비스 MCP를 활용해 아래 질문에 대답할 수 있어야 한다.
"오늘 장 볼 상품 목록을 적어주면, 판매자별로 이 상품들의 정보를 목록화해 주고, 어떤 판매자한테 사는 게 가장 저렴한지 구해줘. 오늘 장 볼 상품은 양파, 우유, 돼지고기야."

아직 장보기쇼핑은 여러 판매자에게서 한 번에 배달받을 수 없기 때문에, 배달비를 아끼기 위해선 하나의 판매자에게서 장을 보는 것이 유리합니다. 그럴 때 여러 상품을 다 담은 결과를 AI가 판매자별로 비교해 줄 수 있다면 고객에게 이로울 것입니다.

주제가 정해졌으니 구현을 시작할 때입니다.

고도화된 tool 구현하기

위 주제를 구현하기 위해선 MCP tool 내에서 활용할 수 있는 API를 개발해야 합니다. 장보기쇼핑은 검색을 지원하는 커머스 시스템을 기반으로 하므로, 이용할 수 있는 검색 API가 존재하여 그대로 사용하기로 했습니다.

그렇다면 수집해야 할 파라미터는 "사용자가 장 볼 상품 목록", "사용자 정보", "사용자 위치"입니다.

그러나 사용자 정보와 관련된 것은 인증을 거쳐야 하고, 사용자 위치도 하드웨어 기준으로 파악해야 하는 문제가 있습니다. 기술적으로 불가능하지는 않지만 본 주제와 멀어지기 때문에 해커톤 당시엔 해당 정보들을 취득한 상황으로 가정하고 하드코딩하여 진행했습니다.

팀원들과, 그리고 커서와 토론해 가며 위 요구사항을 충족하기 위해 도구를 구현했습니다.

# 1. 파라미터로 사용자가 입력한 키워드를 받아 제일 저렴한 상품을 조회하는 tool 구현 @mcp.tool() def search_products_wildcard(keyword: str) -> dict: # 필자 주. FastMCP 에선 바로 이어지는 문자열 리터럴을 tool의 description으로 설정합니다. 이 문구를 통해 LLM이 해당 메서드에 대해 이해합니다. """Elasticsearch를 사용하여 특정 키워드가 상품명에 포함된 상품 목록을 검색합니다. Args: keyword: 검색할 키워드 (예: "딸기") Returns: API 응답 결과 (검색된 상품 목록) """ # query_body로 제일 저렴한 상품을 정렬할 수 있도록 만드는 코드... try: # POST 요청으로 Elasticsearch 쿼리 전송 response = requests.post(url, json=query_body, headers=headers) response.raise_for_status() # HTTP 에러 발생 시 예외 처리 return response.json() except requests.RequestException as e: return {"error": f"Elasticsearch request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"}

코드 15. MCP tool 구현 코드 | 출처 : 필자

검색어를 받으면 여러 판매자 중 가장 저렴한 상품을 찾는 도구가 완성되었습니다.

결과 시연하기

코드 구성을 마치고 MCP server를 새로 구동하여 Cursor에 인식시켰습니다.

먼저 구현한 조에서 떨리는 마음으로 프롬프트를 입력했고, 결과물에 박수가 터져 나왔습니다.
생생한 현장감을 위해 실제 현장에서 촬영했던 영상의 캡처본으로 전달합니다. 비전문 촬영 인력(필자)이 촬영하여 화질이 다소 조악한 점 양해 바랍니다.

그림 16. 제육볶음 재료를 가장 저렴하게 구매할 수 있는 판매자 탐색 요청| 출처 : 필자

그림 17. tool 결과 분석 1 | 출처 : 필자

그림 18. tool 결과 분석 2 | 출처 : 필자

결과물을 정리하고 시계를 보니 저녁 11시였습니다.
토요일 새벽 첫차를 타고 집에 갈 각오로 모인 용사들이었지만 AI가 보우하사 금요일 막차를 타고 귀가할 수 있었습니다. generate code와 질문과 답변의 도움을 받은, AI 생산성의 힘을 느끼기도 한 해커톤이었습니다.

자리를 정리하며

간단한 더미 코드 구현이었지만 MCP를 구현할 때 어떤 문제점들이 생길 수 있는지 엿볼 수 있는 기회였습니다.

  1. MCP 서버를 제공하게 되면 정형적인 GUI를 통해 받는 일반적인 웹/앱 트래픽과 달리, client가 제공된 tool을 통해 다양한 요청을 할 수 있습니다. 불규칙한 트래픽과 예측 불가한 파라미터에 대한 처리가 필요하다고 느꼈습니다. MCP 개발 가이드에서도 tool을 구현할 때 아래 규칙을 반드시(MUST) 지킬 것은 강조하고 있습니다.

    > Servers MUST: 서버 구현자는 아래 사항을 반드시 준수하십시오.

    • Validate all tool inputs 모든 입력을 검증하세요.
    • Implement proper access controls 적절한 접근 제어를 구현하세요.
    • Rate limit tool invocations tool 도구 호출에 대해, 최대 호출 제한 수를 구현하세요.
    • Sanitize tool outputs 도구 응답값을 형식에 맞도록 처리하세요.
  2. 다양한 사용자의 요구에 따른 적재적소의 tool을 구현하지 않는다면, 사용자 입장에선 ‘AI인데 왜 안 되지?’라고 느낄 수 있겠다고 느꼈습니다. 연쇄적으로 잘 이어질 수 있는 tool의 설계가 필요하다고요.

  3. 해커톤에서는 stdio 방식의 로컬 서버를 만들었지만, 코드의 버전 관리를 생각해 본다면 상용 단계에서는 원격 서버로 제공하는 게 유리하다는 생각이 들었습니다. 실제로 회사에서 공식적으로 지원하는 MCP 서버의 대부분은 stdio 방식이 아닌 Streamable HTTP 방식으로 HTTP 통신을 통해 제공하는 원격 서버로 구성되어 있습니다.
    Transports – Model Context Protocol

새로운 것을 배울 때, 함께 즐겁게 익힐 수 있는 동료들과 일하는 것이 얼마나 큰 행운일까요. 해커톤 당시에도 즐거웠고, 글로 정리하는 지금도 즐겁습니다. 이 즐거움이 조금이나마 읽으시는 분께 도움이 되었길 바랍니다.

성시형

배달의민족 커머스 부문에서 서버 개발을 하고 있는 성시형입니다. 서로 공유하고 교류하는 개발 문화를 좋아합니다.

Read Entire Article