MCP Server 처음부터 만들기
AI 도구 호출 완전 개발 가이드

Claude / GPT / Cursor가 채팅만 되고 데이터베이스 조회, 파일 읽기, API 호출은 불가능하다면 필요한 것은 더 긴 Prompt가 아니라 재사용 가능한 도구 계층입니다. 본 글은 백엔드·풀스택 개발자를 대상으로 Hello World부터 ChromaDB 지식 베이스 MCP Server까지, Tools / Resources / Prompts 세 가지 핵심 기능, stdio와 HTTP+SSE 이중 전송, 디버깅·테스트·Docker 프로덕션 배포를 다룹니다. 읽기를 마치면 Cursor에서 바로 호출 가능한 커스텀 도구를 갖게 되며, Server를 원격 Mac 7×24 상주로 옮기는 방법도 이해할 수 있습니다(프로토콜 배경은 MCP 프로토콜 해설 참고).

01

AI에 MCP Server가 필요한 이유: 도구 없는 LLM은 '말만 하는 두뇌'일 뿐입니다

대규모 언어 모델의 학습 데이터에는 시한이 있어 CRM, Git 저장소, 사내 API에 접근할 수 없습니다. 2024년 이전에는 Claude용 Function Calling, GPT용 Plugins, Cursor용 별도 형식을 각각 작성해야 했고, 모델을 바꾸면 처음부터 다시 구축해야 했습니다. MCP Server는 '도구 역량'을 독립 프로세스로 캡슐화하여 한 번 작성하면 Claude Desktop / Cursor / Gemini 모두에서 사용할 수 있게 합니다.

대표 시나리오: Claude Desktop에서 AI가 Postgres 매출 데이터를 조회하고, Cursor에서 Agent가 프로젝트 문서를 읽고 코드를 수정하며, GPT가 HTTP MCP로 사내 티켓 시스템을 호출합니다. 기반은 모두 동일한 Server입니다.

본 글의 가치: 개념 설명이 아니라 say_hello에서 벡터 검색이 포함된 프로덕션급 지식 베이스 Server까지 직접 구축합니다. 대상 독자: Python 또는 TypeScript 기초가 있고 IDE·Desktop에서 AI 역량을 확장하려는 개발자입니다.

여섯 가지 핵심 과제: 'API를 직접 래핑'만으로는 부족한 이유

  1. 01

    모델 벤더 종속: OpenAI Function Calling과 Claude Tool Use 형식이 달라 벤더를 바꿀 때마다 어댑터를 재작성해야 합니다.

  2. 02

    도구 발견 불가: REST API는 정적 문서에 의존하며, AI가 런타임에 tools/list로 역량 목록을 자율 탐색할 수 없습니다.

  3. 03

    IDE 간 비호환: Cursor, VS Code 확장, JetBrains 플러그인의 도구 정의를 공유할 수 없어 N×M 통합을 유지해야 합니다.

  4. 04

    컨텍스트·데이터 단절: LLM이 설정 파일, 사용자 선호, 실시간 로그를 안정적으로 읽으려면 표준화된 Resources 읽기 전용 채널이 필요합니다.

  5. 05

    Prompt 템플릿 분산: Code Review, Incident 보고 등 재사용 템플릿에 통합 등록 메커니즘이 없어 팀마다 복사·붙여넣기를 반복합니다.

  6. 06

    로컬 vs 원격 배포 혼란: stdio 자식 프로세스는 개발에 적합하지만, 프로덕션 HTTP 게이트웨이·인증·모니터링에 통일된 패턴이 부족합니다(stdio 자식 프로세스 거버넌스 참고).

「AI에 MCP Server를 연결하는 것은 개발자에게 IDE 플러그인을 설치하는 것과 같습니다. 역량 경계가 '채팅'에서 '실제 세계 조작'으로 즉시 확장됩니다.」

02

MCP란 무엇인가: Function Calling에서 개방형 프로토콜로

진화 경로는 Function Calling(2023)ChatGPT PluginsMCP(2024.11, Anthropic 오픈소스)입니다. Anthropic은 MCP를 AI와 외부 세계를 연결하는 'USB-C'로 설계했습니다. Host(Cursor/Claude Desktop)에 MCP Client가 내장되어 MCP Server와 1:1 세션을 수립합니다.

아키텍처: Client / Server와 세 가지 핵심 역량

  • Tools(도구): AI가 호출할 수 있는 부수 효과가 있는 작업 — DB 조회, 파일 쓰기, HTTP 요청 등.
  • Resources(리소스): 읽기 전용 컨텍스트 — 설정, 문서 조각, 사용자 프로필, URI로 주소 지정.
  • Prompts(프롬프트 템플릿): code review, incident postmortem 등 사전 정의된 다중 턴 대화 골격.

