OpenDart MCP 서버 구축하기 (sayou-stock 활용)
저자: 김찬우 · 작성일: 2026-01-07 00:00:00 · 분야: MCP
OpenDart MCP 서버 구축하기: Cloud Run에서 한국 주식 데이터 서빙하기
들어가며
한국 주식 시장의 재무제표나 배당 정보가 필요할 때마다 DART 사이트에서 직접 찾아본 경험이 있으신가요? 혹은 금융 데이터를 다루는 AI 에이전트를 만들고 싶지만, 데이터를 어떻게 연결해야 할지 막막하셨나요?
이 글에서는 MCP(Model Context Protocol) 서버를 활용해 OpenDart API를 LLM에 연결하는 방법을 소개합니다. 한번 구축하면 Gemini나 Claude 같은 AI에게 "삼성전자 배당 정보 알려줘"라고 물어보기만 하면 실시간으로 데이터를 가져올 수 있습니다.
OpenDart MCP 서버란?
주요 기능
OpenDart MCP 서버는 금융감독원의 공시 데이터를 LLM에서 자연어로 조회할 수 있게 만든 MCP 서버입니다. 이 서버를 사용하면 Gemini나 Claude 같은 AI에게 간단히 질문만 하면 실시간 금융 데이터를 가져올 수 있습니다.
제공하는 기능:
- 📊 재무제표 조회: 재무상태표, 손익계산서, 현금흐름표
- 💰 배당 정보: 주당 배당금, 배당수익률, 배당성향
- � 기업 정보: 종목 코드, 기업명, 업종 정보
- � 유연한 검색: 종목코드(005930), 기업명(삼성전자) 모두 지원
왜 이 서버를 만들었나요?
기존에 한국 주식 데이터를 AI와 연동하려면 많은 수작업이 필요했습니다:
기존 방식의 문제점:
- DART API를 직접 호출하고 XML 데이터를 파싱해야 함
- 기업 코드를 수동으로 찾아서 변환해야 함
- 매번 복잡한 코드를 작성하고 에러 처리를 구현해야 함
이 MCP 서버의 장점:
- 자연어로 질문하면 AI가 알아서 데이터를 가져옴
- Cloud Run에 배포되어 별도 서버 관리 불필요
- Secret Manager로 API 키를 안전하게 관리
- 캐싱 기능으로 빠른 응답 속도 제공
프로젝트 아키텍처
이번 프로젝트의 전체 구조는 다음과 같습니다:
┌─────────────┐
│ Gemini │ 사용자가 질문
│ CLI │ "삼성전자 배당은?"
└──────┬──────┘
│
▼
┌──────────────────┐
│ MCP Server │ Cloud Run에 배포
│ (FastMCP) │
├──────────────────┤
│ - Google Secret │
│ - sayou-stock │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ OpenDart API │ 금융감독원 공시 데이터
│ │ 재무제표, 배당 정보 등
└──────────────────┘
핵심 컴포넌트
- FastMCP: Python으로 MCP 서버를 쉽게 만들 수 있는 프레임워크
- sayou-stock: OpenDart API를 편리하게 사용할 수 있는 라이브러리
- Cloud Run: 서버리스로 MCP 서버 호스팅
- Secret Manager: API 키를 안전하게 관리
시작하기
사전 준비사항
이 튜토리얼을 따라하려면 다음이 필요합니다:
- ✅ Google Cloud Platform 계정
- ✅
gcloudCLI 설치 - ✅ Python 3.11 이상
1. GCP 환경 설정
먼저 필요한 Google Cloud 서비스를 활성화합니다:
# Cloud Run, Artifact Registry, Cloud Build 활성화
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
왜 이 서비스들이 필요할까요?
- Cloud Run: MCP 서버를 실행할 컨테이너 플랫폼
- Artifact Registry: 컨테이너 이미지 저장소
- Cloud Build: 자동으로 컨테이너 빌드
다음으로 서비스 계정을 만듭니다:
# MCP 서버용 서비스 계정 생성
gcloud iam service-accounts create mcp-server-sa \
--display-name="MCP Server Service Account"
이 서비스 계정은 나중에 Secret Manager에서 API 키를 가져올 때 사용됩니다.
2. 프로젝트 설치
git clone https://github.com/sayouzone/mcp-opendart-server.git
cd mcp-opendart-server
프로젝트 구조를 살펴보면:
mcp-opendart-server/
├── opendarts.py # MCP 서버 메인 코드
├── pyproject.toml # 의존성 정의
├── settings.json # Gemini CLI 설정
└── README.md
코드 구현 살펴보기
pyproject.toml: 의존성 관리
[project]
name = "opendart-mcp"
version = "0.1.0"
description = "Deploying an OpenDart MCP server on Cloud Run"
requires-python = ">=3.11"
dependencies = [
"fastmcp==2.12.4", # MCP 서버 프레임워크
"beautifulsoup4>=4.14.0", # HTML 파싱
"pandas==2.3.3", # 데이터 처리
"xmltodict==1.0.2", # XML 파싱
"sayou-stock>=0.2.0", # OpenDart API 래퍼
"google-cloud-secret-manager", # API 키 관리
"lxml==6.0.2", # XML 처리
]
주요 라이브러리 설명:
fastmcp: MCP 프로토콜을 구현하는 핵심 라이브러리sayou-stock: OpenDart API를 쉽게 사용할 수 있게 만든 래퍼google-cloud-secret-manager: 민감한 API 키를 안전하게 저장
opendarts.py: 핵심 로직
✅ OpenDart API 키 (발급받기)
1) 초기 설정 및 API 키 로드
import logging
from fastmcp import FastMCP
from google.cloud import secretmanager
from sayou.stock.opendart import OpenDartCrawler
# 로깅 설정
logger = logging.getLogger(__name__)
logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)
# Secret Manager에서 API 키 가져오기
sm_client = secretmanager.SecretManagerServiceClient()
name = "projects/1037372895180/secrets/DART_API_KEY/versions/latest"
response = sm_client.access_secret_version(name=name)
dart_api_key = response.payload.data.decode("UTF-8")
💡 보안 팁: API 키를 코드에 하드코딩하지 않고 Secret Manager를 사용하는 이유는:
- 버전 관리 시스템에 민감 정보가 노출되지 않음
- 키 회전(rotation)이 용이함
- 접근 권한을 세밀하게 제어 가능
2) OpenDart Crawler 초기화
# OpenDart API 크롤러 초기화
crawler = OpenDartCrawler(api_key=dart_api_key)
corp_data = crawler.corp_data
crawler.save_corp_data("corpcode.json")
OpenDart API는 주식 종목 코드가 아닌 corp_code를 사용합니다. 이 코드는:
- "삼성전자" → corp_code 변환
- "005930" → corp_code 변환
- 기업명이나 종목 코드로 유연하게 조회 가능
3) MCP Tool 정의: 재무제표 조회
@mcp.tool(
name="find_opendart_finance",
description="""OpenDART에서 한국 주식 재무제표 수집.
사용 예: 005930, 삼성전자, 035720.KQ
반환: {
"ticker": str,
"balance_sheet": str | None, # 재무상태표
"income_statement": str | None, # 손익계산서
"cash_flow": str | None # 현금흐름표
}
""",
tags={"opendart", "fundamentals", "korea"}
)
def find_opendart_finance(stock: str, year: Optional[int] = None,
quarter: Optional[int] = None):
"""재무제표 3종을 한번에 가져오는 도구"""
logger.info(f">>> 🛠️ Tool: 'find_opendart_finance' called for '{stock}'")
# 년도/분기 설정 (없으면 최근 분기 사용)
year, quarter = _year_quarter(year, quarter)
# 기업 코드 찾기
corp_code = crawler.fetch_corp_code(stock)
# API 호출 및 데이터 반환
# ... (실제 구현)
Tool의 역할: LLM이 이 함수를 "도구"로 인식하고, 필요할 때 자동으로 호출합니다.
4) MCP Prompt: 커스텀 명령어
@mcp.prompt()
def dividend(stock: str, year: Optional[int] = None,
quarter: Optional[int] = None):
"""배당 정보를 조회하는 커스텀 명령어"""
return (
f"{stock}의 {year}년 {quarter}분기 배당 정보를 찾았습니다. "
f"문서번호, 기업코드, 기업명, 당기순이익(백만원), "
f"현금배당수익률(%), 주당순이익(원), 주당 현금배당금(원), "
f"현금배당성향(%), 현금배당금총액(백만원)을 응답합니다."
)
Prompt의 특징:
/dividend --stock=삼성전자형태로 사용- LLM에게 명확한 출력 형식을 지시
- 사용자가 일관된 결과를 받을 수 있음
Cloud Run에 배포하기
환경변수 설정
export MCP_SERVER_NAME=opendart-mcp-server
export GOOGLE_CLOUD_PROJECT=sayouzone-ai
서비스 계정 권한 부여
# Secret Manager 접근 권한 부여
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member=serviceAccount:mcp-server-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
--role="roles/secretmanager.secretAccessor"
왜 이 권한이 필요한가요? MCP 서버가 실행될 때 Secret Manager에서 DART API 키를 읽어야 하기 때문입니다.
배포 실행
# 인증이 필요한 배포 (프로덕션 권장)
gcloud run deploy $MCP_SERVER_NAME \
--service-account=mcp-server-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
--no-allow-unauthenticated \
--region=us-central1 \
--source=. \
--labels=dev-tutorial=stocks-mcp
배포 옵션 설명:
--no-allow-unauthenticated: 인증된 사용자만 접근 가능--source=.: 현재 디렉토리의 코드를 자동으로 컨테이너화--region: 서버 위치 (한국에서는asia-northeast3도 가능)
배포가 완료되면 Cloud Run이 자동으로:
- 🐳 Dockerfile 생성 (없으면)
- 📦 컨테이너 이미지 빌드
- 🚀 Cloud Run에 배포
- 🔗 HTTPS URL 생성
Gemini CLI로 테스트하기
Gemini CLI 설치
macOS (Homebrew):
brew install gemini-cli
Windows (npm):
npm install -g @google/gemini-cli
npx (설치 없이 실행):
npx https://github.com/google-gemini/gemini-cli
설정 파일 준비
MCP 서버에 연결하려면 settings.json이 필요합니다:
# 환경변수 설정
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT \
--format="value(projectNumber)")
export ID_TOKEN=$(gcloud auth print-identity-token)
settings.json 생성:
{
"mcpServers": {
"opendart-remote": {
"httpUrl": "https://opendart-mcp-server-$PROJECT_NUMBER.us-central1.run.app/mcp",
"headers": {
"Authorization": "Bearer $ID_TOKEN"
}
}
},
"security": {
"auth": {
"selectedType": "cloud-shell"
}
}
}
중요: 환경변수를 실제 값으로 치환해야 합니다:
# macOS/Linux
envsubst < settings.json > ~/.gemini/settings.json
# 또는 수동으로 복사
cp settings.json ~/.gemini/
Gemini CLI 실행
# Gemini API 키 설정
export GEMINI_API_KEY="YOUR_API_KEY"
# CLI 시작
gemini
성공적으로 연결되면 다음과 같은 화면을 볼 수 있습니다:
░░░ ░░░░░░░░░ ░░░░░░░░░░ ░░░░░░ ░░░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░
░░░ ░░░ ░░░ ░░░ ░░░░░░ ░░░░░░ ░░░ ░░░░░░ ░░░░░ ░░░
░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░ ░░░
███ ░░░ █████████░░██████████ ██████ ░░██████░█████░██████ ░░█████ █████░
███ ░░░ ███░ ███░███░░ ██████ ░██████░░███░░██████ ░█████ ███░░
███ ███░░░ ░░███░░ ███░███ ███ ███░░███░░███░███ ███░░ ███░░
░░░ ███ ███ ░░░█████░██████░░░░░███░░█████ ███░░███░░███░░███ ███░░░ ███░░░
███ ███ ███ ███ ███ ███ ███ ███ ███ ██████ ███
███ ███ ███ ███ ███ ███ ███ ███ █████ ███
███ █████████ ██████████ ███ ███ █████ ███ █████ █████
Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. /help for more information.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Gemini CLI update available! 0.17.1 → 0.23.0 │
│ Installed via Homebrew. Please update with "brew upgrade". │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Using: 1 GEMINI.md file | 1 MCP server
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ > Type your message or @path/to/file │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
~/.../sayouzone/mcp-opendart-server (main*) no sandbox (see /docs) auto
실제 사용 예시
이제 자연어로 질문할 수 있습니다!
> 삼성전자 배당에 대해 알려줘
결과:
╭───────────────────────────────────────────────────────╮
│ ✓ find_opendart_dividend (opendart-remote MCP Server) │
│ {"stock":"삼성전자"} │
╰───────────────────────────────────────────────────────╯
✦ 삼성전자 배당 정보 (2025년 9월 30일 기준):
* 주당 현금배당금(원)
* 보통주: 1,102원
* 우선주: 1,102원
* 현금배당수익률(%)
* 보통주: 1.30%
* 우선주: 1.70%
* 현금배당금총액(백만원): 7,354,422 백만원
* (연결)현금배당성향(%): 29.50%
다양한 질문 예시
MCP 서버는 다양한 형태의 질문을 이해합니다:
재무제표 조회:
- "삼성전자 재무제표 보여줘"
- "2024년 3분기 SK하이닉스 손익계산서"
- "005930의 현금흐름표 분석해줘"
배당 정보:
- "삼성전자 배당이 어떻게 되지?"
- "2025년 LG전자 배당 성향은?"
- "NAVER 주당 배당금 알려줘"
기업 정보:
- "카카오 최근 공시 정보"
- "현대차 정기보고서 주요 내용"
동작 원리 Deep Dive
MCP 통신 흐름
- 사용자 질문: "삼성전자 배당 알려줘"
- Gemini 분석: 질문을 이해하고 적절한 Tool 선택
- MCP 호출:
find_opendart_dividend(stock="삼성전자") - Cloud Run 실행:
- Secret Manager에서 API 키 가져오기
- OpenDart API 호출
- 데이터 파싱 및 정제
- 응답 반환: JSON 형태로 결과 전달
- Gemini 포맷팅: 사용자 친화적인 형태로 표시
캐싱 전략
코드를 자세히 보면 use_cache 옵션이 있습니다:
def find_opendart_finance(stock: str, use_cache: bool = True):
"""
use_cache=True (기본): GCS에서 캐시된 데이터 우선 사용
use_cache=False: 항상 새로 크롤링 (30초+ 소요)
"""
왜 캐싱이 중요한가요?
- OpenDart API는 응답이 느립니다 (30초+)
- 재무제표는 분기마다 업데이트되므로 자주 변하지 않음
- 같은 데이터를 반복 조회하는 경우가 많음
실전 활용 시나리오
1. 투자 분석 에이전트
> 삼성전자와 SK하이닉스의 2024년 영업이익률을 비교해줘
MCP 서버는 두 기업의 재무제표를 자동으로 가져와서 비교 분석을 제공합니다.
2. 배당주 스크리닝
> 배당수익률 3% 이상인 기업 추천해줘
여러 기업의 배당 정보를 조회해 조건에 맞는 종목을 필터링합니다.
3. 공시 모니터링
> 최근 일주일간 NAVER의 주요 공시 요약해줘
실시간 공시 정보를 가져와 요약본을 제공합니다.
트러블슈팅
문제 1: "MCP server connection failed"
원인: 인증 토큰 만료
해결:
# 토큰 재발급
export ID_TOKEN=$(gcloud auth print-identity-token)
# settings.json 업데이트
문제 2: "Secret not found"
원인: Secret Manager에 API 키가 없음
해결:
# API 키 등록
echo -n "YOUR_DART_API_KEY" | \
gcloud secrets create DART_API_KEY \
--data-file=-
문제 3: 느린 응답 속도
원인: OpenDart API 자체가 느림
해결:
use_cache=True옵션 사용- Cloud Run의 인스턴스 최소 개수 설정:
gcloud run services update $MCP_SERVER_NAME \
--min-instances=1
확장 가능성
이 MCP 서버는 다양하게 확장할 수 있습니다:
1. 알림 기능
@mcp.tool()
def monitor_disclosure(stock: str, keywords: list):
"""특정 키워드가 포함된 공시 알림"""
# Pub/Sub + Cloud Functions로 구현
pass
2. 분석 도구 통합
@mcp.tool()
def analyze_financial_health(stock: str):
"""
재무제표를 기반으로:
- 유동비율, 부채비율 계산
- ROE, ROA 분석
- 트렌드 시각화
"""
pass
마치며
이 튜토리얼에서는 MCP 서버를 활용해 OpenDart API를 LLM에 연결하는 방법을 알아봤습니다.
핵심 포인트 정리:
- ✅ MCP는 LLM과 외부 세계의 다리: 실시간 데이터 접근 가능
- ✅ FastMCP로 쉬운 구현: Python 데코레이터만으로 도구 정의
- ✅ Cloud Run의 장점: 서버리스, 자동 스케일링, 보안
- ✅ 자연어 인터페이스: 복잡한 API 호출을 일상 언어로
다음 단계
- 📚 FastMCP 공식 문서 탐색
- 🔧 다른 금융 API (FinanceDataReader, yfinance) 통합
- 🤖 Slack Bot이나 Discord Bot으로 확장
- 📊 Streamlit으로 대시보드 구축
참고 자료
소스 코드: github.com/sayouzone/mcp-opendart-server
질문이나 피드백은 cksdn2034@sayouzone.com 의견을 보내주세요! 🙌