콘텐츠로 이동

리소스

기계 번역

이 페이지는 영어 문서를 자동으로 번역한 것이며, 영어 페이지가 기준이 되는 정식 버전입니다. 어색하거나 잘못된 부분이 있다면 번역 페이지에서 제보하는 방법을 확인하세요.

리소스는 애플리케이션이 읽도록 노출하는 데이터입니다.

도구와 리소스를 가르는 기준이 바로 이것입니다. 도구는 모델이 호출하기로 결정하는 것입니다. 리소스는 애플리케이션이 불러오기로 결정해서(설정 파일, 레코드, 문서 등) 모델에게 컨텍스트로 제시하는 것입니다.

평범한 Python 함수에 @mcp.resource(uri)를 붙이면 리소스를 선언할 수 있습니다.

첫 번째 리소스

server.py
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.resource("config://app")
def get_config() -> str:
    """The active shop configuration."""
    return "theme=dark\nlanguage=en"

도구와 같은 모양이지만 한 가지가 더 있습니다. 바로 URI입니다. 리소스는 이름이 아니라 주소로 지정합니다. 클라이언트는 config://app을 요청하지, get_config를 요청하는 일은 없습니다.

나머지는 SDK가 여전히 함수에서 읽어 냅니다.

  • 이름은 함수 이름인 get_config입니다.
  • 클라이언트가 보는 설명은 독스트링입니다.
  • 내용은 반환하는 값 그대로입니다.

resources/list 때 클라이언트는 다음을 받습니다.

{
  "name": "get_config",
  "uri": "config://app",
  "description": "The active shop configuration.",
  "mimeType": "text/plain"
}

그리고 클라이언트가 config://app을 읽으면 함수가 실행되고 반환값이 텍스트로 돌아옵니다.

result.contents  # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]

Tip

목록 조회는 비용이 거의 들지 않습니다. 함수는 resources/list 때는 호출되지 않고, resources/read 때만, 그것도 요청된 URI에 한해서만 호출됩니다. 리소스를 천 개 노출해도 비용은 누군가 실제로 여는 리소스만큼만 듭니다.

직접 해 보기

MCP Inspector로 서버를 실행하세요.

uv run mcp dev server.py

출력되는 URL을 열고 Resources 탭으로 이동하세요. config://app이 설명과 함께 목록에 있습니다. 클릭하면 Inspector가 읽어 들이며, 앞서 작성한 설정 두 줄이 보입니다.

리소스 템플릿

레코드마다 URI를 하나씩 두는 방식은 확장되지 않습니다. URI에 플레이스홀더를 넣고 함수에 그에 대응하는 매개변수를 두세요.

server.py
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.resource("config://app")
def get_config() -> str:
    """The active shop configuration."""
    return "theme=dark\nlanguage=en"


@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
    """A customer's profile."""
    return f"User {user_id}: 12 orders since 2021."

URI에는 {user_id} 자리를, 함수에는 user_id: str 매개변수를 둡니다. 계약은 이것이 전부입니다.

이제 이것은 리소스 템플릿이며, 있는 곳도 바뀝니다. resources/list에서 빠지고 대신 resources/templates/list에 주소가 아닌 패턴으로 나타납니다.

{
  "name": "get_user_profile",
  "uriTemplate": "users://{user_id}/profile",
  "description": "A customer's profile.",
  "mimeType": "text/plain"
}

클라이언트는 플레이스홀더를 채워 users://42/profile, users://ada/profile 같은 구체적인 URI를 읽습니다. 함수 하나가 이 모든 URI에 응답하며, 일치한 값은 user_id로 전달됩니다.

result.contents  # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]

결과의 uri에 주목하세요. 템플릿이 아니라 클라이언트가 요청한 구체적인 URI입니다.

Check

플레이스홀더와 매개변수는 서로 일치해야 합니다. URI는 여전히 {user_id}인데 함수 매개변수 이름을 user로 바꾸면, 어떤 클라이언트도 접근하기 전인 임포트 시점에 데코레이터가 거부합니다.

ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}

불일치는 버그일 수밖에 없으므로, SDK는 불일치가 있는 채로는 서버를 아예 시작할 수 없게 만듭니다.

