Fall in IT.

MCP란 무엇인가 — 개념부터 동작 흐름까지 본문

Artificial Intelligence

MCP란 무엇인가 — 개념부터 동작 흐름까지

D.Y 2026. 6. 6. 18:13
반응형

MCP가 왜 필요한가

LLM은 본질적으로 텍스트를 받아서 텍스트를 뱉는 기계다. 아무리 똑똑해도 스스로는 아무것도 할 수 없다. 파일을 읽거나, DB에 접속하거나, 외부 API를 호출하는 것 전부 불가능하다.

 

그래서 등장한 것이 도구(Tool) 개념이다.

LLM이 "나는 이 도구를 써야겠다"는 신호를 보내면, 외부 시스템이 실제로 실행하고 결과를 돌려주는 구조다.

그런데 문제가 있었다. 도구마다 연결 방식이 제각각이었다. Slack API는 이렇게, GitHub API는 저렇게, PostgreSQL은 또 다르게 통합할 때마다 새로 만들어야 했다.

 

MCP(Model Context Protocol) 는 이 문제를 해결한다. Anthropic이 만든 표준화된 도구 통신 프로토콜이다.

어떤 외부 시스템이든 MCP 서버로 감싸면, Claude는 동일한 방식으로 연결할 수 있다.

 

MCP의 핵심 구조

MCP는 세 가지 레이어로 이루어진다.

┌─────────────────────────────────┐
│         Claude LLM              │  ← 판단하는 두뇌
│    (tool_use 블록 생성)           │
└──────────────┬──────────────────┘
               │
┌──────────────▼──────────────────┐
│     Claude Code 런타임           │  ← 연결하는 몸통
│  (tool_use 감지 + MCP 관리)       │
└──────────────┬──────────────────┘
               │  JSON-RPC
┌──────────────▼──────────────────┐
│         MCP 서버                 │  ← 실제로 실행하는 손발
│  (외부 시스템과 실제 통신)           │
└─────────────────────────────────┘

각 레이어가 무엇을 하는지 명확히 구분하는 것이 MCP를 이해하는 출발점이다.

Claude LLM — 판단하는 두뇌

어떤 도구를 써야 하는지 결정한다. 실제로 실행하지는 않는다. "이 작업은 DB 조회가 필요하다, query 도구를 호출하겠다"는 신호를 tool_use 블록으로 출력할 뿐이다.

Claude Code 런타임 — 연결하는 몸통

터미널에서 claude 명령어를 실행했을 때 돌아가는 프로그램 전체다. 세 가지 역할을 한다.

  • 중계자: 사용자 입력을 Anthropic API로 전달하고, 응답을 터미널에 출력
  • 감지기: LLM 응답에서 tool_use 블록을 발견하면 도구 실행 신호로 인식
  • MCP 관리자: 설정된 MCP 서버들을 시작하고, 도구 호출 시 JSON-RPC 요청을 전달

LLM은 두 번 등장하지만, 런타임은 처음부터 끝까지 모든 단계에 관여한다.

MCP 서버 — 실제로 실행하는 손발

특정 외부 시스템과의 실제 통신을 담당하는 별도 프로세스다. PostgreSQL MCP 서버라면 SQL을 실행하고, Notion MCP 서버라면 페이지를 읽고 쓰고, GitHub MCP 서버라면 이슈를 조회한다. 모두 동일한 MCP 프로토콜로 Claude와 통신한다.

 

동작 흐름 — 사용자 입력부터 결과까지

예시로 PostgreSQL MCP가 연결된 환경에서 "사용자 정보 조회해줘"라고 입력한 상황을 따라가본다. 흐름은 어떤 MCP를 쓰든 동일하다.

1단계 — 사용자 입력

"사용자 정보 조회해줘"

자연어다. 어떤 테이블인지, 어떤 조건인지 지정하지 않았다. Claude가 이 의도를 해석해야 한다.

2단계 — LLM 추론 (첫 번째 LLM 호출)

Claude Code 런타임이 사용자 입력을 Anthropic API로 전달한다. 이때 시스템 프롬프트에 사용 가능한 MCP 도구 목록이 포함된다. Claude는 이 목록을 보고 판단한다.

사용 가능한 도구:
- postgresql_list_tables: 테이블 목록 조회
- postgresql_query: SQL 실행
- postgresql_describe_table: 테이블 구조 확인

판단: DB 조회가 필요 → postgresql_query 도구를 쓰겠다

3단계 — tool_use 블록 생성

LLM의 응답이 일반 텍스트가 아닌 tool_use 블록으로 나온다.

{
  "type": "tool_use",
  "id": "toolu_01abc...",
  "name": "postgresql_query",
  "input": {
    "query": "SELECT * FROM users LIMIT 20"
  }
}

Claude가 직접 SQL을 실행하는 게 아니다. "이 도구를 이 파라미터로 실행하라"는 요청서를 작성한 것이다.

4단계 — 런타임이 tool_use 감지 후 MCP 서버 호출

