Anthropic MCP 서버 만들기 - 나만의 AI 도구 직접 구축하기
2026년 4월 기준 | AI 신기능 분석
이 글에서 다루는 것: Anthropic의 Model Context Protocol(MCP)을 활용해 나만의 AI 도구 서버를 만드는 방법을 처음부터 끝까지 다룹니다. MCP가 뭔지 이해하는 것을 넘어서, 실제로 파이썬으로 MCP 서버를 구축하고, Claude Desktop과 연동하고, 실무에서 쓸 수 있는 도구를 만드는 과정을 코드와 함께 설명합니다. 날씨 조회, 데이터베이스 검색, 파일 관리 도구를 직접 만들어봤습니다.
MCP 서버란 무엇인가 (빠른 복습)
이전에 MCP 쉽게 이해하기 글에서 MCP의 개념을 다뤘습니다. 빠르게 복습하면, MCP(Model Context Protocol)는 AI 모델이 외부 도구와 데이터에 접근하는 표준 프로토콜입니다.
비유하자면 USB 같은 겁니다. USB 규격이 있기 전에는 각 기기마다 다른 케이블이 필요했습니다. MCP는 AI 도구의 USB입니다. 한 번 MCP 서버를 만들면, Claude Desktop, Claude Code, 그리고 MCP를 지원하는 어떤 클라이언트에서든 바로 쓸 수 있습니다.
AI 클라이언트 (Claude Desktop 등)
↕ MCP 프로토콜 (JSON-RPC)
MCP 서버 (내가 만드는 부분)
↕ 일반 API/DB 호출
외부 서비스 (DB, API, 파일시스템 등)
오늘은 개념을 넘어서, 직접 MCP 서버를 만들고 Claude에 연결하는 실습을 합니다.
MCP 아키텍처 이해하기
MCP 서버가 제공할 수 있는 것은 크게 세 가지입니다:
| 구성요소 | 설명 | 예시 |
|---|---|---|
| Tools (도구) | AI가 호출할 수 있는 함수 | 날씨 조회, DB 검색, 파일 읽기 |
| Resources (리소스) | AI가 읽을 수 있는 데이터 | 설정 파일, 문서, DB 스키마 |
| Prompts (프롬프트) | 재사용 가능한 프롬프트 템플릿 | 코드 리뷰 템플릿, 분석 템플릿 |
대부분의 MCP 서버는 Tools를 중심으로 만들어집니다. 오늘도 Tools 구현에 집중하되, 뒤에서 Resources와 Prompts도 다룹니다.
개발 환경 설정
# Python 3.10 이상 필요 python --version # Python 3.10+ # MCP 파이썬 SDK 설치 pip install mcp # 추가 패키지 (예제용) pip install httpx aiosqlite
mcp 패키지가 Anthropic이 공식 제공하는 MCP Python SDK입니다. 이전에는 mcp[cli]로 설치해야 했는데, 2026년 기준으로는 기본 패키지에 CLI 도구까지 포함됩니다.
첫 번째 MCP 서버 만들기
가장 간단한 MCP 서버부터 시작합니다. "현재 시간 알려주기" + "간단한 계산기" 두 가지 도구를 제공하는 서버입니다.
# my_first_mcp_server.py from mcp.server.fastmcp import FastMCP from datetime import datetime # MCP 서버 인스턴스 생성 mcp = FastMCP("My First MCP Server") # === 도구 1: 현재 시간 조회 === @mcp.tool() def get_current_time(timezone: str = "Asia/Seoul") -> str: """현재 시간을 조회합니다. Args: timezone: 타임존 (예: Asia/Seoul, US/Eastern) """ from zoneinfo import ZoneInfo now = datetime.now(ZoneInfo(timezone)) return f"{timezone} 기준 현재 시간: {now.strftime('%Y-%m-%d %H:%M:%S')}" # === 도구 2: 계산기 === @mcp.tool() def calculate(expression: str) -> str: """수학 계산을 수행합니다. Args: expression: 계산할 수식 (예: "2 + 3 * 4", "100 / 7") """ try: # 안전한 계산만 허용 allowed_chars = set("0123456789+-*/().% ") if not all(c in allowed_chars for c in expression): return "허용되지 않는 문자가 포함되어 있습니다." result = eval(expression) return f"{expression} = {result}" except Exception as e: return f"계산 오류: {e}" # 서버 실행 if __name__ == "__main__": mcp.run()
이게 전부입니다. @mcp.tool() 데코레이터를 붙이면 그 함수가 MCP 도구가 됩니다. 함수의 docstring과 타입 힌트가 자동으로 도구 설명과 파라미터 스키마가 됩니다. FastMCP가 JSON Schema를 자동으로 생성해주니까 별도로 스키마를 정의할 필요가 없습니다.
핵심은 일반 파이썬 함수를 만들고 데코레이터만 붙이면 된다는 겁니다. MCP 프로토콜의 복잡한 내부 구조는 SDK가 다 처리해줍니다.
Claude Desktop과 연동하기
만든 MCP 서버를 Claude Desktop에 연결하는 방법입니다.
설정 파일 수정
Claude Desktop의 설정 파일을 열어서 MCP 서버를 등록합니다.
// Windows: %APPDATA%\Claude\claude_desktop_config.json // Mac: ~/Library/Application Support/Claude/claude_desktop_config.json { "mcpServers": { "my-first-server": { "command": "python", "args": ["C:/projects/mcp/my_first_mcp_server.py"] } } }
Claude Desktop을 재시작하면, 채팅 입력창 옆에 도구 아이콘이 생깁니다. 클릭하면 "get_current_time"과 "calculate"가 보입니다. "지금 서울 시간 알려줘"라고 말하면 Claude가 자동으로 get_current_time 도구를 호출합니다.
처음 연동에 성공했을 때 꽤 감동적이었습니다. 내가 만든 파이썬 함수를 Claude가 직접 호출하는 걸 보는 건, 마치 AI에게 새로운 능력을 부여하는 느낌입니다.
실전 예제: 데이터베이스 검색 도구
진짜 실무에서 쓸 만한 걸 만들어봅시다. SQLite 데이터베이스를 검색하는 MCP 서버입니다. "이번 달 매출 상위 10개 제품 알려줘"라고 Claude에게 말하면, 데이터베이스를 직접 조회해서 답하는 겁니다.
# db_mcp_server.py from mcp.server.fastmcp import FastMCP import sqlite3 import json mcp = FastMCP("Database MCP Server") DB_PATH = "company_data.db" def get_db(): """데이터베이스 연결""" conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn @mcp.tool() def get_table_schema() -> str: """데이터베이스의 모든 테이블과 컬럼 구조를 조회합니다.""" conn = get_db() cursor = conn.cursor() # 모든 테이블 목록 cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") tables = [row[0] for row in cursor.fetchall()] schema = [] for table in tables: cursor.execute(f"PRAGMA table_info({table})") columns = cursor.fetchall() col_info = [f" - {col[1]} ({col[2]})" for col in columns] cursor.execute(f"SELECT COUNT(*) FROM {table}") count = cursor.fetchone()[0] schema.append(f"테이블: {table} ({count}행)\n" + "\n".join(col_info)) conn.close() return "\n\n".join(schema) @mcp.tool() def query_database(sql: str) -> str: """SQL 쿼리를 실행하고 결과를 반환합니다. SELECT 쿼리만 허용됩니다 (데이터 수정 불가). Args: sql: 실행할 SELECT SQL 쿼리 """ # 안전장치: SELECT만 허용 sql_upper = sql.strip().upper() if not sql_upper.startswith("SELECT"): return "오류: SELECT 쿼리만 허용됩니다." dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE"] if any(kw in sql_upper for kw in dangerous_keywords): return "오류: 데이터 수정 쿼리는 허용되지 않습니다." try: conn = get_db() cursor = conn.cursor() cursor.execute(sql) rows = cursor.fetchall() columns = [desc[0] for desc in cursor.description] conn.close() if not rows: return "결과 없음 (0행)" # 결과를 보기 좋게 포맷팅 result = [dict(zip(columns, row)) for row in rows] return json.dumps(result[:100], ensure_ascii=False, indent=2) + \ (f"\n\n(총 {len(rows)}행 중 상위 100행 표시)" if len(rows) > 100 else f"\n\n(총 {len(rows)}행)") except Exception as e: return f"SQL 실행 오류: {e}" @mcp.tool() def get_sales_summary(period: str = "this_month") -> str: """매출 요약 정보를 조회합니다. Args: period: 조회 기간 ('today', 'this_week', 'this_month', 'this_year') """ period_sql = { "today": "date('now')", "this_week": "date('now', '-7 days')", "this_month": "date('now', 'start of month')", "this_year": "date('now', 'start of year')", } if period not in period_sql: return f"지원하지 않는 기간: {period}" conn = get_db() cursor = conn.cursor() sql = f""" SELECT COUNT(*) as 주문수, SUM(amount) as 총매출, AVG(amount) as 평균주문금액, MAX(amount) as 최대주문금액, COUNT(DISTINCT customer_id) as 고객수 FROM orders WHERE order_date >= {period_sql[period]} """ cursor.execute(sql) row = cursor.fetchone() conn.close() return json.dumps({ "기간": period, "주문수": row[0], "총매출": row[1], "평균주문금액": round(row[2], 0) if row[2] else 0, "최대주문금액": row[3], "고객수": row[4] }, ensure_ascii=False) if __name__ == "__main__": mcp.run()
이 서버를 Claude Desktop에 연결하면 이런 대화가 가능합니다:
Claude: (get_sales_summary 도구 호출) "이번 달 총 매출은 1,234만원이고, 456건의 주문이 있었습니다. 고객 수는 289명이네요."
나: "카테고리별로 나눠서 보여줘"
Claude: (query_database 도구 호출 - 자동으로 SQL 생성) "카테고리별 매출을 정리했습니다. 전자제품이 520만원으로 1위..."
Claude가 자연어 질문을 SQL로 변환하고, 내 데이터베이스에서 실제 데이터를 가져옵니다. 이게 MCP의 진짜 힘입니다. 일반 ChatGPT한테는 "내 데이터" 분석을 못 시키지만, MCP 서버를 만들면 Claude가 내 데이터에 직접 접근합니다.
실전 예제: 프로젝트 파일 관리 도구
프로젝트 디렉토리의 파일을 읽고, 검색하고, 구조를 파악하는 도구입니다.
# project_mcp_server.py from mcp.server.fastmcp import FastMCP import os import glob mcp = FastMCP("Project File Manager") # 허용된 프로젝트 디렉토리 (보안) ALLOWED_DIR = "/home/user/projects/my-app" def _validate_path(path): """경로가 허용된 디렉토리 안에 있는지 확인""" abs_path = os.path.abspath(path) if not abs_path.startswith(os.path.abspath(ALLOWED_DIR)): raise ValueError(f"접근 불가: 허용된 디렉토리 밖입니다.") return abs_path @mcp.tool() def list_project_files(directory: str = "", pattern: str = "*") -> str: """프로젝트 디렉토리의 파일 목록을 조회합니다. Args: directory: 하위 디렉토리 경로 (빈 문자열이면 루트) pattern: 파일 패턴 (예: "*.py", "*.ts") """ target = _validate_path(os.path.join(ALLOWED_DIR, directory)) files = glob.glob(os.path.join(target, pattern)) result = [] for f in sorted(files): rel_path = os.path.relpath(f, ALLOWED_DIR) size = os.path.getsize(f) file_type = "DIR" if os.path.isdir(f) else "FILE" result.append(f"[{file_type}] {rel_path} ({size:,} bytes)") return "\n".join(result) if result else "파일 없음" @mcp.tool() def read_file(file_path: str) -> str: """프로젝트 파일의 내용을 읽습니다. Args: file_path: 프로젝트 루트 기준 상대 경로 (예: "src/main.py") """ full_path = _validate_path(os.path.join(ALLOWED_DIR, file_path)) if not os.path.exists(full_path): return f"파일을 찾을 수 없습니다: {file_path}" with open(full_path, "r", encoding="utf-8") as f: content = f.read() # 너무 큰 파일은 잘라서 반환 if len(content) > 10000: content = content[:10000] + f"\n\n... (파일이 너무 큼: 총 {len(content):,}자 중 상위 10,000자만 표시)" return content @mcp.tool() def search_in_files(query: str, file_pattern: str = "*.py") -> str: """프로젝트 파일에서 텍스트를 검색합니다. Args: query: 검색할 텍스트 file_pattern: 검색할 파일 패턴 (예: "*.py", "*.js") """ results = [] search_path = os.path.join(ALLOWED_DIR, "**", file_pattern) for filepath in glob.glob(search_path, recursive=True): try: with open(filepath, "r", encoding="utf-8") as f: for i, line in enumerate(f, 1): if query.lower() in line.lower(): rel_path = os.path.relpath(filepath, ALLOWED_DIR) results.append(f"{rel_path}:{i}: {line.strip()}") except: continue if not results: return f"'{query}'를 찾을 수 없습니다." return f"검색 결과 ({len(results)}건):\n" + "\n".join(results[:50]) if __name__ == "__main__": mcp.run()
이 서버를 연결하면 Claude에게 "이 프로젝트에서 데이터베이스 관련 코드를 찾아줘", "main.py 내용 보여줘", "src 디렉토리 구조 알려줘" 같은 요청이 가능합니다. Claude Code가 하는 것과 비슷하지만, 내가 원하는 범위와 동작을 완전히 통제할 수 있다는 게 장점입니다.
리소스와 프롬프트 추가하기
도구(Tools) 외에 리소스(Resources)와 프롬프트(Prompts)도 추가해봅시다.
# 리소스: AI가 참조할 수 있는 데이터 @mcp.resource("config://app-settings") def get_app_settings() -> str: """앱 설정 정보를 리소스로 제공""" import json settings = { "app_name": "My App", "version": "2.1.0", "database": "SQLite", "framework": "FastAPI", "python_version": "3.12" } return json.dumps(settings, indent=2) # 동적 리소스: URI 템플릿으로 파라미터 받기 @mcp.resource("docs://api/{endpoint}") def get_api_docs(endpoint: str) -> str: """API 엔드포인트별 문서를 리소스로 제공""" docs = { "users": "GET /api/users - 사용자 목록 조회\nPOST /api/users - 사용자 생성", "orders": "GET /api/orders - 주문 목록 조회\nGET /api/orders/{id} - 주문 상세", } return docs.get(endpoint, f"'{endpoint}' 문서를 찾을 수 없습니다.") # 프롬프트 템플릿: 재사용 가능한 프롬프트 @mcp.prompt() def code_review(code: str) -> str: """코드 리뷰 프롬프트 템플릿""" return f"""다음 코드를 리뷰해주세요. 체크리스트: 1. 버그 가능성 2. 성능 이슈 3. 보안 취약점 4. 코드 스타일/가독성 5. 테스트 필요 부분 코드: ``` {code} ```""" @mcp.prompt() def data_analysis(table_name: str) -> str: """데이터 분석 프롬프트 템플릿""" return f"""'{table_name}' 테이블을 분석해주세요. 순서: 1. 먼저 get_table_schema 도구로 테이블 구조를 확인 2. query_database 도구로 기본 통계를 조회 3. 이상치나 패턴을 찾아서 보고 결과는 비전문가도 이해할 수 있게 설명해주세요."""
리소스는 Claude가 문맥으로 참조할 데이터이고, 프롬프트는 미리 만들어둔 질문 템플릿입니다. Claude Desktop에서 "/" 키를 누르면 등록된 프롬프트가 나타나서 바로 사용할 수 있습니다.
디버깅과 테스트
MCP 서버 개발에서 가장 까다로운 부분이 디버깅입니다. 서버가 Claude Desktop 뒤에서 돌아가니까 print 문으로 디버깅이 안 됩니다.
MCP Inspector 사용하기
# MCP Inspector로 서버 테스트 (웹 UI 제공)
npx @modelcontextprotocol/inspector python my_first_mcp_server.py
Inspector를 실행하면 브라우저에서 MCP 서버의 도구 목록을 보고, 직접 파라미터를 넣어서 테스트할 수 있습니다. Claude 없이도 도구가 정상 작동하는지 확인하는 데 필수입니다.
파이썬 코드로 테스트하기
# test_mcp_server.py - 도구 함수 직접 테스트 from my_first_mcp_server import get_current_time, calculate # 도구 함수는 일반 파이썬 함수이므로 직접 호출 가능 print(get_current_time("Asia/Seoul")) print(get_current_time("US/Eastern")) print(calculate("2 + 3 * 4")) print(calculate("100 / 7")) # 엣지 케이스 테스트 print(calculate("import os")) # 거부되어야 함
MCP 도구는 결국 일반 파이썬 함수입니다. 그래서 단위 테스트를 일반 함수처럼 작성할 수 있습니다. 이 점이 MCP SDK의 좋은 설계입니다.
로깅 추가
import logging # 파일로 로그 남기기 (stdout은 MCP 프로토콜이 사용하므로) logging.basicConfig( filename="mcp_server.log", level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger("mcp") @mcp.tool() def query_database(sql: str) -> str: logger.info(f"SQL 실행: {sql}") # ... 기존 코드
print()를 쓰면 안 됩니다. stdout은 MCP 프로토콜 통신에 사용되기 때문에, print가 프로토콜 메시지와 섞이면 서버가 죽습니다. 디버깅은 반드시 파일 로그나 stderr을 사용하세요.
마무리 - MCP 서버의 가능성
직접 MCP 서버를 만들어보니, 생각보다 쉽습니다. 파이썬 함수를 만들고 데코레이터를 붙이면 끝입니다. 어려운 건 "무엇을 만들까"를 결정하는 것이지, 기술적 구현은 간단합니다.
제가 실무에서 만들어서 쓰고 있는 MCP 서버들:
- 회사 DB 조회 서버: Claude에게 "이번 달 매출 요약해줘"라고 말하면 실제 DB에서 데이터를 가져와서 답합니다
- Jira/Linear 연동 서버: "이번 스프린트 남은 태스크 알려줘"로 프로젝트 관리 도구와 연결
- 내부 API 문서 서버: 회사 API 문서를 리소스로 제공해서, Claude가 API 사용법을 정확하게 알려줌
- 로그 분석 서버: 서버 로그 파일을 검색/분석하는 도구를 제공
MCP의 진짜 가치는 AI를 내 업무 환경에 맞춤형으로 연결할 수 있다는 데 있습니다. 범용 AI 도구는 내 회사 데이터를 모릅니다. 하지만 MCP 서버를 만들면 Claude가 내 데이터베이스, 내 프로젝트 코드, 내 문서를 직접 읽고 활용합니다.
그리고 한 번 만든 MCP 서버는 팀원들과 공유할 수 있습니다. 설정 파일에 서버 경로만 추가하면 팀 전체가 같은 도구를 쓸 수 있습니다. 이건 팀 생산성 측면에서 상당한 임팩트입니다.
핵심 정리
- MCP 서버란: AI가 외부 도구/데이터에 접근하는 표준 프로토콜 서버
- 구현 난이도: 파이썬 함수 + @mcp.tool() 데코레이터 = 끝. 생각보다 쉬움
- FastMCP: 타입 힌트와 docstring에서 자동으로 스키마 생성 → 별도 설정 불필요
- 3가지 구성요소: Tools(함수 호출), Resources(데이터 참조), Prompts(템플릿)
- 연동: claude_desktop_config.json에 서버 경로 추가 → Claude Desktop 재시작
- 디버깅: MCP Inspector + 파일 로그 활용 (print 사용 금지)
- 보안: 경로 검증, SELECT만 허용, 허용 디렉토리 제한 등 반드시 적용
- 실전 가치: 내 업무 데이터에 AI를 맞춤 연결 → 팀 전체 생산성 향상
'AI 신기능 분석' 카테고리의 다른 글
| AI 에이전트 프레임워크 비교 - LangGraph vs AutoGen vs CrewAI 실전 테스트 (0) | 2026.04.20 |
|---|---|
| Hugging Face Transformers 입문 - 로컬에서 AI 모델 돌리기 (0) | 2026.04.12 |
| AI 파인튜닝 없이 성능 높이는 법 - Few-shot, CoT, RAG 전략 비교 (0) | 2026.04.09 |
| AI Function Calling 실전 활용법 - GPT & Claude 비교 구현 (0) | 2026.04.09 |
| AI 에이전트 쉽게 이해하기 - 2026년 가장 핫한 AI 트렌드 (0) | 2026.04.06 |