AI 모델이 외부 도구와 데이터를 활용하는 시대가 본격적으로 열렸습니다. Anthropic이 발표한 MCP(Model Context Protocol)는 AI 에이전트가 다양한 외부 시스템과 표준화된 방식으로 소통할 수 있게 해주는 오픈 프로토콜입니다. 이 글에서는 MCP의 핵심 개념부터 실제 서버를 구축하고 Claude Desktop과 연동하는 방법까지 상세하게 다룹니다.

MCP란 무엇인가?

MCP(Model Context Protocol)는 AI 모델과 외부 데이터 소스, 도구를 연결하기 위한 표준화된 통신 프로토콜입니다. 기존에는 각 AI 서비스마다 도구 연동 방식이 달라서 개발자가 플랫폼별로 별도의 통합 코드를 작성해야 했습니다. MCP는 이 문제를 해결하기 위해 하나의 통일된 인터페이스를 제공합니다.

비유하자면, USB가 다양한 주변기기를 하나의 표준 포트로 연결할 수 있게 해준 것처럼, MCP는 AI 모델이 파일 시스템, 데이터베이스, API, 웹 서비스 등 다양한 외부 자원에 하나의 표준으로 접근할 수 있게 해줍니다.

MCP가 등장하기 전의 문제점

핵심 포인트: MCP는 Anthropic이 만들었지만 오픈 표준으로 공개되어 있어, 어떤 AI 모델이든 이 프로토콜을 채택하여 사용할 수 있습니다. 실제로 여러 AI 플랫폼에서 MCP 지원을 확대하고 있습니다.

MCP 아키텍처: Host, Client, Server

MCP는 세 가지 핵심 구성 요소로 이루어져 있습니다. 이 구조를 정확히 이해하는 것이 MCP 활용의 출발점입니다.

Host (호스트)

사용자가 직접 상호작용하는 AI 애플리케이션입니다. Claude Desktop, IDE 플러그인, 커스텀 AI 애플리케이션 등이 Host에 해당합니다. Host는 하나 이상의 MCP Client를 내부에 생성하여 관리합니다.

Client (클라이언트)

Host 내부에서 동작하며, 특정 MCP Server와 1:1로 연결되는 프로토콜 클라이언트입니다. Client는 서버와의 통신을 관리하고, 서버가 제공하는 도구와 리소스 목록을 Host에게 전달합니다.

Server (서버)

실제 외부 데이터나 기능을 노출하는 경량 프로그램입니다. 파일 시스템, 데이터베이스, API 등 특정 기능에 특화된 서버를 만들어 AI 모델이 활용할 수 있게 합니다.

┌─────────────────────────────────────────┐
│              Host (Claude Desktop)        │
│                                          │
│  ┌──────────┐  ┌──────────┐              │
│  │ Client 1 │  │ Client 2 │  ...         │
│  └────┬─────┘  └────┬─────┘              │
│       │              │                    │
└───────┼──────────────┼────────────────────┘
        │              │
   JSON-RPC        JSON-RPC
        │              │
  ┌─────┴─────┐  ┌─────┴─────┐
  │  Server A  │  │  Server B  │
  │ (파일시스템)│  │ (DB 접근)  │
  └───────────┘  └───────────┘

MCP의 핵심 기능 세 가지

MCP 서버는 세 가지 주요 기능을 클라이언트에게 제공할 수 있습니다.

1. Tools (도구)

AI 모델이 호출할 수 있는 함수입니다. 데이터베이스 쿼리, 파일 생성, API 호출 등 실제 작업을 수행합니다. 도구 호출은 항상 모델이 판단하여 요청하고, 결과를 받아 활용합니다.

2. Resources (리소스)

AI 모델에게 컨텍스트를 제공하는 데이터 소스입니다. 파일 내용, 데이터베이스 레코드, 시스템 설정 등을 읽기 전용으로 제공합니다. REST API의 GET 엔드포인트와 유사한 개념입니다.

3. Prompts (프롬프트)

미리 정의된 프롬프트 템플릿으로, 특정 워크플로우에 최적화된 프롬프트를 서버에서 제공할 수 있습니다. 사용자가 슬래시 커맨드처럼 선택하여 활용합니다.