Claude Code 런타임이 tool_use 블록을 감지한다. 그리고 해당 MCP 서버에 JSON-RPC 프로토콜로 요청을 보낸다.

// 런타임 → MCP 서버
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "postgresql_query",
    "arguments": {
      "query": "SELECT * FROM users LIMIT 20"
    }
  }
}

MCP 서버는 로컬에서 돌고 있는 별도 프로세스다. stdio(표준 입출력) 또는 SSE(Server-Sent Events) 방식으로 런타임과 통신한다.

5단계 — MCP 서버가 외부 시스템과 실제 통신

MCP 서버가 요청을 받아 실제 작업을 수행한다. PostgreSQL 예시라면 pg 드라이버로 DB에 접속해 SQL을 실행한다.

MCP 서버 → PostgreSQL: SELECT * FROM users LIMIT 20
PostgreSQL → MCP 서버: [{id:1, name:"홍길동", ...}, ...]

중요한 포인트: Claude는 외부 시스템에 직접 접근하지 않는다. MCP 서버가 항상 중간에서 통역사 역할을 한다.

6단계 — 결과를 tool_result로 포장해 LLM에 전달

MCP 서버의 응답이 Claude Code 런타임으로 돌아온다. 런타임은 이것을 tool_result 블록으로 포장해 LLM의 컨텍스트에 넣는다.

{
  "type": "tool_result",
  "tool_use_id": "toolu_01abc...",
  "content": [
    {
      "type": "text",
      "text": "[{\"id\":1,\"name\":\"홍길동\",...}, ...]"
    }
  ]
}

이 블록이 LLM의 두 번째 입력이 된다. Claude는 이제 실제 외부 시스템의 데이터를 컨텍스트에서 "보게" 된다.

7단계 — 최종 응답 생성 (두 번째 LLM 호출)

tool_result를 받은 Claude가 최종 응답을 생성한다. 원시 데이터를 그대로 뱉는 게 아니라 자연어로 해석해서 답한다.

조회 결과 20건입니다.

| id | name   | email            |
|----|--------|------------------|
|  1 | 홍길동 | hong@example.com |
|  2 | 김철수 | kim@example.com  |
...

추가 조건이 있으면 말씀해주세요.

LLM 호출이 두 번 일어난다는 것의 의미

이 흐름에서 LLM은 두 번 호출된다.

1차 호출: 사용자 입력 → tool_use 생성
2차 호출: tool_result 수신 → 최종 응답 생성

도구를 n번 쓰면 LLM 호출도 n+1번 일어난다. 비용과 지연시간이 선형으로 늘어나는 이유가 여기에 있다. 또한 이 루프 자체가 에이전트의 가장 기본 단위다. 더 복잡한 멀티 에이전트 시스템도 결국 이 루프의 반복과 중첩으로 이루어진다.

 

계정정보는 어디에 저장되는가

MCP가 외부 시스템에 접속하려면 자격증명(API 키, DB 비밀번호 등)이 필요하다. 기본적으로 설정 파일에 평문으로 저장된다.

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)

{
  "mcpServers": {
    "postgresql": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "POSTGRES_HOST": "localhost",
        "POSTGRES_USER": "myuser",
        "POSTGRES_PASSWORD": "mypassword123"
      }
    }
  }
}

Claude Code 런타임이 이 파일을 읽어서 MCP 서버를 시작할 때 환경변수로 주입한다. LLM은 이 자격증명을 볼 수 없다. 런타임과 MCP 서버 사이에서만 오가는 정보다.

 

실무에서는 평문 저장 대신 세 가지 방법을 쓴다.

환경변수 참조 — 설정 파일에 값 대신 변수명을 쓴다.

"POSTGRES_PASSWORD": "${POSTGRES_PASSWORD}"

.env 파일 분리 — 자격증명을 별도 파일로 관리하고 git에서 제외한다.

Secret Manager — AWS Secrets Manager나 HashiCorp Vault 같은 전용 비밀 관리 시스템에서 런타임에 값을 가져온다. 운영 환경의 표준 방식이다.

 

정리

MCP를 한 문장으로 정의하면 이렇다.

LLM이 외부 세계와 표준화된 방식으로 통신하기 위한 프로토콜.

 

그리고 동작 흐름의 핵심은 세 가지다.

첫째, LLM은 판단만 한다. 실제 실행은 MCP 서버가 담당한다. Claude가 직접 DB에 접속하거나 API를 호출하지 않는다.

둘째, LLM 호출은 두 번 일어난다. tool_use 생성과 최종 응답 생성, 이 두 번의 API 호출 사이에 MCP 서버 실행이 끼어드는 구조다.

셋째, MCP는 표준화가 핵심이다. PostgreSQL이든, Notion이든, GitHub이든 — Claude 입장에서는 동일한 tool_use/tool_result 인터페이스로 모든 것을 다룬다. 외부 시스템이 바뀌어도 Claude 쪽 로직은 바뀌지 않는다.

반응형
Comments