byteforce

CPN 한국어 자습서 · 러닝패스 2 / 4 — Building with the Claude API

MCP

리소스 정의하기

Defining resources

리소스는 MCP 서버가 클라이언트에 데이터를 노출하는 방법입니다. 사용자가 @로 문서를 멘션하면 ① 문서 목록을 자동완성으로 띄우고 ② 멘션된 문서 내용을 프롬프트에 주입합니다. 이번 장에서는 @mcp.resourcedirect 리소스와 templated 리소스 둘을 정의합니다.

전체 내레이션영상 나레이션 한국어 번역 (전체)

Stephen Grider · Anthropic 기술 스태프

이번 영상에서는 MCP 서버의 다음 큰 기능인 리소스(Resources)로 넘어갑니다. 이해를 돕기 위해 프로젝트에 기능을 하나 더 붙입니다. 사용자가 @ 기호에 이어 문서 이름을 적어 문서를 멘션할 수 있게 하고, 그렇게 하면 그 문서의 내용을 자동으로 가져와 Claude에 보내는 프롬프트에 끼워 넣고 싶습니다.

이 기능에는 두 가지 측면이 있습니다. 사용자가 메시지에 @를 입력하는 순간 멘션 가능한 모든 문서 목록을 작은 자동완성 창에 보여 줍니다. 그리고 멘션이 들어간 메시지를 제출하면 그 문서의 내용을 가져와 Claude에 보내는 프롬프트에 삽입합니다. 예를 들어 사용자가 "@report.pdf 파일에 뭐가 들어 있어?"라고 하면, 질문과 함께 "사용자가 이 문서를 참조했고 그 내용은 이것"이라고 알려 주는 프롬프트를 조립해 보냅니다.

핵심은 Claude가 도구를 써서 report.pdf 안을 알아내도록 의존하지 않는다는 점입니다. 대신 사용자가 미리 파일을 멘션하면 우리가 그 컨텍스트를 사전에 끼워 넣습니다. 정리하면 별개의 두 기능입니다 — 첫째, @ 입력 시 멘션 가능한 문서 목록을 MCP 서버에서 받아 옵니다. 둘째, 멘션이 포함된 메시지를 제출하면 단일 문서의 내용을 MCP 서버에서 받아 옵니다. 이 정보를 MCP 서버에서 꺼내는 데 리소스를 씁니다.

리소스는 MCP 서버가 클라이언트에 일정량의 데이터를 노출하게 해 줍니다. 보통 읽기 작업 하나당 리소스 하나를 정의합니다. 우리 예시에선 문서 목록을 받는 작업과 단일 문서 내용을 읽는 작업이 있으니, 리소스 둘을 만들게 됩니다. 하나는 문서 이름 목록만 돌려줘 자동완성에 넣고, 다른 하나는 문서 ID로 단일 문서의 내용을 노출합니다.

이 리소스들은 MCP 클라이언트를 통해 접근됩니다. 전체 흐름은 이렇습니다 — 사용자가 "@"를 입력하는 순간 자동완성에 문서 이름을 띄워야 하므로, 우리 코드가 MCP 클라이언트에 요청하고, 클라이언트는 read resource request를 MCP 서버로 보냅니다. 이 요청 안에는 URI가 들어 있습니다 — 읽고 싶은 리소스의 주소죠. 이 URI는 리소스를 처음 정의할 때 정해집니다. 요청이 도착하면 MCP 서버는 그 URI에 해당하는 함수를 실행하고, 결과를 read resource result 메시지로 돌려보냅니다. 그러면 우리는 그 데이터를 자동완성에 표시하거나 필요한 곳에 씁니다.