Python으로 MCP 서버 만들기

실제로 MCP 서버를 만들어 보겠습니다. Python의 공식 MCP SDK를 사용하면 간단하게 서버를 구축할 수 있습니다.

프로젝트 설정

# 프로젝트 초기화
mkdir my-mcp-server && cd my-mcp-server

# 가상환경 생성 및 활성화
python -m venv venv
source venv/bin/activate

# MCP SDK 설치
pip install mcp[cli]

간단한 날씨 정보 MCP 서버

# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json
import httpx

app = Server("weather-server")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_weather",
            description="지정한 도시의 현재 날씨 정보를 조회합니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "날씨를 조회할 도시 이름 (예: Seoul, Tokyo)"
                    }
                },
                "required": ["city"]
            }
        ),
        Tool(
            name="get_forecast",
            description="지정한 도시의 3일간 날씨 예보를 조회합니다",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "예보를 조회할 도시 이름"
                    }
                },
                "required": ["city"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "get_weather":
        city = arguments["city"]
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"https://api.weatherapi.com/v1/current.json",
                params={"key": "YOUR_API_KEY", "q": city, "lang": "ko"}
            )
            data = response.json()

        current = data["current"]
        result = {
            "도시": data["location"]["name"],
            "온도": f"{current['temp_c']}°C",
            "체감온도": f"{current['feelslike_c']}°C",
            "상태": current["condition"]["text"],
            "습도": f"{current['humidity']}%",
            "풍속": f"{current['wind_kph']} km/h"
        }
        return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]

    elif name == "get_forecast":
        city = arguments["city"]
        # 예보 API 호출 로직
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"https://api.weatherapi.com/v1/forecast.json",
                params={"key": "YOUR_API_KEY", "q": city, "days": 3, "lang": "ko"}
            )
            data = response.json()

        forecasts = []
        for day in data["forecast"]["forecastday"]:
            forecasts.append({
                "날짜": day["date"],
                "최고기온": f"{day['day']['maxtemp_c']}°C",
                "최저기온": f"{day['day']['mintemp_c']}°C",
                "상태": day["day"]["condition"]["text"]
            })
        return [TextContent(type="text", text=json.dumps(forecasts, ensure_ascii=False, indent=2))]

    raise ValueError(f"알 수 없는 도구: {name}")

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

TypeScript로 MCP 서버 만들기

TypeScript를 선호한다면 공식 TypeScript SDK를 사용할 수 있습니다. Node.js 환경에서 동작하며, 타입 안전성을 보장합니다.

// package.json 의존성 설치
// npm install @modelcontextprotocol/sdk zod

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "database-server",
  version: "1.0.0"
});

// 도구 정의: SQL 쿼리 실행
server.tool(
  "execute_query",
  "데이터베이스에 읽기 전용 SQL 쿼리를 실행합니다",
  {
    query: z.string().describe("실행할 SELECT SQL 쿼리"),
    database: z.string().optional().describe("대상 데이터베이스 이름")
  },
  async ({ query, database }) => {
    // SELECT 문만 허용하는 안전 검증
    if (!query.trim().toUpperCase().startsWith("SELECT")) {
      return {
        content: [{
          type: "text",
          text: "오류: SELECT 쿼리만 실행할 수 있습니다."
        }],
        isError: true
      };
    }

    try {
      const result = await executeQuery(query, database);
      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      };
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `쿼리 실행 오류: ${error.message}`
        }],
        isError: true
      };
    }
  }
);