기반 통신은 JSON-RPC 2.0입니다: initializetools/list / tools/callresources/read. 전송 계층에는 두 가지 수명 주기가 있습니다.

  • stdio: Host가 Server를 자식 프로세스로 시작하고 stdin/stdout으로 JSON-RPC를 교환합니다. 프로세스 종료 시 세션도 종료됩니다.
  • HTTP + SSE: Client가 원격 URL에 연결하고 Server가 이벤트 스트림을 푸시합니다. 팀 공유와 수평 확장에 적합합니다.

전체 사양은 공식 사이트 modelcontextprotocol.io에서 확인할 수 있습니다.

MCP vs OpenAI FC vs LangChain Tools

차원MCPOpenAI Function CallingLangChain Tools
개방성크로스 벤더 개방 프로토콜, AAIF 거버넌스OpenAI API에 종속프레임워크 내부 추상화, 전송 표준 아님
발견 메커니즘런타임 tools/list요청 내 functions 배열코드 등록, 표준 발견 없음
읽기 전용 데이터Resources + URI scheme일급 시민 없음Retriever 개념, 프로토콜급 아님
Prompt 템플릿Prompts 표준 인터페이스없음PromptTemplate 클래스
전송stdio / HTTP+SSE / Streamable HTTPHTTPS API 일체형Agent 런타임에 따라 다름
재사용성동일 Server로 Cursor + Claude + Gemini 서비스OpenAI 생태계만프레임워크 간 바인딩 재작성 필요
03

개발 환경 구축: Python FastMCP vs TypeScript SDK

두 가지 주류 경로가 있습니다: Python mcp + FastMCP(데이터·스크립트 친화)와 TypeScript @modelcontextprotocol/sdk(Web/API 통합·타입 안전). SDK 소스: python-sdk, typescript-sdk.

bash
# Python 경로
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]" httpx pydantic

# TypeScript 경로
npm init -y && npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node

권장 프로젝트 구조

tree
my-mcp-server/
├── pyproject.toml          # 또는 package.json
├── src/
│   ├── server.py           # FastMCP 진입점
│   ├── tools/              # 각 도구 모듈
│   ├── resources/          # Resource 제공자
│   └── prompts/            # Prompt 템플릿
├── tests/
│   └── test_tools.py       # pytest + ClientSession
├── Dockerfile
└── README.md

환경 준비 6단계 체크리스트

  1. 01

    언어 스택 선택: 데이터·ML 팀은 Python 우선, Node 풀스택은 TypeScript를 선택합니다.

  2. 02

    가상 환경 생성 및 의존성 고정: pip freeze 또는 package-lock.json으로 schema 드리프트를 방지합니다.

  3. 03

    MCP Inspector 설치: npx @modelcontextprotocol/inspector로 JSON-RPC를 시각적으로 디버깅합니다.

  4. 04

    Claude Desktop 설정: ~/Library/Application Support/Claude/claude_desktop_config.json을 편집해 Server command/args를 추가합니다.

  5. 05

    Cursor 설정: Settings → MCP → Add Server에서 stdio에 python -m src.server 또는 절대 경로를 입력합니다.

  6. 06

    Inspector 연결 확인: Server 시작 → Inspector 연결 → tools/list가 비어 있지 않은지 확인합니다.

json
// Cursor / Claude Desktop MCP 설정 예시
{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["-m", "src.server"],
      "env": { "API_KEY": "your-key" }
    }
  }
}
04

Hello World: 30초 만에 첫 MCP Server 실행하기

FastMCP로 say_hello 도구를 작성하여 코드 → Inspector → Cursor 전체 경로를 검증합니다.

python
# src/server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("hello-server")

@mcp.tool()
def say_hello(name: str = "World") -> str:
    """지정 대상에게 인사"""
    return f"Hello, {name}! MCP is working"

if __name__ == "__main__":
    mcp.run()  # 기본 stdio 전송
bash
# Inspector로 디버깅
npx @modelcontextprotocol/inspector python -m src.server

# 또는 stdio로 직접 시작
python -m src.server

Cursor에 동일한 command를 추가한 뒤 Agent에게 「say_hello로 NodeMini에 인사해 줘」라고 요청합니다. JSON 결과가 반환되면 Client ↔ Server 핸드셰이크가 성공한 것입니다.

info

참고: FastMCP는 함수 docstring과 타입 어노테이션에서 JSON Schema를 자동 생성하므로 파라미터 설명을 수동 작성할 필요가 없습니다.

