본문으로 건너뛰기
엔지니어링Mar 28, 2026

첫 번째 MCP 서버 구축: 개발자를 위한 실전 가이드

OS
Open Soft Team

Engineering Team

Model Context Protocol(MCP)이란?

**Model Context Protocol(MCP)**은 Anthropic이 만든 오픈 스탠다드로, AI 애플리케이션이 외부 데이터 소스 및 도구와 통신하는 방법을 정의합니다. MCP를 AI 통합을 위한 USB-C라고 생각하세요. USB-C가 충전, 데이터 전송, 디스플레이 출력을 위한 단일 유니버설 커넥터를 제공하는 것처럼, MCP는 AI 모델을 데이터베이스, API, 파일 시스템 및 기타 서비스에 연결하기 위한 단일 유니버설 프로토콜을 제공합니다. MCP는 모든 AI 애플리케이션과 모든 도구 간의 커스텀 통합 필요성을 제거합니다.

2026년 현재, MCP는 AI-도구 통신의 지배적인 스탠다드가 되었습니다. Gartner는 2026년 말까지 엔터프라이즈 애플리케이션의 40%가 AI 에이전트를 포함할 것이라고 예측하며, MCP는 이를 대규모로 실현 가능하게 만드는 프로토콜입니다. MCP 이전에는 AI 모델을 N개의 도구에 연결하려면 N개의 커스텀 통합이 필요했습니다. M개의 AI 애플리케이션과 N개의 도구가 있으면 M x N개의 통합 어댑터가 필요했습니다. MCP는 이를 M + N으로 줄입니다. 모든 AI 앱이 하나의 MCP client를 구현하고, 모든 도구가 하나의 MCP server를 구현합니다.

MCP 아키텍처: Host, Client, Server, Transport

코드를 작성하기 전에 MCP 아키텍처를 이해하는 것이 필수적입니다. 프로토콜은 네 가지 핵심 역할을 정의합니다:

구성 요소역할예시
Host최종 사용자가 상호작용하는 AI 애플리케이션Claude Desktop, Cursor, VS Code Copilot
ClientHost 내부의 MCP 프로토콜 핸들러Host 애플리케이션에 내장
ServerMCP를 통해 tools, resources, prompts를 노출커스텀 서버(우리가 구축할 것)
TransportClient와 Server 간의 통신 레이어stdio, HTTP+SSE, Streamable HTTP

통신 흐름은 다음과 같이 작동합니다:

  1. Host가 시작되고 구성된 각 서버에 대해 MCP Client를 초기화합니다.
  2. ClientTransport(로컬은 stdio, 원격은 HTTP)를 통해 Server에 연결합니다.
  3. Clientinitialize 요청을 보내 프로토콜 버전과 기능을 협상합니다.
  4. Server가 사용 가능한 tools, resources, prompts로 응답합니다.
  5. AI 모델이 외부 데이터나 작업이 필요할 때, Client가 적절한 서버 메서드를 호출합니다.
  6. Server가 작업을 실행하고 JSON-RPC 2.0 메시지로 결과를 반환합니다.

Tools vs Resources vs Prompts

MCP server는 세 가지 유형의 기능을 노출할 수 있습니다:

  • Tools는 AI 모델이 호출할 수 있는 함수입니다. 구조화된 입력을 받고 구조화된 출력을 반환합니다. 예: query_database(sql: string) 또는 send_email(to: string, subject: string, body: string).
  • Resources는 AI 모델이 읽을 수 있는 데이터입니다. URI로 식별되며 콘텐츠를 반환합니다. 예: file:///path/to/document.md 또는 postgres://localhost/mydb/users.
  • Prompts는 서버가 제공하는 재사용 가능한 프롬프트 템플릿입니다. AI가 서버의 도메인과 상호작용하는 방법을 표준화합니다. 예: Jira MCP server를 위한 summarize_ticket 프롬프트 템플릿.

단계별: TypeScript로 MCP 서버 구축하기

SQLite 데이터베이스에 쿼리를 실행하는 도구를 제공하는 실용적인 MCP 서버를 구축해 봅시다. 이것은 일반적인 사용 사례로, AI 모델에게 애플리케이션 데이터에 대한 안전한 읽기 전용 접근을 제공합니다.

1단계: 프로젝트 초기화

mkdir mcp-sqlite-server && cd mcp-sqlite-server
npm init -y
npm install @modelcontextprotocol/sdk better-sqlite3
npm install -D typescript @types/node @types/better-sqlite3
npx tsc --init

tsconfig.json 설정:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

2단계: 서버 구현

src/index.ts 생성:

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

const db = new Database(process.env.DB_PATH || "./data.db", {
  readonly: true,
});

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

