미디어 (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)
Image는path(읽을 파일) 또는data(원시 바이트) 중 정확히 하나를 받아요.- 클라이언트가 보는 MIME 타입은 접미사에서 추측돼요:
logo.png는image/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_content는None이에요.Image는 애플리케이션이 파싱할 데이터가 아니라 모델이 볼 콘텐츠예요. 출력 스키마가 없어요. (반환 애너테이션 자체가 스키마인 **구조화된 출력**과 대조돼요.)
!!! info
ImageContent와 AudioContent는 mcp.types에, 평범한 str 결과가 되는 TextContent 바로 옆에 있어요. 도구 결과는 콘텐츠 블록 목록이에요. Image와 Audio는 두 바이너리 종류를 만드는 가장 짧은 길이에요.
실행해 보기
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를 유지해요.EmbeddedResource와TextResourceContents는mcp.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_type과sizes("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/list의 Tool 객체, 리소스의 것은 resources/list의 Resource, 프롬프트의 것은 prompts/list의 Prompt에 있어요. 필드 이름은 항상 icons예요.
요약
- 도구에서
Image나Audio를 반환하면 클라이언트는ImageContent/AudioContent블록을 받아요: 바이트를 base64로 인코딩하고 MIME 타입 포함. path=에서 만들면 접미사가 MIME 타입을 결정하고, 메모리data=는 명시적format=과 함께 써요.EmbeddedResource를 반환하면 문서(텍스트 또는 base64 blob, URI·MIME 타입 포함)를 결과에 넣고,ResourceLink는 포인터만 보내요.- 미디어 결과는
structured_content도 출력 스키마도 없어요. Icon은 포인터예요:srcURI + 선택적mime_type,sizes,theme.icons=[...]는 서버, 도구, 리소스, 프롬프트에서 동작하고 클라이언트는 매칭 객체에서 찾아요.
그게 도구가 결과에 넣을 수 있는 모든 것이에요. 도구가 실패했을 때 일어나는 일(그리고 누가 알게 되는지)은 **에러 처리**예요.