05

Tools 심화 실습: Schema부터 다섯 가지 프로덕션급 도구까지

Tool은 MCP의 핵심 역량입니다. AI는 tools/call로 부수 효과가 있는 작업을 실행합니다. 각 Tool은 이름, 설명, inputSchema를 노출해야 하며, FastMCP는 Pydantic 모델로 검증을 강화합니다.

python
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    query: str = Field(..., description="검색 키워드")
    limit: int = Field(10, ge=1, le=100, description="반환 건수")

@mcp.tool()
async def search_docs(params: SearchInput) -> str:
    """문서 라이브러리에서 검색"""
    results = await index.search(params.query, params.limit)
    return json.dumps(results, ensure_ascii=False)

다섯 가지 자주 쓰는 Tool 패턴

  • calculator: 안전한 eval 또는 ast.literal_eval 사용, exec 금지.
  • file_read / file_write: 루트 디렉터리 화이트리스트로 경로 순회 공격 방지.
  • fetch_url(async): httpx 비동기 요청, timeout·도메인 화이트리스트 설정.
  • db_query: 읽기 전용 SQL + 파라미터화 쿼리, DDL 금지.
  • get_current_time: ISO8601 타임존 시간 반환, LLM 시간 환각 해소.
python
import httpx
from datetime import datetime, timezone

@mcp.tool()
async def fetch_url(url: str) -> str:
    """HTTP GET으로 URL 콘텐츠 조회(도메인 화이트리스트 내)"""
    allowed = ("api.github.com", "nodemini.com")
    if not any(url.startswith(f"https://{d}") for d in allowed):
        raise ValueError(f"Domain not allowed: {url}")
    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.get(url)
        resp.raise_for_status()
        return resp.text[:8000]

@mcp.tool()
def get_current_time() -> str:
    """현재 UTC 시간 반환"""
    return datetime.now(timezone.utc).isoformat()

에러 처리 모범 사례

  • 구조화된 에러 반환: raise ValueError("human-readable msg")를 사용하면 Client가 message를 LLM에 전달합니다.
  • 재시도 가능 vs 치명적 구분: 네트워크 타임아웃은 retryable로 표시, 권한 거부는 즉시 fail.
  • 로그와 마스킹: tool 이름·소요 시간만 기록하고 API Key·PII 전체는 로그하지 않습니다.
  • 멱등 설계: 쓰기 작업에 idempotency_key를 포함해 Agent 중복 호출로 인한 더티 데이터를 방지합니다.
06

Resources: 읽기 전용 컨텍스트와 URI 스키마

Tool vs Resource: Tool은 부수 효과가 있고 AI가 능동 호출합니다. Resource는 읽기 전용 컨텍스트로, Host가 대화 전에 주입하거나 AI가 resources/read로 가져옵니다. URI scheme은 config://, user://, file:// 등으로 자유롭게 정의합니다.

python
@mcp.resource("config://app/settings")
def app_settings() -> str:
    """정적 앱 설정(text/plain)"""
    return open("config/settings.json").read()

@mcp.resource("user://{user_id}/profile")
def user_profile(user_id: str) -> str:
    """동적 사용자 프로필(application/json)"""
    return json.dumps(get_user(user_id))

Resource 콘텐츠 유형

MIME 유형용도예시
text/plain로그, READMEfile://logs/app.log
application/json설정, API 응답config://env
application/octet-stream바이너리(base64)PDF 요약
text/event-stream실시간 구독로그 tail, metrics 스트림

파일 시스템 Resource Server 패턴: resources/list로 디렉터리 스캔, resources/read로 URI 읽기, resources/subscribe로 watchfiles 변경 감지·푸시 — 코드베이스 문서를 Cursor Agent에 노출하는 데 적합합니다.

07

Prompts: 재사용 가능한 다중 턴 대화 템플릿

MCP Prompt는 Server에 등록된 대화 골격입니다. Client는 prompts/get으로 메시지 목록을 가져오며, user / assistant 역할과 파라미터 자리표시자를 포함할 수 있습니다. 팀이 Code Review, Incident 절차를 공유할 때 개인 Prompt 파일을 유지할 필요가 없습니다.

python
from mcp.types import PromptMessage, TextContent

