MCP 기초 설명
MCP 기초 — 통신 구조 이해하기
이 레포는 MCP(Model Context Protocol)가 실제로 어떻게 동작하는지를
코드와 함께 설명합니다.
worker.js는 이 README가 설명하는 내용을 그대로 구현한 최소한의 MCP 서버입니다.
1. MCP가 하는 일
Claude(클라이언트)와 외부 도구(서버) 사이에서 표준화된 방식으로 데이터를 주고받는 규약입니다.
Claude Desktop / claude.ai
│
│ "get_fillet_r 실행해줘, keyword = 부드러운"
▼
MCP 서버 (worker.js)
│
│ "{ r: 5 }"
▼
Claude
2. 3가지 연결 방식
MCP 서버에 붙는 방법은 세 가지입니다.
STDIO (로컬 프로세스)
Claude Desktop
│ (프로세스 직접 실행)
▼
server.py / server.js
(내 컴퓨터에서 실행)
- Claude Desktop이 서버 프로그램을 직접 실행하고, stdin/stdout으로 통신
- 사례: RhinoMCP, 파일시스템 MCP 등 로컬 도구
- 설정 예시:
{
"mcpServers": {
"rhino": {
"command": "python",
"args": ["C:/rhino_mcp/server.py"]
}
}
}
HTTP + SSE (원격 서버)
Claude Desktop / claude.ai
│ (HTTP 요청)
▼
원격 서버 (Cloudflare Workers 등)
- URL만 등록하면 누구나 접근 가능
- 사례: caffeineworks MCP, 이 레포의
worker.js - 설정 예시:
{
"mcpServers": {
"my-server": {
"url": "https://my-mcp.workers.dev/mcp"
}
}
}
Streamable HTTP (신규 표준)
- HTTP + SSE의 후속, 2025년 공식화
- 단일 엔드포인트로 단순화된 방식
- 현재 이 레포의 구현 방식
3. 3가지 통신 구조
MCP는 JSON-RPC 2.0 형식으로 메시지를 주고받습니다.
요청 (Request) — 클라이언트 → 서버
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_fillet_r",
"arguments": { "keyword": "부드러운" }
}
}
| 필드 | 역할 |
|------|------|
| jsonrpc | 항상 "2.0" 고정 |
| id | 요청-응답 매칭용 번호 |
| method | 무엇을 요청하는지 |
| params | 요청 데이터 |
응답 (Response) — 서버 → 클라이언트
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "{ \"r\": 5 }" }
]
}
}
id가 요청과 일치해야 함result.content는 배열, 각 항목은type과 내용으로 구성
에러 응답
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Tool not found"
}
}
4. 연결할 때 벌어지는 일 (핸드셰이크)
Claude가 MCP 서버에 처음 붙으면 이 순서로 진행됩니다.
Claude MCP 서버
│ │
│──── initialize ─────────▶│ "나는 Claude야, 뭘 할 수 있어?"
│◀─── result ──────────────│ "나는 shape-server야"
│ │
│──── tools/list ─────────▶│ "툴 목록 줘"
│◀─── result ──────────────│ [툴 이름, 설명, 파라미터 스키마]
│ │
│──── tools/call ─────────▶│ "get_fillet_r 실행해줘"
│◀─── result ──────────────│ "{ r: 5 }"
5. 서버가 반드시 응답해야 하는 메서드
| 메서드 | 역할 |
|--------|------|
| initialize | 서버 이름, 버전, 기능 목록 반환 |
| tools/list | 툴 이름 + 설명 + 파라미터 스키마 반환 |
| tools/call | 실제 툴 실행 후 결과 반환 |
이 세 가지에 응답할 수 있으면 MCP 서버입니다.
6. 툴 스키마 — Claude가 툴을 이해하는 방법
tools/list에서 반환하는 스키마가
Claude가 "언제, 어떻게 이 툴을 호출할지" 판단하는 근거입니다.
{
"name": "get_fillet_r",
"description": "디자인 감성어를 입력하면 Rhino 모델링에 쓸 Fillet R 수치(mm)를 반환한다",
"inputSchema": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "디자인 감성 형용사. 예: 부드러운, 단단한, 따뜻한"
}
},
"required": ["keyword"]
}
}
중요:
description이 부실하면 Claude가 엉뚱하게 호출합니다.
"Fillet R 수치를 반환한다"처럼 무엇을 반환하는지 명확하게 쓰세요.
7. 코드와 README의 1:1 대응
worker.js의 구조는 이 README와 정확히 대응됩니다.
worker.js
├── SECTION A: 키워드-수치 데이터 (JSON) ← 서버의 "두뇌"
├── SECTION B: initialize 응답 ← 핸드셰이크 4번
├── SECTION C: tools/list 응답 ← 툴 스키마 6번
├── SECTION D: tools/call 응답 ← 실제 실행
└── SECTION E: HTTP 요청 라우팅 ← 연결 방식 2번
코드를 읽을 때 각 SECTION 주석을 기준으로 이 README와 대조하며 보세요.
8. 직접 수정해보기
worker.js의 SECTION A만 수정하면 서버의 동작이 바뀝니다.
// SECTION A: 키워드-수치 데이터 ← 여기만 고치면 됩니다
const FILLET_DATA = {
"부드러운": 8,
"단단한": 1,
"따뜻한": 6,
// 원하는 키워드와 수치를 여기에 추가하세요
};
나중에는 이 데이터를 별도 JSON 파일로 분리하거나,
임베딩 기반으로 고도화할 수 있습니다. (3일차 실습)