본문 바로가기
  • 데이터에 가치를 더하다, 서영석입니다.
카테고리 없음

MCP 완전체 만들기 — Resource와 Prompt까지 붙여보기

by 꿀먹은데이터 2026. 3. 15.

지난 글에서는 @mcp.tool()을 이용해서 AI가 직접 DB를 조회할 수 있는 MCP 서버를 만들어봤다.

Tool을 하나 붙이고 나니까 확실히 느낌이 다르다.
Claude가 그냥 답을 만들어내는 게 아니라, 실제로 서버 함수를 호출해서 데이터를 가져오기 시작한다.

그런데 MCP를 조금 더 만져보면 알게 된다. Tool만으로는 생각보다 할 수 있는 게 제한적이다.

MCP에는 Tool 말고도 두 가지 핵심 기능이 더 있다.

기능 데코레이터 역할
Tool @mcp.tool() AI가 실행할 수 있는 함수
Resource @mcp.resource() AI에게 읽을 수 있는 데이터를 제공
Prompt @mcp.prompt() AI에게 재사용 가능한 지시문을 제공

이 세 가지를 모두 갖추면 진짜 쓸 수 있는 MCP 서버가 된다.

Tool / Resource / Prompt 차이를 한 번에 이해하는 방법

비유로 설명하면 이렇다.

Tool = AI의 손 (직접 실행, 결과 반환) 
Resource = AI의 참고서 (읽기 전용 데이터, 컨텍스트 제공) 
Prompt = AI의 업무 매뉴얼 (미리 정의된 지시 템플릿)

 

예를 들어 "이번 달 매출 보고서 작성해줘" 라는 요청이 오면:

  1. Resource — 회사 DB 스키마, 보고서 양식 파일을 읽는다
  2. Tool — SQL 쿼리를 실행해서 데이터를 가져온다
  3. Prompt — "보고서 작성 전문가 모드"로 전환해서 결과물을 생성한다

STEP 1. Resource 만들기 — @mcp.resource()

Resource는 AI가 언제든지 참조할 수 있는 데이터다. Tool과 달리 실행이 아니라 읽기 전용이다.

기본 구조

Copy@mcp.resource("resource://스키마명")
def 함수명() -> str:
    """리소스 설명"""
    return "데이터 내용"

URI 형식(resource://...)으로 이름을 붙인다는 점이 Tool과 다르다.

- DB 스키마를 Resource로 제공하기

AI가 SQL을 작성하려면 테이블 구조를 알아야 한다. 

매번 Tool로 조회하는 대신, Resource로 고정해두면 훨씬 효율적이다.

from mcp.server.fastmcp import FastMCP
import sqlite3

mcp = FastMCP("sales-db-server")

@mcp.resource("resource://schema/sales")
def get_schema() -> str:
    """sales.db의 전체 테이블 스키마 반환"""
    with sqlite3.connect("sales.db") as conn:
        tables = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table'"
        ).fetchall()

        schema_info = []
        for (table,) in tables:
            columns = conn.execute(
                f"PRAGMA table_info({table})"
            ).fetchall()
            col_desc = ", ".join(
                f"{col[1]}({col[2]})" for col in columns
            )
            schema_info.append(f"[{table}] {col_desc}")

    return "\n".join(schema_info)

이제 Claude는 resource://schema/sales를 참조하면 전체 DB 구조를 한눈에 파악할 수 있다.

파일을 Resource로 제공하기

텍스트 파일, 설정 파일도 Resource로 만들 수 있다.

@mcp.resource("resource://config/report-template")
def get_report_template() -> str:
    """보고서 작성 양식 반환"""
    with open("report_template.txt", "r", encoding="utf-8") as f:
        return f.read()

 

핵심: Resource는 AI의 사전 지식처럼 동작한다.

Tool은 "실행해야 결과가 나오는 것", Resource는 "항상 참조 가능한 것"으로 구분하면 된다.

STEP 2. Dynamic Resource — 동적으로 변하는 데이터

Resource는 고정 데이터만 제공하는 게 아니다. URI 파라미터를 받아서 동적으로 데이터를 반환할 수도 있다.

@mcp.resource("resource://customer/{customer_id}")
def get_customer_profile(customer_id: str) -> str:
    """특정 고객 프로필 반환"""
    with sqlite3.connect("sales.db") as conn:
        row = conn.execute(
            "SELECT * FROM customers WHERE id = ?",
            (customer_id,)
        ).fetchone()

    if not row:
        return f"고객 ID {customer_id}를 찾을 수 없습니다."

    return f"이름: {row[1]}, 이메일: {row[2]}, 지역: {row[3]}"

 

이제 Claude는resource://customer/42처럼 특정 고객 정보를 바로 참조할 수 있다.

( Tool로 조회하는 것과 비슷해 보이지만 개념적으로는 읽기 전용 데이터 제공에 가깝다.)

STEP 3. Prompt 만들기 — @mcp.prompt()

Prompt는 자주 쓰는 지시 패턴을 템플릿으로 저장하는 기능이다. 쉽게 말해 "Claude에게 미리 세팅해두는 업무 지시서"다.

기본 구조

@mcp.prompt()
def 프롬프트명(파라미터: str) -> str:
    """프롬프트 설명"""
    return f"지시문 내용 {파라미터}"

매출 분석 전문가 프롬프트

from mcp.server.fastmcp import FastMCP
from mcp.types import TextContent