@mcp.prompt()
def code_review_prompt(language: str = "python") -> list[PromptMessage]:
    """표준화된 Code Review 다중 턴 템플릿"""
    return [
        PromptMessage(role="user", content=TextContent(
            type="text",
            text=f"당신은 시니어 {language} 엔지니어입니다. 보안·성능·가독성 세 축으로 아래 diff를 검토해 주세요."
        )),
        PromptMessage(role="assistant", content=TextContent(
            type="text",
            text="diff를 붙여넣거나 PR 번호를 지정해 주세요. CHECKLIST에 따라 구조화된 review를 출력하겠습니다."
        )),
    ]

다중 턴 템플릿에 {ticket_id}, {severity} 등 변수를 중첩할 수 있으며, Server 측에서 버전을 통합 관리하면 Client가 Server를 업그레이드할 때 팀 전체가 최신 review 기준을 동기화합니다.

08

HTTP 전송: stdio에서 Streamable HTTP 프로덕션 배포까지

차원stdioHTTP + SSE / Streamable HTTP
배포로컬 자식 프로세스, Host가 시작독립 서비스, URL 연결
확장단일 머신, 수평 확장 어려움로드 밸런싱, 다중 복제본
인증Host 환경 변수에 의존Bearer Token / API Key / mTLS
디버깅Inspector 직접 연결curl + SSE 클라이언트
적합 용도개인 개발, Cursor 로컬팀 공유, SaaS 통합
python
# Streamable HTTP 모드(FastMCP 2026)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("prod-server", host="0.0.0.0", port=8080)

# ... tools 등록 ...

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

프로덕션 필수 항목: Bearer Token 검증 미들웨어, CORS 화이트리스트(자사 Host 도메인만 허용), rate limit(예: 100 req/min/IP), HTTPS는 리버스 프록시에서 종료. 원격 게이트웨이 운영 세부 사항은 HTTP 게이트웨이 거버넌스를 참고하세요.

warning

주의: HTTP MCP를 인증 없이 공개망에 노출하지 마세요. 2026년에도 미인증 노출 Server가 다수 발견되므로 반드시 인증과 IP 제한을 적용해야 합니다.

09

디버깅과 테스트: Inspector + pytest 단위 테스트

MCP Inspector는 공식 시각 디버거입니다. stdio 또는 URL에 연결한 뒤 tools/list, tools/call을 수동 전송하고 JSON-RPC 왕복을 확인할 수 있어 Cursor 로그만으로 추측하는 것보다 훨씬 효율적입니다.

python
# tests/test_tools.py
import pytest
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

