コンテンツにスキップ

補完

機械翻訳

このページは英語版ドキュメントから自動翻訳されたものであり、正式な版は英語版のページです。不自然な箇所があれば、翻訳についてで報告の方法を説明しています。

サーバーの上に UI を構築するクライアントは、ユーザーの入力に合わせて引数の値を自動補完したいと考えます。言語名、リポジトリ名、ファイルパスなどです。

補完(completions)は、サーバーがそうした候補を提供するための仕組みです。

補完する対象を用意する

補完が適用されるのはちょうど 2 つだけです。プロンプトの引数と、リソーステンプレートのパラメーターです。そこで、まずはその両方を 1 つずつ持つサーバーから始めます。

server.py
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_codelanguage を受け取ります。どの綴りが受け付けられるかをユーザーに推測させるべきではありません。
  • github_repoownerrepo を受け取ります。両方とも自由入力のテキストボックスでは、使いにくいフォームになります。

補完ハンドラー

@mcp.completion() でデコレートした関数を 1 つ追加します。

server.py
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
  • ハンドラーはサーバーごとに 1 つです。補完リクエストはすべてここに届くので、何が補完されているかに応じて分岐します。
  • async def でなければなりません。SDK がこれを await します。
  • 3 つの引数を受け取ります。
  • ref:「どの」プロンプトまたはリソーステンプレートかを表し、PromptReferenceResourceTemplateReference のどちらかです。見分けるには isinstance を使います。
  • argumentargument.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 はハンドラーが受け取るのと同じ参照型です。
  • argumentnamevalue のちょうど 2 つのキーを持つ、普通の 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 がハンドラーを見つけて、代わりにケイパビリティを宣言したのです。「オプション」のケイパビリティはすべてこの仕組みで動きます。ハンドラーが宣言そのものです。(3 つのプリミティブはオプションではありません。MCPServer はハンドラーの有無にかかわらず常にそれらを宣言します。)

Check

最初の server.py(ハンドラーのないほう)に戻り、それでも問い合わせてみてください。呼び出しは JSON-RPC エラーで失敗します。

Method not found

そして client.server_capabilities.completionsNone です。これこそがケイパビリティの存在意義です。行儀のよいクライアントはこれを確認し、応答できないリクエストは最初から送りません。

依存する引数

github://repos/{owner}/{repo} にはパラメーターが 2 つあり、repo として意味のある値は、先にどの owner が選ばれたかによって変わります。

そのためにあるのが context です。ユーザーがすでに解決した引数を運びます。

server.py
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= で送ります。今回の refResourceTemplateReference(uri="github://repos/{owner}/{repo}") です。空の valuerepo を要求し、context_arguments={"owner": "modelcontextprotocol"} を渡します。

result.completion.values  # ['python-sdk', 'typescript-sdk', 'inspector']

context_arguments= を外すと、同じ呼び出しが [] を返します。ハンドラーは、オーナーがわかるまでどのリポジトリを提示すべきか知りようがありません。

Info

Completiontotal=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)です。ツールがテキスト以外に返せるものはすべて画像、音声、アイコンにまとめてあります。