@mcp.prompt()
def sales_analyst_prompt(period: str = "이번 달") -> list:
    """매출 분석 전문가 모드 활성화"""
    return [
        TextContent(
            type="text",
            text=f"""당신은 데이터 기반 매출 분석 전문가입니다.
{period}의 매출 데이터를 분석할 때 다음 순서로 진행하세요:

1. 전체 주문 수와 총 매출액 확인
2. 상품별, 고객별 매출 비교
3. 이상 패턴(급증/급감) 탐지
4. 핵심 인사이트 3가지 도출
5. 다음 달 개선 제안 작성

모든 수치는 한국 원화(₩)로 표기하고,
숫자는 천 단위 구분자(,)를 사용하세요.
"""
        )
    ]

 이렇게 해두면 AI가 보고서를 작성할 때 분석 방식 자체가 일정하게 유지된다.

에러 리포트 프롬프트

@mcp.prompt()
def error_report_prompt(error_type: str) -> list:
    """DB 에러 발생 시 리포트 작성 지시"""
    return [
        TextContent(
            type="text",
            text=f"""DB에서 '{error_type}' 에러가 발생했습니다.
다음 항목을 순서대로 점검하고 보고서를 작성하세요:
- 에러 발생 시각과 빈도
- 영향받은 테이블과 데이터 범위
- 임시 조치 방안
- 근본 원인 추정
- 재발 방지 대책"""
        )
    ]

STEP 4. Tool + Resource + Prompt 합치기

이제 Tool + Resource + Prompt를 모두 담은 완성형 MCP 서버를 만들어보자.

# mcp_server_full.py
from mcp.server.fastmcp import FastMCP
from mcp.types import TextContent
import sqlite3

mcp = FastMCP("sales-db-full")

# ── Resource ──────────────────────────────
@mcp.resource("resource://schema/sales")
def get_schema() -> str:
    """DB 전체 스키마"""
    with sqlite3.connect("sales.db") as conn:
        tables = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table'"
        ).fetchall()
        result = []
        for (t,) in tables:
            cols = conn.execute(f"PRAGMA table_info({t})").fetchall()
            col_str = ", ".join(f"{c[1]}({c[2]})" for c in cols)
            result.append(f"[{t}] {col_str}")
    return "\n".join(result)

@mcp.resource("resource://customer/{customer_id}")
def get_customer(customer_id: str) -> str:
    """특정 고객 정보"""
    with sqlite3.connect("sales.db") as conn:
        row = conn.execute(
            "SELECT * FROM customers WHERE id=?", (customer_id,)
        ).fetchone()
    return str(row) if row else "고객 없음"

# ── Tool ──────────────────────────────────
@mcp.tool()
def query_sales(sql: str) -> list:
    """SELECT SQL 실행"""
    if not sql.strip().upper().startswith("SELECT"):
        return [{"error": "SELECT만 허용됩니다"}]
    with sqlite3.connect("sales.db") as conn:
        return conn.execute(sql).fetchall()

@mcp.tool()
def get_sales_summary() -> dict:
    """매출 요약 통계"""
    with sqlite3.connect("sales.db") as conn:
        total = conn.execute("SELECT COUNT(*) FROM orders").fetchone()[0]
        revenue = conn.execute(
            "SELECT SUM(o.quantity * p.price) "
            "FROM orders o JOIN products p ON o.product_id = p.id"
        ).fetchone()[0]
    return {"총_주문수": total, "총_매출": revenue}

# ── Prompt ────────────────────────────────
@mcp.prompt()
def sales_analyst_prompt(period: str = "이번 달") -> list:
    """매출 분석 전문가 프롬프트"""
    return [TextContent(type="text", text=f"""
당신은 {period} 매출 분석 전문가입니다.
1. 총 매출 요약 확인
2. 상품/고객별 분석
3. 인사이트 3가지 도출
4. 개선 제안 제시
수치는 ₩ 단위, 천 단위 구분자 사용.
""")]

if __name__ == "__main__":
    mcp.run(transport="stdio")
Copy

 핵심은 세 가지 역할이 명확히 나뉜다는 것이다.

Resource → DB 구조, 고객 정보 같은 참고 데이터

Tool → 실제 SQL 실행

Prompt → 분석 방식 정의

STEP 5. 각 기능의 동작 흐름 비교

질문 기능 흐름
"테이블 구조 알려줘" Resource resource://schema/sales 참조
"5월 매출 합계 계산해줘" Tool query_sales() 실행
"매출 보고서 작성해줘" Prompt + Tool sales_analyst_prompt()  get_sales_summary()
"고객 42번 정보 확인해줘" Resource resource://customer/42 참조

전체 프로젝트 구조

mcp_demo/
├── setup_db.py          # DB 초기화
├── mcp_server.py        # Tool만 (지난 글)
├── mcp_server_full.py   # Tool + Resource + Prompt (이번 글)
├── report_template.txt  # 보고서 양식
└── sales.db

마치며

지난 글: @mcp.tool() — AI의 손을 만들었다

이번 글: @mcp.resource() + @mcp.prompt() 

MCP의 세 구조를 정리하면 이렇다.

  • Tool = AI가 직접 실행하는 것
  • Resource = AI가 참조하는 것
  • Prompt = AI의 행동 방식을 정의하는 것

이 세 가지를 조합하면 단순한 "DB 조회 봇"이 아니라, 업무 컨텍스트를 이해하는 진짜 AI 어시스턴트가 된다.

시간될 때 이 MCP 서버를 로컬을 넘어 클라우드에 배포하는 방법을 다룰 예정이다. stdio 방식에서 SSE/HTTP 방식으로 전환하고, 팀원 누구나 접속 가능한 MCP 서버를 만들어볼 것이다.



반응형