플레이스홀더 문법은 RFC 6570을 따릅니다. 여러 세그먼트에 걸친 값에는 {+path}, 선택적 쿼리 매개변수에는 {?q,lang} 등을 쓸 수 있습니다. SDK는 추출된 값에 기본적으로 경로 안전성 검사도 적용합니다. 전체 레퍼런스는 URI 템플릿과 경로 안전성에서 확인하세요.

get_user_profileContext로 어노테이션한 매개변수도 받을 수 있습니다. SDK는 이 매개변수를 URI 매개변수로 취급하는 일 없이 주입해 주며, 무엇을 제공하는지는 Context 페이지에서 다룹니다.

반환하는 값

str만 반환할 수 있는 것은 아닙니다. 리소스마다 mime_type을 지정하고 알맞은 값을 반환하세요.

server.py
import base64

from mcp.server import MCPServer

mcp = MCPServer("Bookshop")


@mcp.resource("docs://readme", mime_type="text/markdown")
def readme() -> str:
    """How to use this server."""
    return "# Bookshop\n\nSearch the catalog with the `search_books` tool."


@mcp.resource("stats://catalog", mime_type="application/json")
def catalog_stats() -> dict[str, int]:
    """Live counts for the catalog."""
    return {"books": 1204, "authors": 391}


@mcp.resource("covers://placeholder", mime_type="image/gif")
def placeholder_cover() -> bytes:
    """A 1x1 transparent GIF, shown when a book has no cover."""
    return base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
  • readmestr을 반환하므로 그대로 전송됩니다. 가장 흔한 경우입니다.
  • catalog_statsdict를 반환하므로 SDK가 대신 JSON 텍스트로 직렬화합니다.

    {
      "books": 1204,
      "authors": 391
    }
    
  • placeholder_coverbytes를 반환하므로 클라이언트는 TextResourceContents 대신 BlobResourceContents를 받으며, 반환한 바이트는 base64로 인코딩되어 blob 필드에 담깁니다.

JSON으로 직렬화할 수 있는 다른 모든 것(리스트, Pydantic 모델, 데이터클래스)에도 같은 규칙이 적용됩니다. strbytes도 아니면 JSON이 됩니다.

mime_type은 직접 선언하는 값이며 기본값은 text/plain입니다. SDK는 반환값을 들여다보고 이를 추측하는 일이 결코 없으므로, 따로 표시하지 않은 dict 리소스는 클라이언트에 여전히 일반 텍스트로 알려집니다.

Tip

이름, 제목, 설명을 함수에서 끌어내고 싶지 않다면 @mcp.resource()name=, title=, description=도 받습니다. 그리고 작성할 함수가 아예 없는 경우에는 mcp.server.mcpserver.resources에 미리 만들어진 Resource 클래스(TextResource, BinaryResource, FileResource, HttpResource, DirectoryResource)가 있으며, mcp.add_resource(...)로 등록하면 됩니다.

클라이언트는 리소스를 구독해서 리소스가 바뀔 때 알림을 받을 수도 있습니다. 이것은 클라이언트 쪽 이야기이며 클라이언트에서 다룹니다.

요약

  • 함수에 @mcp.resource(uri)를 붙이면 리소스가 됩니다. URI는 주소, 반환값은 내용, 독스트링은 설명입니다.
  • URI에 {placeholder} 자리가 있으면 템플릿이 됩니다. resources/templates/list에 나열되며 함수 하나가 일치하는 모든 URI를 처리합니다.
  • 플레이스홀더 이름은 함수의 매개변수 이름과 같아야 합니다. 틀리면 프로덕션이 아니라 임포트 시점에 알게 됩니다.
  • 함수는 리소스를 나열할 때가 아니라 읽을 때 실행됩니다.
  • str은 텍스트가 되고, bytes는 base64 blob이 되며, 그 밖의 것은 모두 JSON 텍스트가 됩니다. 레이블은 mime_type= 인자로 붙입니다.
  • 도구는 모델이 행동하기 위한 것이고, 리소스는 애플리케이션이 읽기 위한 것입니다.

세 번째 프리미티브, 즉 사람이 메뉴에서 고르는 것은 프롬프트입니다.