본문 바로가기
IT/claude

전자공시시스템(DART) MCP 서버 만들기

by 가능성1g 2026. 4. 14.
반응형

mcp 서버를 opencode로 테스트 하다가 claude 를 쓸 수 있어서 바꿔서 테스트 했다.

만드는 방식은 동일하니, 양쪽다 쓸수 있다.

 

하지만 역시, claude 쪽이 뭔가 좀더 깔끔한 느낌이긴하다. 역시 상용이 좋..;

 

mcp 서버 생성은 동일하게 하면 된다.

 

** 준비물 **

DART 의 API 키

dart 사이트에 회원가입후, 오픈API 키를 받는다.

2~3영업일 걸릴수 있다고 써 있지만, 보통 바로 되는것 같다.

전자공시시스템

 

전자공시시스템

많이 본 문서 최근 3영업일 기준 가장 많이 본 공시를 보여줍니다.

dart.fss.or.kr

 

그리고 직접 API 호출이 아닌 OpenDartReader 라는 파이썬 라이브러리를 활용하도록 한다. 

 

1. 환경구성

Dart-MCP 폴더 생성

mkdir Dart-MCP

프로젝트 초기 화 및 필요한 라이브러리 설치

uv init

uv add opendartreader fastmcp

[project]
name = "dart-mcp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastmcp>=3.2.3",
    "opendartreader>=0.2.3",
]

.env 파일을 만들고 키를 써준다.

DART_API_KEY="DART키"

 

2. main.py 를 다음과 같이 수정한다.

import OpenDartReader
import pandas as pd
import os

from fastmcp import FastMCP
from typing import Annotated, Literal
from pydantic import Field
from dotenv import load_dotenv

load_dotenv()

# OpenDartReader uses relative path 'docs_cache' — ensure cwd is project root
os.chdir(os.path.dirname(os.path.abspath(__file__)))

mcp = FastMCP("Dart-MCP")
dart = OpenDartReader(os.getenv("DART_API_KEY"))

REPORT_CODES = [
    '조건부자본증권미상환', '미등기임원보수', '회사채미상환', '단기사채미상환', '기업어음미상환',
    '채무증권발행', '사모자금사용', '공모자금사용', '임원전체보수승인', '임원전체보수유형',
    '주식총수', '회계감사', '감사용역', '회계감사용역계약', '사외이사', '신종자본증권미상환',
    '증자', '배당', '자기주식', '최대주주', '최대주주변동', '소액주주', '임원', '직원',
    '임원개인보수', '임원전체보수', '개인별보수', '타법인출자'
]
EVENT_CODES = [
    '부도발생', '영업정지', '회생절차', '해산사유', '유상증자', '무상증자', '유무상증자', '감자',
    '관리절차개시', '소송', '해외상장결정', '해외상장폐지결정', '해외상장', '해외상장폐지',
    '전환사채발행', '신주인수권부사채발행', '교환사채발행', '관리절차중단', '조건부자본증권발행',
    '자산양수도', '타법인증권양도', '유형자산양도', '유형자산양수', '타법인증권양수', '영업양도',
    '영업양수', '자기주식취득신탁계약해지', '자기주식취득신탁계약체결', '자기주식처분', '자기주식취득',
    '주식교환', '회사분할합병', '회사분할', '회사합병', '사채권양수', '사채권양도결정'
]

@mcp.tool(
    name="get_corp_code",
    description="공시대상회사의 고유번호(corp_code)를 반환하는 함수입니다.",
)
def get_corp_code(
    corp_name: Annotated[str, Field(description="공시대상회사의 이름입니다.")]
) -> str:
    """
    Args:
        corp_name (str): 공시대상회사의 이름입니다.
    Returns:
        str: 공시대상회사의 고유번호(corp_code)입니다.
    """
    return dart.find_corp_code(corp_name)

@mcp.tool(
    name="get_company_overview",
    description="공시대상회사의 개요를 반환하는 함수입니다.",
)
def get_company_overview(
    corp_code: Annotated[str, Field(description="공시대상회사의 고유번호입니다.")]
) -> dict:
    """
    Args:
        corp_code (str): 공시대상회사의 고유번호입니다.
    Returns:
        dict: 공시대상회사의 개요 정보입니다.
    """
    return dart.company(corp_code)

@mcp.tool(
    name="get_financial_statement",
    description="공시대상회사의 재무제표를 반환하는 함수입니다.",
)
def get_financial_statement(
    corp_code: Annotated[str, Field(description="공시대상회사의 고유번호입니다.")],
    date: Annotated[str, Field(description="재무제표의 날짜입니다. YYYY 형식입니다.")],
    report_code: Annotated[str, Field(description="재무제표의 종류입니다. 11013: 연결재무제표, 11012: 별도재무제표")],
    sj_div: Annotated[Literal['BS','IS'], Field(description="재무제표의 구분입니다. BS: 재무상태표, IS: 손익계산서")],
) -> pd.DataFrame:
    """
    Args:
        corp_code (str): 공시대상회사의 고유번호입니다.
        date (str): 재무제표의 날짜입니다. YYYY 형식입니다.
        report_code (str): 재무제표의 종류입니다. 11013: 연결재무제표, 11012: 별도재무제표
        sj_div (str): 재무제표의 구분입니다. BS: 재무상태표, IS: 손익계산서
    Returns:
        pd.DataFrame: 공시대상회사의 재무제표 정보입니다.
    """
    df = dart.finstate(corp_code, date, report_code)
    filtered_df = df[(df['fs_div'] == 'CFS') & (df['sj_div'] == sj_div)]
    if filtered_df.empty:
        filtered_df = df[(df['fs_div'] == 'OFS') & (df['sj_div'] == sj_div)]
    filtered_df = filtered_df[["corp_code", "bsns_year", "reprt_code", "account_nm", "thstrm_amount"]]
    return filtered_df

