mcp 실습 심화 - 데이터 에이전트(db) 2

이우철·2026년 3월 18일

테이블 조인해서 가져와 보자.

  1. 테이블 고도화 - 사원, 부서, 프로젝트 구성
import sqlite3
import os

def init_advanced_db():
    db_path = 'enterprise.db'
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    
    # 테이블 초기화 (기존 데이터 삭제 후 재생성)
    cursor.execute('DROP TABLE IF EXISTS projects')
    cursor.execute('DROP TABLE IF EXISTS employees')
    cursor.execute('DROP TABLE IF EXISTS departments')
    
    # 1. 부서 테이블 (부서명, 예산)
    cursor.execute('CREATE TABLE departments (dept_id INTEGER PRIMARY KEY, dept_name TEXT, budget INTEGER)')
    
    # 2. 사원 테이블 (사원번호, 성함, 부서명, 직급) - 제공해주신 형식
    cursor.execute('CREATE TABLE employees (emp_id INTEGER PRIMARY KEY, name TEXT, dept_name TEXT, position TEXT)')
    
    # 3. 프로젝트 테이블 (프로젝트명, 담당 사원번호)
    cursor.execute('CREATE TABLE projects (proj_id INTEGER PRIMARY KEY, proj_name TEXT, lead_emp_id INTEGER)')
    
    # 데이터 삽입 (제공해주신 사원 정보 기준)
    departments = [(1, '개발팀', 8000), (2, '인사팀', 3000), (3, 'UI팀', 5000)]
    employees = [
        (101, '이개발', '개발팀', '대리'),
        (102, '김인사', '인사팀', '과장'),
        (103, '박디자', 'UI팀', '사원')
    ]
    projects = [
        (501, '차세대 ERP 구축', 101), 
        (502, '모바일 앱 개편', 103)
    ]
    
    cursor.executemany('INSERT INTO departments VALUES (?,?,?)', departments)
    cursor.executemany('INSERT INTO employees VALUES (?,?,?,?)', employees)
    cursor.executemany('INSERT INTO projects VALUES (?,?,?)', projects)
    
    conn.commit()
    conn.close()
    print(f"데이터 정합성 완료: {os.path.abspath(db_path)}")

if __name__ == "__main__":
    init_advanced_db()
  1. 3중 조인 MCP 서버 구성 (joint_server.py)
    사원 이름(employees)과 부서 이름(departments)이 텍스트로 연결되는 구조를 고려하여 조인 쿼리를 작성했습니다.
import sqlite3
import os
import sys
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("AdvancedDataAgent")
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "enterprise.db")

@mcp.tool()
def get_project_and_dept_info(project_name: str) -> str:
    """프로젝트명을 입력받아 담당자(직급 포함)와 해당 부서의 예산을 조회합니다."""
    try:
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        # 3중 Join: Projects -> Employees -> Departments
        query = """
        SELECT p.proj_name, e.name, e.position, e.dept_name, d.budget
        FROM projects p
        JOIN employees e ON p.lead_emp_id = e.emp_id
        JOIN departments d ON e.dept_name = d.dept_name
        WHERE p.proj_name LIKE ?
        """
        
        cursor.execute(query, (f"%{project_name}%",))
        result = cursor.fetchone()
        conn.close()
        
        if result:
            p_name, e_name, pos, d_name, budget = result
            return (f"[{p_name}] 프로젝트 상세 리포트\n"
                    f"- 담당자: {e_name} {pos}\n"
                    f"- 소속 부서: {d_name}\n"
                    f"- 부서 가용 예산: {budget}만원")
        else:
            return f"'{project_name}' 프로젝트에 대한 정보를 찾을 수 없습니다."
            
    except Exception as e:
        return f"데이터 조회 오류: {str(e)}"

if __name__ == "__main__":
    # 서버 실행 시 시동 확인 로그 (stderr)
    print("--- Enterprise Join Agent Online ---", file=sys.stderr)
    mcp.run()
  1. claude 설정파일 추가
"adv-db-agent": {
      "command": "C:/dev/llm/llm_env/Scripts/python.exe",
      "args": ["C:/dev/llm/join_server.py"]
    }
  1. 실습 가이드 - 테스트 시나리오
    Claude Desktop에서 아래와 같이 질문하여 에이전트의 판단 능력을 확인합니다.

질문: "차세대 ERP 프로젝트 담당자가 누구야? 그 팀 예산은 얼마인지도 알려줘."

기대 답변: "차세대 ERP 구축 프로젝트의 담당자는 이개발 대리이며, 소속된 개발팀의 예산은 8,000만원입니다."

포인트
데이터 매핑: projects에는 사원 번호(101)만 있지만, 조인을 통해 이개발이라는 이름과 대리라는 직급을 찾아오는 과정을 설명하세요.

비즈니스 로직의 추상화: 신입사원들에게 "사용자는 SQL 조인문을 몰라도 '예산'과 '담당자'를 동시에 물어볼 수 있고, 에이전트가 이를 해석해 기술(Tool)을 사용한다"는 점을 강조하세요.

예외 처리: 만약 '인사팀'이 담당하는 프로젝트를 물어보면(현재 데이터엔 없음), 에이전트가 "해당 프로젝트를 찾을 수 없다"고 정확히 답변하는지 확인하게 하세요.

업로드중..

profile
개발 정리 공간 - 업무일때도 있고, 공부일때도 있고...

0개의 댓글