// 리소스 정의: 테이블 스키마 제공
server.resource(
  "schema://tables",
  "데이터베이스의 전체 테이블 스키마 정보",
  async () => {
    const schema = await getSchemaInfo();
    return {
      contents: [{
        uri: "schema://tables",
        mimeType: "application/json",
        text: JSON.stringify(schema, null, 2)
      }]
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
보안 주의사항: MCP 서버에서 데이터베이스에 접근할 때는 반드시 읽기 전용 쿼리만 허용하거나, 별도의 읽기 전용 데이터베이스 계정을 사용하세요. AI 모델이 의도치 않게 데이터를 수정하거나 삭제하는 것을 방지해야 합니다.

Claude Desktop과 MCP 서버 연동

만든 MCP 서버를 Claude Desktop에 연결하는 방법입니다. 설정 파일 하나만 수정하면 됩니다.

설정 파일 위치

# macOS
~/Library/Application Support/Claude/claude_desktop_config.json

# Windows
%APPDATA%\Claude\claude_desktop_config.json

설정 파일 작성

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/path/to/weather-server/server.py"],
      "env": {
        "WEATHER_API_KEY": "your-api-key-here"
      }
    },
    "database": {
      "command": "node",
      "args": ["/path/to/database-server/dist/index.js"],
      "env": {
        "DB_HOST": "localhost",
        "DB_PORT": "5432",
        "DB_NAME": "myapp"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/jaeseong/projects"
      ]
    }
  }
}

설정 파일을 저장한 후 Claude Desktop을 재시작하면, 채팅 입력창 옆에 도구 아이콘이 나타납니다. 이를 클릭하면 연결된 MCP 서버들과 사용 가능한 도구 목록을 확인할 수 있습니다.

팁: env 필드를 활용하면 API 키 같은 민감한 정보를 코드에 하드코딩하지 않고 설정 파일에서 관리할 수 있습니다. 하지만 이 설정 파일 자체도 안전하게 관리해야 합니다.

주요 MCP 서버 생태계

이미 다양한 공식/커뮤니티 MCP 서버가 제공되고 있어 직접 만들지 않아도 바로 활용할 수 있습니다.

공식 제공 서버

커뮤니티 서버

실무 활용 사례

사례 1: 코드 리뷰 자동화

GitHub MCP 서버와 Filesystem MCP 서버를 조합하면, AI에게 PR의 변경 사항을 분석하고 코드 리뷰 코멘트를 작성하게 할 수 있습니다.

# Claude에게 요청하는 프롬프트 예시
"GitHub의 my-project 리포지토리에서 최신 PR을 확인하고,
변경된 파일들의 코드를 분석해서 잠재적인 버그나
개선 사항을 리뷰 코멘트로 남겨줘."

# Claude는 다음 도구들을 순차적으로 호출합니다:
# 1. github.list_pull_requests - PR 목록 조회
# 2. github.get_pull_request_diff - 변경 사항 확인
# 3. github.create_review_comment - 리뷰 작성

사례 2: 데이터 분석 파이프라인

PostgreSQL MCP 서버를 연결하면 자연어로 데이터베이스를 분석할 수 있습니다.

# Claude에게 요청
"지난 달 가입한 사용자 중에서 첫 주문까지 걸린
평균 시간을 분석하고, 가입 경로별로 비교해줘."

# Claude가 자동으로 생성하여 실행하는 SQL
SELECT
  u.signup_source,
  COUNT(*) as user_count,
  AVG(EXTRACT(EPOCH FROM (o.created_at - u.created_at)) / 3600)
    as avg_hours_to_first_order
FROM users u
JOIN orders o ON u.id = o.user_id
  AND o.id = (
    SELECT MIN(o2.id) FROM orders o2 WHERE o2.user_id = u.id
  )
WHERE u.created_at >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')
  AND u.created_at < DATE_TRUNC('month', CURRENT_DATE)
GROUP BY u.signup_source
ORDER BY avg_hours_to_first_order;

사례 3: 인프라 모니터링

Docker MCP 서버와 커스텀 모니터링 MCP 서버를 조합하면 인프라 상태를 자연어로 관리할 수 있습니다.

# Claude에게 요청
"현재 실행 중인 컨테이너 상태를 확인하고,
메모리 사용량이 80%를 넘는 컨테이너가 있으면 알려줘."

MCP 서버 개발 모범 사례

에러 처리