@mcp.tool(
    name="get_specific_business_report",
    description="공시대상회사의 특정 사업보고서 항목을 반환하는 함수",
)
def get_specific_business_report(
    corp_code: Annotated[str, Field(description="공시대상회사의 고유번호입니다.")],
    report_code: Annotated[str, Field(description="사업보고서의 종류입니다. 11013: 연결재무제표, 11012: 별도재무제표")],
    date: Annotated[str, Field(description="사업보고서의 날짜입니다. YYYY 형식입니다.")],
) -> pd.DataFrame:
    """
    Args:
        corp_code (str): 공시대상회사의 고유번호입니다.
        report_code (str): 사업보고서의 종류입니다. 11013: 연결재무제표, 11012: 별도재무제표
        date (str): 사업보고서의 날짜입니다. YYYY 형식입니다.
    Returns:
        pd.DataFrame: 공시대상회사의 특정 사업보고서 항목 정보입니다.
    """
    if report_code not in REPORT_CODES:
        return {"error": "유효하지 않은 report_code입니다. 유효한 report_code는 다음과 같습니다: " + ", ".join(REPORT_CODES)}
    
    result = dart.report(corp_code, report_code, date)

    if isinstance(result, pd.DataFrame) and result.empty:
        return {"error": "해당 report_code에 대한 데이터가 없습니다."}
    
    return result

@mcp.tool(
    name="get_major_event_report",
    description="공시대상회사의 주요 이벤트 보고서를 반환하는 함수",
)
def get_major_event_report(
    corp_code: Annotated[str, Field(description="공시대상회사의 고유번호입니다.")],
    event: Annotated[str, Field(description="이벤트 보고서의 종류입니다. 이벤트 코드중에 하나여야합니다. {EVENT_CODES}")],
    date: Annotated[str, Field(description="이벤트 보고서의 날짜입니다. YYYY 형식입니다.")],
) :
    """
    Args:
        corp_code (str): 공시대상회사의 고유번호입니다.
        event_code (str): 이벤트 보고서의 종류입니다. 부도발생, 영업정지, 회생절차 등
        date (str): 이벤트 보고서의 날짜입니다. YYYY 형식입니다.
    Returns:
        dict or pd.DataFrame: 공시대상회사의 주요 이벤트 보고서 정보입니다. 이벤트가 유효하지 않거나 데이터가 없으면 에러를 반환합니다.
    """
    if event not in EVENT_CODES:
        return {"error": "유효하지 않은 event_code입니다. 유효한 event_code는 다음과 같습니다: " + ", ".join(EVENT_CODES)}
    
    result = dart.event(corp_code, event, date)

    if isinstance(result, pd.DataFrame) and result.empty:
        return {"error": "해당 event_code에 대한 데이터가 없습니다."}
    
    return result

if __name__ == "__main__":
    mcp.run()

dotenv 모듈은 별도로 설치 안해도 됐었다.(fastmcp에 포함되어있나?)

처음 mcp 모듈 실행시 폴더 권한 오류가 발생하는데, 모듈 폴더에 docs_cache 를 미리 생성해 두자. (관련 소스 라인 참고!)

 

3. 테스트 실행 

환경 엑티베이트 하고

.venv\Script\activate.bat

fastmcp 로 실행해서 정상실행을 확인한다.

fastmcp run main.py

정상이면, claude config 에 mcp 서버로 등록한다.

 

4. mcp 모듈 등록

클로드에게 

"이 mcp 서버를 mcp 모듈로 등록해줘"

라고 해도 잘 등록해준다.

fastmcp의 기능으로 아래와 같이 실행하면

fastmcp install claude-desktop main.py

되어야 될것 같은데, 나는 안됐다. config 를 못찾는다고 나온다.

클로드가 이렇게 등록해 줬다.

C:\Users\{사용자ID}\AppData\Local\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json

    "dart-mcp": {
      "command": "C:\\MENDIX_DEV\\MCP\\Dart-MCP\\.venv\\Scripts\\fastmcp.exe",
      "args": [
        "run",
        "C:\\MENDIX_DEV\\MCP\\Dart-MCP\\main.py"
      ],
      "env": {
        "DART_API_KEY": "API키"
      }
    }

 

아래와 같이 활용 가능하다.

 

xx DART 고유번호를 조회해줘

xx 기업정보를 조회해줘

xx 2025년 4분기 재무상태표/손익계산서 정보를 조회해줘

xx 2025년 배당 관련 사업보고서 항목을 조회해줘

 

등의 다양한 질문이 가능하다.

반응형