server.tool(
  "query",
  "SQLite 데이터베이스에 대해 읽기 전용 SQL 쿼리 실행",
  {
    sql: z.string().describe("실행할 SQL SELECT 쿼리"),
  },
  async ({ sql }) => {
    const normalized = sql.trim().toUpperCase();
    if (!normalized.startsWith("SELECT")) {
      return {
        content: [{ type: "text", text: "오류: SELECT 쿼리만 허용됩니다." }],
        isError: true,
      };
    }
    try {
      const rows = db.prepare(sql).all();
      return {
        content: [{ type: "text", text: JSON.stringify(rows, null, 2) }],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `쿼리 오류: ${(error as Error).message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "list_tables",
  "데이터베이스의 모든 테이블과 스키마 목록 표시",
  {},
  async () => {
    const tables = db
      .prepare(
        `SELECT name, sql FROM sqlite_master
         WHERE type='table' AND name NOT LIKE 'sqlite_%'
         ORDER BY name`
      )
      .all();
    return {
      content: [{ type: "text", text: JSON.stringify(tables, null, 2) }],
    };
  }
);

3단계: 빌드 및 로컬 테스트

npx tsc
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node dist/index.js

서버의 기능이 포함된 JSON-RPC 응답이 표시됩니다.

4단계: Python으로 같은 서버 구축

Python 개발자를 위해, 공식 MCP Python SDK를 사용한 동일한 구현을 보여드립니다:

import sqlite3
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("sqlite-query")
DB_PATH = "data.db"

@mcp.tool()
def query(sql: str) -> str:
    """SQLite 데이터베이스에 대해 읽기 전용 SQL 쿼리를 실행합니다."""
    normalized = sql.strip().upper()
    if not normalized.startswith("SELECT"):
        raise ValueError("SELECT 쿼리만 허용됩니다.")
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        cursor = conn.execute(sql)
        rows = [dict(row) for row in cursor.fetchall()]
        return json.dumps(rows, indent=2, default=str)
    finally:
        conn.close()

@mcp.tool()
def list_tables() -> str:
    """데이터베이스의 모든 테이블과 스키마를 나열합니다."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        cursor = conn.execute(
            "SELECT name, sql FROM sqlite_master "
            "WHERE type='table' AND name NOT LIKE 'sqlite_%'"
        )
        tables = [dict(row) for row in cursor.fetchall()]
        return json.dumps(tables, indent=2)
    finally:
        conn.close()

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

Claude Desktop에 연결하기

Claude Desktop은 MCP 서버를 네이티브로 지원합니다. 서버를 연결하려면 Claude Desktop 설정 파일을 편집합니다:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "sqlite-query": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
      "env": {
        "DB_PATH": "/absolute/path/to/your/data.db"
      }
    }
  }
}

Claude Desktop을 재시작합니다. 채팅 인터페이스에 사용 가능한 MCP 도구를 나타내는 망치 아이콘이 표시됩니다. 이제 Claude에게 “데이터베이스에 어떤 테이블이 있나요?” 또는 “가입일 기준 상위 10명의 사용자를 보여주세요“와 같이 질문하면 MCP 서버를 사용하여 데이터베이스에 직접 쿼리합니다.

Cursor에 연결하기

Cursor도 MCP 서버를 지원합니다. 프로젝트 루트의 .cursor/mcp.json에 설정을 추가합니다:

{
  "mcpServers": {
    "sqlite-query": {
      "command": "node",
      "args": ["./dist/index.js"],
      "env": {
        "DB_PATH": "./data.db"
      }
    }
  }
}

저장 후 Cursor를 재시작합니다. MCP 도구가 AI 어시스턴트 패널에 표시되며 코드 생성 및 디버깅 세션 중에 호출할 수 있습니다.

MCP 서버 테스트 및 디버깅

MCP Inspector 사용하기

MCP Inspector는 공식 디버깅 도구입니다. 서버와 상호작용하기 위한 웹 인터페이스를 제공합니다:

npx @modelcontextprotocol/inspector node dist/index.js

http://localhost:5173에서 브라우저 인터페이스가 열리며 다음을 수행할 수 있습니다:

  • 등록된 모든 tools, resources, prompts 조회
  • 커스텀 입력으로 도구를 호출하고 응답 검사
  • JSON-RPC 메시지 스트림을 실시간으로 모니터링
  • 잘못된 요청을 보내 오류 처리 테스트

일반적인 디버깅 문제

문제원인해결책
서버가 Claude Desktop에 나타나지 않음설정 경로 또는 JSON 구문 오류JSON 검증, 절대 경로 확인
“Tool not found” 오류연결 전에 도구가 등록되지 않음server.connect() 호출 전에 도구 등록
도구 호출 시 타임아웃진행 없는 장시간 작업server.sendProgress()로 진행 알림 추가
stderr 출력이 프로토콜 손상console.log가 stdout에 기록(stdio 트랜스포트)stdio에서는 로깅에 console.error() 사용
유휴 후 연결 끊김트랜스포트 타임아웃하트비트 구현 또는 HTTP 트랜스포트 사용

프로덕션 모범 사례

  1. 입력 검증: 항상 도구 입력을 검증하고 살균합니다. TypeScript에서는 Zod 스키마, Python에서는 Pydantic 모델을 사용하여 엄격한 타입 검사를 수행합니다.

  2. 기본 읽기 전용: 읽기 전용 접근으로 시작합니다. 쓰기 기능은 정말 필요한 경우에만 추가하고, 파괴적 작업에는 항상 확인을 요청합니다.

  3. 오류 처리: isError: true로 구조화된 오류 메시지를 반환합니다. 내부 스택 트레이스나 데이터베이스 연결 문자열을 노출하지 마세요.

  4. 로깅: 모든 도구 호출을 타임스탬프, 입력, 실행 시간과 함께 기록합니다. stdio 트랜스포트 사용 시 로깅에 stderr(stdout이 아닌)을 사용합니다.

  5. 속도 제한: 제어되지 않는 AI 루프가 백엔드 서비스를 과부하시키는 것을 방지하기 위해 도구별 속도 제한을 구현합니다.

  6. 타임아웃: 모든 도구 핸들러에 실행 타임아웃을 설정합니다. AI 모델이 비용이 많이 드는 쿼리를 트리거하는 도구를 호출할 수 있으므로 인프라를 보호하세요.

  7. 환경 분리: 모든 설정에 환경 변수를 사용합니다. 데이터베이스 URL, API 키, 파일 경로를 하드코딩하지 마세요.

  8. 버전 관리: MCP 서버에 시맨틱 버전 관리를 사용합니다. initialize 핸드셰이크에는 버전 협상이 포함되며, 호환성을 깨는 변경에는 메이저 버전 범프가 필요합니다.

FAQ

Q: MCP와 function calling의 차이점은 무엇인가요? A: Function calling(OpenAI, Anthropic 등이 사용)은 각 API 요청에 인라인으로 도구를 정의합니다. MCP는 도구 정의를 독립 서버로 외부화하여 MCP 호환 호스트가 발견하고 사용할 수 있게 합니다. Function calling은 요청별이고, MCP는 스테이트풀 세션이 있는 영구적인 프로토콜입니다.

Q: Claude 외의 모델에서도 MCP를 사용할 수 있나요? A: 네. MCP는 오픈 프로토콜입니다. OpenAI, Google DeepMind, Microsoft는 2026년 초부터 플랫폼에 MCP 지원을 채택했습니다. MCP client를 구현하는 모든 AI 애플리케이션은 어떤 MCP server에도 연결할 수 있습니다.

Q: MCP는 로컬 도구 전용인가요? A: 아닙니다. stdio 트랜스포트는 로컬 서버용으로 설계되었지만, HTTP+SSE 및 Streamable HTTP 트랜스포트는 원격 MCP 서버를 지원합니다. MCP 서버를 네트워크를 통해 접근 가능한 클라우드 서비스로 배포할 수 있습니다.

Q: MCP는 인증을 어떻게 처리하나요? A: 원격 서버에는 OAuth 2.0을 지원합니다. 로컬 stdio 서버는 호스트 프로세스의 보안 컨텍스트를 상속합니다. 엔터프라이즈 배포에서는 MCP 게이트웨이가 인증 및 권한 부여를 중앙화할 수 있습니다.

Q: MCP 서버를 구축하는 데 어떤 언어를 사용할 수 있나요? A: 공식 SDK는 TypeScript, Python, Java, Kotlin, C#, Swift에서 사용 가능합니다. 커뮤니티 SDK에는 Rust, Go, Ruby, PHP가 포함됩니다. 프로토콜은 언어에 구애받지 않으며, stdio 또는 HTTP를 통해 JSON-RPC를 읽고 쓸 수 있는 모든 언어로 MCP 서버를 구현할 수 있습니다.

Q: 호스트를 재시작하지 않고 MCP 서버를 업데이트하는 방법은? A: MCP는 기능 변경 알림을 지원합니다. 서버의 도구가 변경되면 서버가 notifications/tools/list_changed 메시지를 보내 client가 도구 목록을 다시 가져오도록 합니다. stdio 서버의 경우 보통 호스트를 재시작해야 합니다. HTTP 서버는 호스트 재시작 없이 업데이트할 수 있습니다.