@pytest.mark.asyncio
async def test_say_hello():
    params = StdioServerParameters(command="python", args=["-m", "src.server"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("say_hello", {"name": "MCP"})
            assert "MCP" in result.content[0].text

흔한 오류 대조표

현상원인수정
Server 시작 직후 종료stdout이 print로 오염됨로그는 stderr로, stdout print 금지
tools/list가 비어 있음데코레이터 미등록 또는 import 순서 오류@mcp.tool()run() 전에 실행되도록 확인
Cursor disconnected 표시command 경로 오류 또는 venv 미활성절대 경로 사용, config에 python 전체 경로 기입
JSON-RPC parse error비 JSON 출력이 stdio에 혼입debug banner 비활성, 라이브러리 로그 레벨 WARNING+
10

프로덕션 배포: Docker, 클라우드와 관측 가능성

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir .
COPY src/ src/
EXPOSE 8080
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
CMD ["python", "-m", "src.server", "--transport", "streamable-http"]

배포 플랫폼 선택: Railway / Render는 빠른 검증에 적합하고, AWS ECS / GCP Cloud Run은 엔터프라이즈 컴플라이언스에, VPS + Docker Compose는 비용이 가장 낮지만 패치를 직접 관리해야 합니다.

인용 가능한 프로덕션 파라미터(EEAT)

  • 프로토콜 버전: MCP 2025-03-26 spec부터 Streamable HTTP 지원. Client 핸드셰이크 시 initializeprotocolVersion을 협상하며, Server는 호환 범위를 선언해야 합니다.
  • 리소스 기준선: 경량 Tool Server 약 128MB RAM. ChromaDB 벡터 인덱스 포함 시 2GB RAM 이상·SSD 영구 볼륨 권장.
  • 관측 가능성 스택: 구조화 JSON 로그 → Loki/CloudWatch. Prometheus /metrics(tool 호출 QPS, P99 지연). Sentry로 미처리 예외 캡처. /health는 K8s liveness용.
11

실전 프로젝트: ChromaDB 지식 베이스 MCP Server

사내 Wiki / Markdown 문서를 벡터화하고 index_document, search_knowledge, write_note 세 Tool을 노출하면 Cursor Agent가 「회사 지식 베이스를 조회한 뒤 코드 작성」 워크플로를 실행할 수 있습니다.

요구사항: 증분 인덱싱(watchfiles로 docs/ 감시), 시맨틱 검색 Top-K, 선택적 scratchpad 쓰기.

python
import chromadb
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction

client = chromadb.PersistentClient(path="./data/chroma")
collection = client.get_or_create_collection(
    "wiki", embedding_function=SentenceTransformerEmbeddingFunction()
)

@mcp.tool()
def search_knowledge(query: str, top_k: int = 5) -> str:
    """내부 지식 베이스 시맨틱 검색"""
    hits = collection.query(query_texts=[query], n_results=top_k)
    return json.dumps(hits["documents"][0], ensure_ascii=False)

@mcp.tool()
def index_document(path: str) -> str:
    """단일 Markdown 파일 인덱싱"""
    text = open(path).read()
    collection.upsert(ids=[path], documents=[text], metadatas=[{"path": path}])
    return f"Indexed: {path}"

Cursor 데모 쿼리: 「지식 베이스에서 MCP HTTP 배포 관련 문서를 검색하고 3단계 상용화 체크리스트를 요약해 줘」 — Agent가 먼저 search_knowledge를 호출한 뒤 검색 결과를 바탕으로 답변합니다. 대규모 환경에서는 Qdrant(원격 gRPC)로 벡터 DB를 교체할 수 있습니다.

12

생태계, 2026 트렌드와 학습 경로

공식·커뮤니티에 즉시 사용 가능한 Server가 많아 매번 처음부터 만들 필요는 없습니다.

  • mcp-server-filesystem — 샌드박스 파일 읽기·쓰기
  • mcp-server-github — PR/Issue API
  • brave-search / postgres / slack — 검색, SQL, 팀 협업

2026 트렌드: MCP Marketplace 등장, OAuth 2.1 도구 인증이 spec 로드맵에 편입, Streamable HTTP가 순수 SSE를 점진 대체. 학습 경로 체크리스트: ① spec 읽기 → ② Hello World → ③ Tool 3개 작성 → ④ Resource 추가 → ⑤ pytest → ⑥ Docker 배포 → ⑦ Cursor 연동.

요약

say_hello에서 ChromaDB 지식 베이스까지, MCP Server 풀스택 역량을 습득했습니다: Tools 실행, Resources 컨텍스트, Prompts 템플릿, 이중 전송, 테스트·프로덕션 운영. 다음 단계는 커뮤니티 Server를 fork하거나 사내 API를 팀 표준 도구 계층으로 캡슐화하는 것입니다.

순수 로컬 stdio는 개인 실험에 적합하지만, 다중 Server 병렬 실행, 벡터 인덱스 상주, HTTP 장기 연결은 16GB 노트북에서 빈번한 swap을 유발합니다. 저렴한 Linux VPS는 macOS 전용 도구 체인을 실행하기 어렵습니다. 자체 HTTP 게이트웨이에 session affinity·인증이 없으면 연결 누수·미인증 노출이 발생하기 쉬워 장기 안정성이 기대에 못 미칩니다.

MCP를 프로덕션 인프라로 운영하면서 Cursor Agent와 iOS/macOS CI를 병행하는 팀이라면, MCP Server를 독점 가능한 클라우드 Mac에서 7×24 실행하는 것이 로컬 노트북이나 범용 VM보다 통제 가능성이 높습니다. NodeMini Mac Mini 클라우드 임대는 MCP + Agent 실행층으로 활용할 수 있으며, 하위 LLM을 교체해도 SSH 노드와 Server 설정은 그대로 유지됩니다. 사양은 임대 가격 안내를, 연동 절차는 고객센터를 참고하세요.

FAQ

자주 묻는 질문

Python FastMCP는 가장 빠르게 시작할 수 있어 데이터·스크립트형 도구에 적합합니다. TypeScript SDK는 타입 안전성과 Node 생태계 통합에 강점이 있습니다. 두 스택 모두 프로토콜이 완전히 호환됩니다. 7×24로 여러 Server를 운영하려면 임대 가격 안내에서 원격 Mac 사양을 확인하세요.

Function Calling은 OpenAI에 종속됩니다. MCP는 Claude/GPT/Gemini/Cursor를 아우르는 개방형 프로토콜로 Resources와 Prompts를 지원합니다. 배경은 MCP 프로토콜 해설을 참고하세요.

경량 stdio는 로컬에서 실행 가능합니다. 다중 Server + 벡터 DB + HTTP 장기 연결은 독점 원격 Mac을 권장합니다. 연동 절차는 고객센터를, 운영은 stdio 자식 프로세스 거버넌스를 참고하세요.