MCP 서버에서의 에러 처리는 AI 모델이 에러 상황을 이해하고 적절히 대응할 수 있도록 명확한 메시지를 반환하는 것이 중요합니다.

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    try:
        result = await process_request(name, arguments)
        return [TextContent(type="text", text=json.dumps(result))]
    except ValidationError as e:
        # 입력값 검증 오류: 모델이 재시도할 수 있도록 명확한 메시지
        return [TextContent(
            type="text",
            text=f"입력값 오류: {str(e)}. 올바른 형식으로 다시 시도해주세요."
        )]
    except PermissionError:
        return [TextContent(
            type="text",
            text="권한 오류: 해당 리소스에 접근할 권한이 없습니다."
        )]
    except Exception as e:
        # 예상하지 못한 오류도 안전하게 처리
        logger.error(f"Unexpected error in {name}: {e}")
        return [TextContent(
            type="text",
            text="내부 서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요."
        )]

보안 고려사항

중요: MCP 서버는 AI 모델에게 시스템 접근 권한을 부여하는 것이므로, 신뢰할 수 있는 서버만 연결하고, 각 서버가 접근할 수 있는 범위를 최소한으로 제한하는 것이 필수입니다.

MCP의 전송 방식

MCP는 두 가지 전송(transport) 방식을 지원합니다.

Stdio (표준 입출력)

로컬에서 실행되는 서버에 적합합니다. Host가 서버 프로세스를 직접 실행하고, 표준 입출력을 통해 JSON-RPC 메시지를 주고받습니다. Claude Desktop의 기본 전송 방식입니다.

SSE (Server-Sent Events)

원격 서버에 적합한 HTTP 기반 전송 방식입니다. 클라이언트가 HTTP POST로 메시지를 보내고, SSE를 통해 서버의 응답을 스트리밍으로 수신합니다. 여러 클라이언트가 하나의 원격 MCP 서버에 접속하는 구조에 유용합니다.

# SSE 전송을 사용하는 MCP 서버 실행
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route, Mount

sse = SseServerTransport("/messages/")

async def handle_sse(request):
    async with sse.connect_sse(
        request.scope, request.receive, request._send
    ) as streams:
        await app.run(streams[0], streams[1])

starlette_app = Starlette(
    routes=[
        Route("/sse", endpoint=handle_sse),
        Mount("/messages/", app=sse.handle_post_message),
    ]
)

# uvicorn으로 실행: uvicorn server:starlette_app --port 8000
실무 경험 공유: 개인적으로 Claude Desktop에 MCP를 연결해보면서 가능성을 느낀 경험이 있습니다. 파일 시스템이나 간단한 DB 조회용 MCP 서버를 로컬에서 띄워보았는데, AI가 외부 도구와 자연스럽게 연동되는 것을 보고 앞으로의 활용 가능성에 기대가 컸습니다. 아직 업무에 본격적으로 적용하지는 못했지만, 계속 공부하고 있습니다.

MCP를 도입하면서 겪은 시행착오

MCP는 AI 에이전트 생태계의 인프라 역할을 하는 중요한 프로토콜입니다. USB-C가 다양한 기기를 하나의 포트로 연결했듯이, MCP는 AI 모델과 외부 세계를 하나의 표준으로 연결합니다.

다만 도입 과정이 항상 순탄하지는 않았습니다. 초기에는 MCP 서버의 응답 시간이 불안정해서 Claude Desktop이 타임아웃을 내는 경우가 빈번했고, 서버 로그를 꼼꼼히 확인하면서 커넥션 풀 설정과 쿼리 최적화를 반복해야 했습니다. 또한 팀원들이 MCP를 통해 어떤 데이터에 접근했는지 감사 로그를 남기는 것도 보안팀과의 약속이었기 때문에, 별도의 로깅 레이어를 추가하는 작업도 필요했습니다.

이런 시행착오를 거치면서 느낀 점은, MCP 자체는 강력하지만 조직에 도입하려면 기술적 준비만큼이나 보안 정책 수립과 팀 교육이 중요하다는 것입니다. 지금 MCP를 익혀두면 AI 에이전트 개발의 핵심 역량을 미리 확보할 수 있습니다.

Jaeseong
Jaeseong

10년차 풀스택 개발자. Spring Boot, Flutter, AI 등 실무 경험을 기록합니다.

GitHub →

💬 댓글