미디어 (Media)

미디어 (Media)

텍스트가 도구가 반환할 수 있는 전부는 아니에요.

SDK는 바이너리 결과를 위한 헬퍼 둘(**Image**와 Audio)과, 서버·도구·리소스·프롬프트에 클라이언트 UI에서 얼굴을 주기 위한 Icon 타입을 제공해요.

이미지 반환

반환 타입을 Image로 애너테이트하고 파일을 가리킨 뒤 반환해요:

# docs_src/media/tutorial001.py
from pathlib import Path

from mcp.server import MCPServer
from mcp.server.mcpserver import Image

mcp = MCPServer("Brand kit")

LOGO_FILE = Path(__file__).parent / "logo.png"  # or the path to your file on disk


@mcp.tool()
def logo() -> Image:
    """The brand logo as a PNG."""
    return Image(path=LOGO_FILE)
  • Imagepath(읽을 파일) 또는 data(원시 바이트) 중 정확히 하나를 받아요.
  • 클라이언트가 보는 MIME 타입은 접미사에서 추측돼요: logo.pngimage/png로 알려져요.
  • 여기 로고에 특별한 건 없어요. server.py 옆의 어떤 PNG든 돼요: 코드가 렌더링한 차트, 다이어그램, 사진.

Image는 SDK 편의 기능이지 프로토콜 타입이 아니에요. 와이어 위에서 반환값은 ImageContent 블록(파일 바이트를 base64로 인코딩 + MIME 타입)이 돼요:

result.content             # [ImageContent(type="image", data="iVBORw0KGgoAAAANSUhEUg...", mime_type="image/png")]
result.structured_content  # None

두 가지를 주목하세요:

  • data는 base64예요. 바이트를 만지지 않았어요. SDK가 파일을 읽고 인코딩했어요.
  • structured_contentNone이에요. Image는 애플리케이션이 파싱할 데이터가 아니라 모델이 볼 콘텐츠예요. 출력 스키마가 없어요. (반환 애너테이션 자체가 스키마인 **구조화된 출력**과 대조돼요.)

!!! info ImageContentAudioContentmcp.types에, 평범한 str 결과가 되는 TextContent 바로 옆에 있어요. 도구 결과는 콘텐츠 블록 목록이에요. ImageAudio는 두 바이너리 종류를 만드는 가장 짧은 길이에요.

실행해 보기

server.py 옆에 아무 PNG를 logo.png로 두고 실행해요:

uv run mcp dev server.py

Tools 탭을 열고 logo를 호출해요. 결과는 문자열이 아니에요: image 콘텐츠 블록이고, Inspector가 그림을 렌더링해요. 디스크의 파일부터 화면의 픽셀까지 전부 SDK가 했어요.

오디오 반환

Audio는 같은 모양이에요. logo.png를 그대로 두고 옆에 아무 WAV를 chime.wav로 두세요:

# docs_src/media/tutorial002.py
from pathlib import Path

from mcp.server import MCPServer
from mcp.server.mcpserver import Audio, Image

mcp = MCPServer("Brand kit")

LOGO_FILE = Path(__file__).parent / "logo.png"
CHIME_FILE = Path(__file__).parent / "chime.wav"


@mcp.tool()
def logo() -> Image:
    """The brand logo as a PNG."""
    return Image(path=LOGO_FILE)


@mcp.tool()
def chime() -> Audio:
    """The notification chime as a WAV."""
    return Audio(path=CHIME_FILE)

결과는 AudioContent 블록이에요:

result.content             # [AudioContent(type="audio", data="UklGR...", mime_type="audio/wav")]
result.structured_content  # None

같은 거래예요: 디스크의 파일이 들어가 base64와 MIME 타입이 나오고, 출력 스키마는 없어요.

바이트 또는 파일

두 헬퍼 모두 path= 대신 data=(원시 바이트)도 받아요. 그건 그 자체 파일이 없는 바이트를 위한 모드예요 — DB 컬럼, HTTP 응답, Pillow가 막 그린 것:

# docs_src/media/tutorial003.py
from pathlib import Path

from mcp.server import MCPServer
from mcp.server.mcpserver import Image

mcp = MCPServer("Brand kit")

LOGO_FILE = Path(__file__).parent / "logo.png"


@mcp.tool()
def logo_from_bytes() -> Image:
    """The brand logo as a PNG."""
    png = LOGO_FILE.read_bytes()  # a database read, an HTTP response, Pillow output...
    return Image(data=png, format="png")

path=에서는 선언할 게 없어요: 결과가 만들어질 때 파일이 읽히고 MIME 타입이 접미사에서 추측돼요:

  • Image: .png, .jpg, .jpeg, .gif, .webp.
  • Audio: .wav, .mp3, .ogg, .flac, .aac, .m4a.

인식하지 못하는 접미사는 application/octet-stream으로 폴백돼요.

!!! check data=에는 파일 이름이 없으니 추측할 게 없어요. format=을 잊으면 SDK는 기본값으로 폴백해요: 이미지는 image/png, 오디오는 audio/wav. 그렇게 MP3 바이트로 Audio를 만들면 클라이언트는 mime_type="audio/wav"로 알려받고, 충실하게 디코딩에 실패해요. data=를 넘길 땐 format=도 넘기세요.

리소스 임베드