리소스에는 두 종류가 있습니다 — directtemplated입니다. direct 리소스는 static 리소스라고도 부릅니다. direct 리소스는 정적 URI라서 항상 똑같습니다(예: docs://documents). templated 리소스는 URI 안에 파라미터를 하나 이상 둡니다(예: docs://documents/{doc_id} 같은 와일드카드). 우리가 이 리소스를 요청하면 URI 안의 그 doc_id가 Python MCP SDK에 의해 자동으로 파싱되어 같은 이름의 키워드 인자로 함수에 전달됩니다. 와일드카드에 적은 문자열이 곧 함수 인자 이름이 됩니다. 즉 더 많은 선택·다양성·맞춤이 필요할 때 templated 리소스를 씁니다.

이제 에디터에서 mcp_server.py를 엽니다. 첫 리소스는 모든 문서 ID를 돌려줍니다 — 우리에게 문서 ID는 사실상 문서 이름이라 자동완성에 그대로 넣을 수 있습니다. @mcp.resource를 추가하고 첫 인자로 URI docs://documents를, 그리고 mime_type으로 application/json을 줍니다. 리소스는 평문·JSON·바이너리 등 어떤 데이터든 돌려줄 수 있으므로, 어떤 종류의 데이터인지 힌트를 주는 것이 MIME 타입입니다. application/json은 "구조화된 JSON 문자열을 보낼 것"이라는 힌트이고, 그걸 역직렬화해 쓸 책임은 클라이언트에 있습니다. 함수 list_docslist(docs.keys())를 돌려줍니다. 우리가 직접 JSON 문자열로 만들지 않아도 SDK가 알아서 문자열로 변환해 줍니다.

두 번째 리소스는 templated입니다 — URI에 {doc_id} 와일드카드를 넣습니다. 이번엔 변화를 주려고 MIME 타입을 text/plain으로 둡니다. 문서 내용만 평문으로 돌려줄 거라 구조로 감싸지 않으니까요. (실무라면 보통 ID·내용·작성자 등이 담긴 문서 레코드 전체를 돌려주겠지만, 예시라 평문만 보여 드립니다.) 함수 fetch_doc(doc_id: str) -> str에서 먼저 doc_id not in docs면 ValueError를 던지고, 통과하면 docs[doc_id]를 돌려줍니다. 와일드카드 이름이 그대로 키워드 인자가 되며, 파라미터를 더 넣으면 추가 키워드 인자로 들어옵니다.

이제 MCP Inspector로 테스트합니다. 터미널에서 uv run mcp dev mcp_server.py를 실행하면 기본 6274(혹은 6277) 포트의 웹 서버가 뜹니다. 브라우저로 열어 Connect를 누르고 Resources로 갑니다. List Resources는 static(direct) 리소스만 보여 주므로 docs://documents 하나가 나오고, Resource Templates를 따로 펼치면 fetch_doc 하나가 보입니다. docs://documents를 실행하면 MCP 서버가 돌려준 정확한 구조가 나오는데, text 프로퍼티 안에 JSON 문자열로 직렬화된 데이터가 들어 있습니다 — 이 문자열을 받아 역직렬화하는 건 우리 CLI 앱의 몫입니다. fetch_doc은 doc_id(예: report.pdf)를 넣어 실행하면 text/plain으로 그 문서 내용이 나옵니다. text/plain이라는 힌트는 "JSON으로 풀려고 하지 말라"는 신호입니다.

이 장에서 배우는 것What you'll learn

약 8분
1

리소스 = MCP 서버가 클라이언트에 데이터를 노출 (읽기 작업 1개당 1개)

2

@ 멘션 기능 — 목록 자동완성 + 단일 문서 내용 주입

3

read resource request(URI 포함) → 서버가 함수 실행 → result

4

@mcp.resource(uri, mime_type=...) 데코레이터로 정의

5

direct(static, 고정 URI) vs templated({doc_id} 와일드카드)

6

MIME 타입은 데이터 종류 힌트 — application/json · text/plain

먼저 짚고 갈 용어
리소스 (Resource)
MCP 서버가 클라이언트에 데이터를 노출하는 프리미티브. 보통 읽기 작업 하나당 하나.
URI
리소스의 주소. docs://documents처럼 리소스를 정의할 때 정해진다.
direct / templated
direct(=static)는 고정 URI. templated는 {doc_id} 같은 파라미터를 URI에 둔다.
MIME 타입
돌려주는 데이터 종류 힌트. application/json은 역직렬화 신호, text/plain은 평문.

리소스란

Expose data to the client

리소스는 MCP 서버가 클라이언트에 데이터를 노출하는 프리미티브입니다. 보통 읽기 작업 하나당 리소스 하나를 정의합니다. 우리 @ 멘션 기능에는 두 가지가 필요합니다 — 자동완성용 문서 목록과 멘션된 단일 문서 내용이라, 리소스 둘을 만듭니다.

왜 리소스인가

사용자가 @report.pdf처럼 미리 멘션하면, 그 내용을 프롬프트에 사전 주입합니다. Claude가 도구를 써서 문서를 찾아 읽도록 의존할 필요가 없어집니다.

리소스 타입 토글 · read resource 흐름

두 리소스 모두 클라이언트가 read resource request(URI 포함)를 보내면, 서버가 그 URI의 함수를 실행해 result를 돌려줍니다. templated는 URI의 {doc_id}가 같은 이름의 키워드 인자로 함수에 전달됩니다.

direct 리소스 — 문서 목록

A direct (static) resource

direct 리소스는 고정 URI를 가집니다(docs://documents). 모든 문서 ID(우리에겐 곧 문서 이름) 목록을 돌려줘 자동완성에 그대로 넣습니다. MIME 타입 application/json은 "구조화된 JSON을 보낼 것"이라는 힌트이고, 역직렬화는 클라이언트의 몫입니다.

mcp_server.py · @mcp.resource — list_docs
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")

docs = {
    "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
    "report.pdf": "The report details the state of a 20m condenser tower.",
    "financials.docx": "These financials outline the project budget and expenditures.",
    "outlook.pdf": "This document presents the projected future performance of the company.",
    "plan.md": "The plan outlines the steps for the project's implementation.",
    "spec.txt": "These specifications define the technical requirements.",
}

# direct(static) 리소스 — 고정 URI. 모든 문서 ID(=이름) 목록을 돌려준다.
@mcp.resource(
    "docs://documents",
    mime_type="application/json",
)
def list_docs() -> list[str]:
    return list(docs.keys())

list(docs.keys())를 그냥 돌려줄 뿐, 직접 JSON 문자열로 만들지 않습니다 — SDK가 알아서 문자열로 직렬화합니다.

templated 리소스 — 단일 문서

A templated resource

templated 리소스는 URI에 {doc_id} 와일드카드를 둡니다. 요청 시 그 값이 같은 이름의 키워드 인자로 함수에 전달됩니다. 이번엔 MIME 타입을 text/plain으로 둬서 문서 내용을 평문으로 돌려줍니다.

mcp_server.py · @mcp.resource — fetch_doc
# templated 리소스 — URI에 {doc_id} 와일드카드. 단일 문서 내용을 평문으로.
@mcp.resource(
    "docs://documents/{doc_id}",
    mime_type="text/plain",
)
def fetch_doc(doc_id: str) -> str:
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    return docs[doc_id]

이제 인스펙터로 확인합니다 — uv run mcp dev mcp_server.py로 띄워 Connect 후 Resources로 갑니다. List Resources는 direct만 보여 주고(docs://documents), Resource Templatesfetch_doc이 나옵니다.

MCP Inspector · result 구조
docs://documents 실행 →
{
  "contents": [
    {
      "uri": "docs://documents",
      "mimeType": "application/json",
      "text": "[\"deposition.md\", \"report.pdf\", ...]"
    }
  ]
}

※ text는 JSON 문자열 → 클라이언트가 역직렬화
fetch_doc(doc_id="report.pdf") → mimeType: text/plain (평문 그대로)

정리 & 점검

Recap & check
핵심 정리
  • 리소스 = 서버가 클라이언트에 데이터를 노출. 읽기 작업 하나당 하나.
  • 흐름: read resource request(URI) → 서버가 URI 함수 실행 → result.
  • direct(static, docs://documents) vs templated(docs://documents/{doc_id}).
  • MIME 타입은 힌트 — application/json(역직렬화) · text/plain(평문).

Q1docs://documents/{doc_id}처럼 URI에 와일드카드를 둔 리소스를 무엇이라 하나요?

Q2list_docsmime_type="application/json"을 준 이유는?

Q3인스펙터의 List Resources에는 어떤 리소스가 나오나요?

MEMBER SESSION REQUIRED · REGISTRATION IS FREE

여기부터는 등록한 분에게 열립니다.

전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 76개 레슨을 끝까지 읽을 수 있습니다.

등록하고 이어서 읽기

이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.