자동 완성
기계 번역
이 페이지는 영어 문서를 자동으로 번역한 것이며, 영어 페이지가 기준이 되는 정식 버전입니다. 어색하거나 잘못된 부분이 있다면 번역 페이지에서 제보하는 방법을 확인하세요.
서버 위에 UI를 만드는 클라이언트는 사용자가 입력하는 동안 인수 값을 자동 완성하고 싶어 합니다. 언어 이름, 리포지토리 이름, 파일 경로 같은 것들입니다.
자동 완성(completions)은 서버가 이런 제안을 제공하는 방법입니다.
자동 완성할 만한 것
자동 완성은 정확히 두 가지에만 적용됩니다. 프롬프트의 인수와 리소스 템플릿의 매개변수입니다. 그러니 각각 하나씩 가진 서버로 시작하세요.
from mcp.server import MCPServer
mcp = MCPServer("GitHub Explorer")
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
아직 자동 완성과 관련된 것은 아무것도 없습니다.
review_code는language를 받습니다. 서버가 어떤 철자를 허용하는지 사용자가 추측해야 해서는 안 됩니다.github_repo는owner와repo를 받습니다. 둘 다 자유 입력 칸으로 두면 좋은 폼이 아닙니다.
자동 완성 핸들러
@mcp.completion()으로 데코레이트한 함수를 하나 추가하세요.
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("GitHub Explorer")
LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
if isinstance(ref, PromptReference) and argument.name == "language":
return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
return None
- 핸들러는 서버당 하나입니다. 모든 자동 완성 요청이 여기로 들어오며, 무엇을 자동 완성하는지에 따라 분기합니다.
- 반드시
async def여야 합니다. SDK가 이 함수를 await합니다. - 인수 세 개를 받습니다.
ref: 어떤 프롬프트 또는 리소스 템플릿인지를PromptReference또는ResourceTemplateReference로 나타냅니다. 둘을 구분할 때는isinstance를 사용합니다.argument:argument.name은 자동 완성 중인 인수이고,argument.value는 사용자가 지금까지 입력한 내용입니다.context: 이미 확정된 인수입니다. 지금은 무시하세요.Completion(values=[...])을 반환하거나, 제안할 것이 없으면None을 반환합니다.
Tip
argument.value는 사용자가 입력한 접두사입니다. SDK는 대신 필터링해 주지 않습니다.
values에 넣은 것이 그대로 UI에 표시됩니다. startswith는 직접 작성해야 합니다.
직접 해 보기
테스트의 인메모리 Client로 실행해 보세요.
ref=PromptReference(name="review_code")와
argument={"name": "language", "value": "py"}로 client.complete()를 호출하세요.
result.completion.values # ['python']
ref는 핸들러가 받는 것과 같은 참조 타입입니다.argument는name과value라는 키 두 개만 가진 평범한 dict입니다.
빈 value를 보내면 전체 목록이 돌아옵니다. lang.startswith("")는 모든 언어에서 참이기 때문입니다.
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
code(핸들러가 인식하지 못하는 인수)를 물어보면 None을 반환하고, SDK는 이를 빈 목록으로 바꿉니다.
result.completion.values # []
None은 "제안 없음"을 뜻할 뿐, 결코 오류가 아닙니다. UI는 일반 텍스트 입력 칸으로 대체합니다.
선언한 적 없는 기능
핸들러를 등록하는 것이 곧 선언입니다. 클라이언트를 연결하고 확인해 보세요.
client.server_capabilities.completions # CompletionsCapability()
어디에도 completions를 나열하지 않았습니다. SDK가 핸들러를 보고 기능을 대신 선언했습니다. 모든 선택적 기능은 이런 식으로 동작합니다. 핸들러가 곧 선언입니다. (세 가지 프리미티브는 선택적이지 않습니다. MCPServer는 핸들러가 있든 없든 항상 이 세 가지를 선언합니다.)
Check
첫 번째 server.py(핸들러가 없는 버전)로 돌아가서 그래도 요청해 보세요. 호출은
JSON-RPC 오류와 함께 실패합니다.
Method not found
그리고 client.server_capabilities.completions는 None입니다. 이것이 바로 기능의 의의입니다.
제대로 동작하는 클라이언트는 기능을 확인하고, 서버가 응답할 수 없는 요청은 아예 보내지 않습니다.
의존하는 인수
github://repos/{owner}/{repo}에는 매개변수가 두 개 있고, repo에 유용한 값은 먼저 어떤 owner를 골랐는지에 따라 달라집니다.
context는 바로 이를 위한 것입니다. 사용자가 이미 확정한 인수를 담고 있습니다.
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("GitHub Explorer")
LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]
REPOS_BY_OWNER = {
"modelcontextprotocol": ["python-sdk", "typescript-sdk", "inspector"],
"pydantic": ["pydantic", "pydantic-ai", "logfire"],
}
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
if isinstance(ref, PromptReference) and argument.name == "language":
return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
if isinstance(ref, ResourceTemplateReference) and argument.name == "repo":
if context is None or context.arguments is None:
return None
repos = REPOS_BY_OWNER.get(context.arguments.get("owner", ""), [])
return Completion(values=[repo for repo in repos if repo.startswith(argument.value)])
return None
- 새 분기는 템플릿의
repo매개변수에 대해 실행됩니다. context.arguments는 지금까지 선택된 값(여기서는owner)을 담은dict[str, str] | None입니다.- 아직
owner가 없으면 의미 있는 제안도 없으므로, 핸들러는None을 반환합니다.
클라이언트는 확정된 값을 context_arguments=로 보냅니다. 이번에는 ref가
ResourceTemplateReference(uri="github://repos/{owner}/{repo}")입니다. 빈 value로 repo를
요청하면서 context_arguments={"owner": "modelcontextprotocol"}를 전달하세요.
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
context_arguments=를 빼면 같은 호출이 []를 반환합니다. 핸들러는 owner를 알기 전까지는 어떤 리포지토리를 제안해야 할지 알 수 없습니다.
Info
Completion은 total=과 has_more=도 받습니다. values가 더 긴 목록의 일부일 때 설정하면
UI가 "외 200개"처럼 표시할 수 있습니다. 대부분의 핸들러에는 필요하지 않습니다.
요약
- 자동 완성은 프롬프트 인수와 리소스 템플릿 매개변수에 대한 제안입니다. 그 외에는 없습니다.
@mcp.completion()은 하나뿐인 핸들러를 등록합니다. 형태는async def (ref, argument, context) -> Completion | None입니다.isinstance(ref, ...)와argument.name으로 분기하세요.argument.value로 필터링하는 것은 직접 해야 합니다.None은 빈 목록이 됩니다. 결코 오류가 아닙니다.context.arguments는 이미 확정된 값을 담고 있으며, 클라이언트는 이를context_arguments=로 제공합니다.completions기능은 핸들러를 등록하는 순간 나타납니다. 핸들러가 없으면 요청은Method not found가 됩니다.
제안은 사용자가 프롬프트나 템플릿을 아직 채우고 있는 동안 도움이 됩니다. 도구 호출 도중에 사용자에게 질문하려면 엘리시테이션(elicitation)이 필요합니다. 도구가 텍스트 외에 반환할 수 있는 모든 것은 이미지, 오디오, 아이콘에서 확인하세요.