도구는 문서도 반환할 수 있어요: URI와 MIME 타입과 함께 어떤 텍스트나 바이트. 그게 **EmbeddedResource**이고, 또 다른 종류의 콘텐츠 블록이에요. 평범한 str과 달리 클라이언트에게 콘텐츠가 무엇인지 알려줘서, 첨부로 보여주거나 이미 아는 리소스로 알아볼 수 있게 해요.

# docs_src/media/tutorial005.py
from mcp.server import MCPServer
from mcp.types import EmbeddedResource, TextResourceContents

mcp = MCPServer("Brand kit")


@mcp.resource("brand://guidelines", mime_type="text/markdown")
def guidelines() -> str:
    """How to use the brand assets."""
    return "# Brand guidelines\n\nUse the primary colour for calls to action.\n"


@mcp.tool()
def brand_guidelines() -> EmbeddedResource:
    """The brand guidelines as a Markdown document."""
    return EmbeddedResource(
        resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text=guidelines())
    )
  • brand://guidelines은 평범한 리소스예요. 도구가 요청 시 같은 문서를 모델에 건네고, guidelines()를 직접 호출해 하나의 source of truth를 유지해요.
  • EmbeddedResourceTextResourceContentsmcp.types에서 와요. 이미지처럼 헬퍼는 없어요: 여러분이 만든 블록이 결과에 그대로 들어가고 structured_content는 없어요.
  • 리소스가 등록된 URI를 써서, 클라이언트가 첨부와 brand://guidelines이 같은 문서임을 알게 해요. 어떤 URI든 등록됐든 안 됐든 합법이에요.
result.content  # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]

바이너리 콘텐츠는 TextResourceContents 대신 TextResourceContents 자리에 바이트를 base64 인코딩한 blob이 있는 BlobResourceContents(uri=..., mime_type=..., blob=...)를 써요. 나중에 클라이언트가 resources/read할 수 있는 포인터만 보내려면, ResourceLink(name=..., uri=...)를 반환해요. 그것도 콘텐츠 블록이에요.

아이콘

Icon은 콘텐츠가 아니라 메타데이터예요. 이미지를 담지 않고 URI로 가리키며, 클라이언트는 그것을 가져와 서버 이름, 도구, 리소스, 프롬프트 옆에 보여줄 수 있어요.

# docs_src/media/tutorial004.py
from mcp.server import MCPServer
from mcp.types import Icon

LOGO = Icon(src="https://example.com/brand-kit.png", mime_type="image/png", sizes=["48x48"])
PALETTE = Icon(src="https://example.com/palette.svg", mime_type="image/svg+xml", sizes=["any"])

mcp = MCPServer("Brand kit", icons=[LOGO])


@mcp.tool(icons=[PALETTE])
def palette() -> list[str]:
    """The brand colour palette as hex codes."""
    return ["#1d4ed8", "#f59e0b", "#10b981"]


@mcp.resource("brand://guidelines", icons=[LOGO])
def guidelines() -> str:
    """How to use the brand assets."""
    return "Use the primary colour for calls to action."
  • src는 클라이언트가 해석할 수 있는 URI예요: https: 또는, 추가 fetch 없이 아이콘을 임베드하고 싶다면 data: URI.
  • mime_typesizes("48x48", 또는 확장 가능한 포맷은 "any")는 여러 개를 제공할 때 클라이언트가 올바른 것을 고르게 해요.
  • theme="light" 또는 theme="dark"는 아이콘을 한 색상 스킴으로 표시해요.

같은 icons=[...] 키워드는 MCPServer(...), @mcp.tool(), @mcp.resource(), @mcp.prompt()가 받아줘요.

클라이언트가 보는 곳

아이콘은 장식하는 대상과 함께 이동해요. 서버의 것은 클라이언트가 연결할 때 client.server_info로 도착해요(2026 시대 연결에서는 선택적이니 먼저 좁혀야 해요):

assert client.server_info is not None  # python-sdk servers identify themselves by default
client.server_info.icons  # [Icon(src="https://example.com/brand-kit.png", mime_type="image/png", sizes=["48x48"])]

도구의 아이콘은 tools/listTool 객체, 리소스의 것은 resources/listResource, 프롬프트의 것은 prompts/listPrompt에 있어요. 필드 이름은 항상 icons예요.

요약

  • 도구에서 ImageAudio를 반환하면 클라이언트는 ImageContent/AudioContent 블록을 받아요: 바이트를 base64로 인코딩하고 MIME 타입 포함.
  • path=에서 만들면 접미사가 MIME 타입을 결정하고, 메모리 data=는 명시적 format=과 함께 써요.
  • EmbeddedResource를 반환하면 문서(텍스트 또는 base64 blob, URI·MIME 타입 포함)를 결과에 넣고, ResourceLink는 포인터만 보내요.
  • 미디어 결과는 structured_content도 출력 스키마도 없어요.
  • Icon은 포인터예요: src URI + 선택적 mime_type, sizes, theme.
  • icons=[...]는 서버, 도구, 리소스, 프롬프트에서 동작하고 클라이언트는 매칭 객체에서 찾아요.

그게 도구가 결과에 넣을 수 있는 모든 것이에요. 도구가 실패했을 때 일어나는 일(그리고 누가 알게 되는지)은 **에러 처리**예요.