프롬프트
프롬프트 (Prompts)
프롬프트(prompt) 는 사용자가 고르는 메시지 템플릿입니다.
도구는 모델을 위한 것입니다. 프롬프트는 그 반대예요. 사용자가 클라이언트의 메뉴(슬래시 명령, 버튼)에서 하나를 고르고 인자를 채우면, 렌더링된 메시지가 마치 직접 타이핑한 것처럼 대화에 들어갑니다.
텍스트를 반환하는 함수에 @mcp.prompt()를 붙이면 선언됩니다.
첫 번째 프롬프트
from mcp.server import MCPServer
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
SDK는 도구에서 읽는 것과 같은 세 가지를 읽습니다.
- 이름은 함수 이름입니다:
review_code. - 클라이언트가 보여주는 설명은 docstring입니다:
Review a piece of code. - 인자는 파라미터에서 옵니다.
code는 기본값이 없으므로 필수입니다.
그게 클라이언트가 prompts/list에서 돌려받는 것입니다.
{
"name": "review_code",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "required": true}
]
}
여기엔 JSON Schema가 없습니다. 프롬프트 인자는 이름 붙은 문자열 값의 평평한 리스트입니다. 모델이 구성하는 페이로드가 아니라 사람이 채우는 폼이죠.
렌더링하기
클라이언트는 인자를 넘겨 prompts/get으로 템플릿을 렌더링합니다. 함수가 실행되고 반환한 str이 사용자 메시지 하나가 됩니다.
{
"description": "Review a piece of code.",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this code:\n\ndef add(a, b): return a + b"
}
}
],
"resultType": "complete"
}
그게 프롬프트의 전체 생애입니다. 이름으로 나열되고, 요청 시 렌더링되고, 채팅에 들어가죠.
!!! check
required는 함수가 실행되기 전에 강제됩니다. code 없이 review_code를 렌더링하면 요청 자체가 JSON-RPC 오류(코드 -32603)로 실패합니다.
```text
mcp.shared.exceptions.MCPError: Internal server error
```
모델에게 돌려줄 도구 스타일의 오류 결과는 없습니다. 루프에 모델이 없으니까요. 호출이 그냥 raise합니다. 이유(`Missing required arguments: {'code'}`)는 여러분 서버의 로그에 남습니다.
직접 해 보기
MCP Inspector로 서버를 실행합니다.
uv run mcp dev server.py
Prompts 탭을 열고 review_code를 선택합니다. Inspector는 필수 code 필드가 하나 있는 폼을 그립니다. 채우고 렌더링하면 정확히 위의 사용자 메시지를 돌려받습니다.
메시지가 하나보다 많을 때
코드 리뷰는 메시지 하나입니다. 디버깅 세션은 대화이고, 프롬프트가 그 전체의 씨앗이 될 수 있어요.
str 대신 메시지 리스트를 반환하세요.
from mcp.server import MCPServer
from mcp.server.mcpserver.prompts.base import AssistantMessage, Message, UserMessage
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
@mcp.prompt()
def debug_error(error: str) -> list[Message]:
"""Start a debugging conversation."""
return [
UserMessage("I'm seeing this error:"),
UserMessage(error),
AssistantMessage("I'll help debug that. What have you tried so far?"),
]
UserMessage와AssistantMessage는mcp.server.mcpserver.prompts.base에서 옵니다.str을 넘기면TextContent로 감싸 줍니다. 역할(role)은 클래스 이름이죠.Message는 그 공통 베이스입니다. 반환 어노테이션으로 쓰세요.
debug_error를 렌더링하면 이제 메시지 세 개가 순서대로 나옵니다.
{
"description": "Start a debugging conversation.",
"messages": [
{"role": "user", "content": {"type": "text", "text": "I'm seeing this error:"}},
{"role": "user", "content": {"type": "text", "text": "TypeError: 'int' object is not iterable"}},
{
"role": "assistant",
"content": {"type": "text", "text": "I'll help debug that. What have you tried so far?"}
}
],
"resultType": "complete"
}
마지막 것을 주목하세요. assistant 턴을 미리 채우는 것은 사용자가 직접 타이핑하지 않아도 모델의 다음 답변을 유도하는 방법입니다.
타이틀과 인자 설명
review_code는 함수 이름이지 라벨이 아닙니다. 클라이언트가 버튼에 넣을 더 나은 것을 주고, 각 인자를 설명해서 폼이 스스로 설명하게 하세요.
from typing import Annotated
from pydantic import Field
from mcp.server import MCPServer
mcp = MCPServer("Code Helper")
@mcp.prompt(title="Code review")
def review_code(
code: Annotated[str, Field(description="The code to review.")],
language: Annotated[str, Field(description="The language the code is written in.")] = "python",
) -> str:
"""Review a piece of code."""
return f"Please review this {language} code:\n\n{code}"
title="Code review"는 도구의title과 정확히 같은, 사람이 읽을 수 있는 이름입니다.Annotated[str, Field(description=...)]는 Tools 가 도구 파라미터를 설명하는 데 쓰는 것과 같은 패턴입니다. 여기선 설명이 스키마가 아니라 인자에 붙지요.language는 기본값이 있어서 필수에서 빠집니다.
prompts/list 항목은 이제 클라이언트가 좋은 폼을 그리는 데 필요한 전부를 담고 있습니다.
{
"name": "review_code",
"title": "Code review",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "description": "The code to review.", "required": true},
{"name": "language", "description": "The language the code is written in.", "required": false}
]
}
!!! info
Tools 를 읽었다면 이 시점까지는 이미 전부 아는 것입니다. 같은 데코레이터, 같은 docstring-as-description, 같은 Annotated/Field. 바뀌는 건 무엇이 트리거하느냐(사용자)와 결과가 어디로 가느냐(대화 속으로)뿐입니다.
텍스트보다 더
UserMessage와 AssistantMessage는 str을 받는 자리에 콘텐츠 블록이나 Image/Audio 헬퍼도 받습니다. 프롬프트에서 자주 나오는 두 경우는 문서 붙이기와 그림 붙이기입니다.
파일 임베딩하기
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver import Message, UserMessage
from mcp.types import EmbeddedResource, TextResourceContents
mcp = MCPServer("Code Helper")
STYLE_GUIDE_FILE = Path(__file__).parent / "style-guide.md" # or the path to your file on disk
@mcp.resource("style://python", mime_type="text/markdown")
def style_guide() -> str:
"""The team's Python style guide."""
return STYLE_GUIDE_FILE.read_text(encoding="utf-8")
@mcp.prompt()
def review_code(code: str) -> list[Message]:
"""Review a piece of code against the team style guide."""
guide = TextResourceContents(uri="style://python", mime_type="text/markdown", text=style_guide())
return [
UserMessage(EmbeddedResource(resource=guide)),
UserMessage(f"Review this code against the style guide above:\n\n{code}"),
]
- 스타일 가이드는
style://python의 리소스입니다(그런 것은 Resources 가 다룹니다).server.py옆의style-guide.md에서 읽어요. 아무 Markdown 파일이나 거기 두면 됩니다. EmbeddedResource(resource=TextResourceContents(...))— 둘 다mcp.types에서 — 파일을 URI와 MIME 타입과 함께 첫 번째 메시지로 나릅니다. 그것을 가리키는 요청은 평문으로 뒤따르죠.- 가이드를 f-string에 붙여 넣는 대신 임베딩하면, 클라이언트가 첨부로 표시하고 나중에
style://python을 다시 열 수 있으며, 모델은 파일을 그대로 받습니다. 바이너리 파일은 base64blob과 함께BlobResourceContents를 쓰세요.
렌더링되면 첫 메시지의 content는 resource 블록입니다.
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
이미지 붙이기
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver import Image, Message, UserMessage
mcp = MCPServer("Code Helper")
DIAGRAM_FILE = Path(__file__).parent / "architecture.png" # or the path to your file on disk
@mcp.prompt()
def explain_component(component: str) -> list[Message]:
"""Explain one component using the architecture diagram."""
return [
UserMessage(Image(path=DIAGRAM_FILE)),
UserMessage(f"Where does {component} sit in this architecture, and what does it talk to?"),
]
Image는 Images, audio & icons 의 헬퍼입니다.UserMessage는 프롬프트가 렌더링될 때 그걸ImageContent블록으로 변환합니다(파일은 base64, MIME 타입은.png에서 추측).Audio는 같은 방식으로AudioContent가 돼요.server.py옆에architecture.png라는 아무 PNG나 두세요. 프롬프트 인자는 문자열이라 그림은 항상 서버에서 오고,component는 단어만 공급합니다.
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
런타임에 목록 바꾸기
클라이언트가 연결된 동안 프롬프트를 추가할 수 있습니다. 예를 들어 사용자가 지시문을 자신만의 메뉴 항목으로 저장하게 하는 경우죠. 프롬프트를 등록한 다음 알리면 됩니다.
from contextlib import suppress
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
from mcp.server.mcpserver.prompts import Prompt
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
@mcp.tool()
async def save_template(name: str, instruction: str, ctx: Context) -> str:
"""Save an instruction as a prompt the user can pick from the menu."""
def template(code: str) -> str:
return f"{instruction}\n\n{code}"
with suppress(ValueError): # replace an existing entry of the same name
mcp.remove_prompt(name)
mcp.add_prompt(Prompt.from_function(template, name=name, description=instruction))
await ctx.notify_prompts_changed()
await ctx.session.send_prompt_list_changed()
return f"Saved '{name}' to the prompt menu."
mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))는@mcp.prompt()가 그랬을 것처럼 정확히 함수를 등록하고,mcp.remove_prompt(name)은 그 반대입니다.add_prompt는 같은 이름의 기존 항목을 덮어쓰지 않고 유지하므로, 저장을 "교체"로 만들려면 도구가 먼저 옛것을 제거합니다.prompts/list는 즉시 그 변경을 반영합니다.await ctx.notify_prompts_changed()는subscriptions/listen스트림을 듣고 있는 모든2026-07-28클라이언트에게notifications/prompts/list_changed를 보냅니다(Subscriptions).await ctx.session.send_prompt_list_changed()는 그 클라이언트가 2026 이전일 때 호출 클라이언트에 보냅니다(Serving legacy clients). 둘 다 호출하세요. 알릴 상대가 없으면 각각 아무것도 하지 않아요.- 알림을 받은 클라이언트는
prompts/list를 다시 호출합니다. PythonClient에서는async with client.listen(prompts_list_changed=True) as sub:가PromptsListChanged이벤트를 yield합니다.
정리(Recap)
- 함수에
@mcp.prompt()를 붙이면 프롬프트가 됩니다. 이름은 함수에서, 설명은 docstring에서. - 프롬프트는 사용자가 제어합니다. 클라이언트가 나열하고, 사용자가 고르고 인자를 채우죠.
- 인자는 이름 붙은 문자열의 평평한 리스트입니다(스키마 없음). 기본값이 있는 파라미터는 선택적입니다.
str을 반환하면 사용자 메시지 하나가 됩니다.UserMessage/AssistantMessage리스트를 반환하면 다중 턴 대화의 씨앗이 됩니다.title=과Field(description=...)가 클라이언트가 UI에 넣는 것들입니다.- 필수 인자가 없으면 요청 전체가 실패합니다. 프롬프트별 오류 결과는 없어요.
UserMessage에EmbeddedResource나Image를 감싸면 문서나 그림을 붙일 수 있습니다.- 런타임에
mcp.add_prompt(...)/mcp.remove_prompt(...)로 프롬프트를 추가·제거한 다음await ctx.notify_prompts_changed()와await ctx.session.send_prompt_list_changed()를 호출하세요.
프롬프트(또는 리소스 템플릿) 인자의 서버 측 자동완성은 Completions 입니다.