첫걸음
기계 번역
이 페이지는 영어 문서를 자동으로 번역한 것이며, 영어 페이지가 기준이 되는 정식 버전입니다. 어색하거나 잘못된 부분이 있다면 번역 페이지에서 제보하는 방법을 확인하세요.
랜딩 페이지는 빠르게 진행합니다. 서버를 작성하고, 실행하고, 도구를 호출합니다.
이 페이지는 천천히 진행합니다. 서버가 노출할 수 있는 세 가지를 모두 다루고, 그 과정에서 등장하는 모든 것에 이름을 붙입니다.
호스트, 클라이언트, 서버
지금부터 모든 페이지에서 마주칠 세 단어입니다.
- 호스트는 LLM 애플리케이션입니다. Claude, IDE, 에이전트 런타임이 여기에 해당하며, 사용자가 대화하는 상대가 바로 호스트입니다.
- 클라이언트는 호스트 안에 있으며 MCP로 통신합니다. 호스트는 연결된 서버마다 클라이언트를 하나씩 실행합니다.
- 서버는 이 SDK로 만드는 것입니다. 서버는 클라이언트에 여러 가지를 노출하며, 모델과 직접 대화하는 일은 없습니다.
직접 작성하는 것은 서버입니다. 호스트는 다른 누군가가 만든 제품입니다. SDK는 Client도 제공합니다. 서버를 테스트할 때 쓰게 되며, 이 페이지 뒷부분에서 다시 등장합니다.
세 가지 프리미티브
서버가 노출하는 것은 정확히 세 종류입니다. 셋을 가르는 기준은 누가 사용을 결정하는가입니다.
| 프리미티브 | 제어 주체 | 설명 | 예시 |
|---|---|---|---|
| 도구 | 모델 | 모델이 어떤 동작을 수행하려고 호출하는 함수 | API 호출, 데이터베이스 쓰기 |
| 리소스 | 애플리케이션 | 호스트가 모델의 컨텍스트에 불러오는 데이터 | 파일 내용, API 응답 |
| 프롬프트 | 사용자 | 사용자가 이름으로 호출하는 재사용 가능한 메시지 템플릿 | 슬래시 명령, 메뉴 항목 |
"제어 주체"가 이 구분의 핵심입니다. 도구는 모델이 호출하기로 결정했기 때문에 실행됩니다. 리소스는 애플리케이션이 모델에 필요하다고 판단했기 때문에 첨부됩니다. 프롬프트는 사용자가 골랐기 때문에 실행됩니다.
Info
웹 API를 만들어 본 적이 있다면 필요한 감각은 이미 대부분 갖추고 있습니다. 리소스는 GET(데이터를
불러오고 아무것도 바꾸지 않음)이고 도구는 POST(작업을 수행하며 부작용이 있을 수 있음)입니다.
프롬프트는 HTTP에 대응하는 것이 없으며, 사용자가 이름으로 실행하는 저장된 쿼리에 더 가깝습니다.
서버 하나에 세 가지 모두
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.prompt()
def summarize(text: str) -> str:
"""Summarize a piece of text in one sentence."""
return f"Summarize the following text in one sentence:\n\n{text}"
평범한 함수 셋, 데코레이터 셋입니다. 각 데코레이터가 곧 등록의 전부입니다.
@mcp.tool()은add를 도구로 만듭니다.@mcp.resource("greeting://{name}")은greeting을 리소스 템플릿으로 만듭니다. URI의{name}이 함수의 매개변수입니다.@mcp.prompt()는summarize를 프롬프트로 만듭니다. 이 함수가 반환하는 문자열은 사용자 메시지가 됩니다.
나머지(이름, 설명, 인자 스키마)는 모두 SDK가 함수 자체에서 읽어 냅니다. 함수 이름, 독스트링, 타입 힌트에서 가져오는 것입니다. 어느 것도 따로 선언하지 않았습니다.
Tip
SDK의 두 부분은 임포트 경로도 둘입니다. from mcp import Client와
from mcp.server import MCPServer입니다. from mcp import MCPServer는 없습니다.
직접 해 보기
MCP Inspector로 실행하세요.
uv run mcp dev server.py
출력되는 URL을 여세요. Inspector에는 프리미티브마다 탭이 하나씩 있습니다. 순서대로 살펴보세요.
도구. 항목은 add 하나이며, Add two numbers.라는 설명이 붙어 있습니다. 폼에는 필수 정수 필드가 a에 하나, b에 하나 있습니다. 값을 채워 호출하면 결과는 3입니다. Inspector는 a: int, b: int를 보고 이 폼을 만들었습니다. 다른 모든 클라이언트도 마찬가지입니다.
리소스. Resources 목록은 비어 있습니다. greeting은 Resource Templates 아래에 있습니다. greeting://{name}에 매개변수가 있어서, 누군가 name을 제공하기 전까지는 나열할 단일 리소스가 없기 때문입니다. World를 넣고 읽어 보세요.
Hello, World!
프롬프트. 항목은 summarize 하나이며, 필수 인자는 text 하나입니다. 텍스트를 넣어 프롬프트를 가져오면 role: user와 렌더링된 문자열을 내용으로 하는 메시지 하나가 돌아옵니다. 프롬프트는 이것이 전부입니다. 메시지를 만드는 함수일 뿐입니다.
Inspector는 서버를 stdio로 실행했습니다. stdio는 MCP 서버가 사용할 수 있는 트랜스포트 중 하나입니다. 아직 트랜스포트를 고를 필요는 없습니다. 그 내용은 서버 실행하기 페이지에서 다룹니다.
기능
Inspector에서 탭 세 개를 보았습니다. Inspector가 세 개라는 것을 어떻게 알았는지 살펴보겠습니다.
클라이언트가 연결하면 서버는 기능, 즉 어떤 부류의 요청에 응답할지를 선언합니다. 클라이언트는 이 선언을 보고 애초에 무엇을 요청할지 결정합니다. 이 선언을 작성한 적은 없습니다. MCPServer가 대신 선언합니다.
직접 확인해 보세요. SDK의 Client는 서버 객체를 그대로 받아 인메모리로 연결합니다(서브프로세스도, 포트도 없습니다).
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
print(client.server_capabilities.model_dump(exclude_none=True))
asyncio.run(main())
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
이 딕셔너리가 서버가 선언한 기능입니다. 연결하는 모든 클라이언트가 가장 먼저 알게 되는 내용입니다.
| 기능 | 클라이언트가 이제 호출할 수 있는 것 |
|---|---|
tools |
tools/list, tools/call |
resources |
resources/list, resources/templates/list, resources/read |
prompts |
prompts/list, prompts/get |
MCPServer는 세 프리미티브를 모두 제공하므로 셋 다 항상 선언됩니다.
없는 것에도 주목하세요. completions(리소스 템플릿과 프롬프트의 인자 자동 완성)에는 직접 작성하는 핸들러가 필요한데, 이 서버에는 핸들러가 없으므로 해당 기능이 빠져 있고, 올바르게 동작하는 클라이언트라면 요청하지 않습니다. 선택 사항은 모두 이 규칙을 따릅니다. 등록하면 기능이 나타납니다. 자동 완성 페이지가 이를 보여 줍니다.
Info
Client(mcp)는 이 문서의 모든 예제를 테스트하는 데 쓰이는 바로 그 인메모리 클라이언트이며,
작성한 서버도 같은 방식으로 테스트하게 됩니다. 이를 다루는 페이지가 따로 있습니다. 테스트입니다.
작성하지 않은 것
이 페이지를 되돌아보세요. 작성한 것은 작은 Python 함수 세 개입니다. 다음은 작성하지 않았습니다.
- JSON Schema.
a: int, b: int가 곧add의 스키마입니다. - 요청 핸들러.
tools/list,resources/read,prompts/get은 모두 대신 처리됩니다. - 기능 선언.
MCPServer가 대신 만들었습니다. - 프로토콜 코드 단 한 줄. 버전 협상, JSON-RPC 프레이밍, 기능 교환은 모두
mcp dev와Client(mcp)안에서 일어났고, 눈에 보이지도 않았습니다.
이 비율이야말로 SDK가 존재하는 이유입니다.
요약
- 호스트는 LLM 앱이고, 클라이언트는 그 안에서 MCP로 통신하는 부분이며, 서버는 직접 만드는 것입니다.
- 도구는 모델이, 리소스는 애플리케이션이, 프롬프트는 사용자가 제어합니다.
- 프리미티브마다 데코레이터 하나면 됩니다.
@mcp.tool(),@mcp.resource(uri),@mcp.prompt()입니다. 이름, 설명, 스키마는 함수에서 가져옵니다. {param}이 들어간 URI는 리소스 템플릿을 만들며, 구체적인 리소스와는 따로 나열됩니다.- 서버의 기능은 자동으로 선언되며, 클라이언트는 서버가 선언한 것만 요청합니다.
Client(mcp)는 서버 객체에 인메모리로 연결합니다. 첫날부터 갖추는 테스트 하네스입니다.
다음은 실제 호스트에 연결하기입니다. 이 서버를 Claude Desktop이나 IDE 안에서 실제로 돌려 봅니다. 그다음은 테스트입니다. 페이지 하나, 인메모리 클라이언트 하나면 동작하는지 추측할 일이 없어집니다. 그 뒤로는 프리미티브마다 전용 페이지가 이어지며, 모델이 주도하는 프리미티브인 도구부터 시작합니다.