chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""`docs/servers/completions.md`: every claim the page makes, proved against the real SDK."""
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp_types import (
|
||||
Completion,
|
||||
CompletionContext,
|
||||
CompletionsCapability,
|
||||
ErrorData,
|
||||
PromptReference,
|
||||
ResourceTemplateReference,
|
||||
)
|
||||
|
||||
from docs_src.completions import tutorial001, tutorial002, tutorial003
|
||||
from mcp import Client, MCPError
|
||||
|
||||
# See test_index.py for why this is a per-module mark and not a conftest hook.
|
||||
pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")]
|
||||
|
||||
TEMPLATE_REF = ResourceTemplateReference(uri="github://repos/{owner}/{repo}")
|
||||
PROMPT_REF = PromptReference(name="review_code")
|
||||
|
||||
|
||||
async def test_a_server_with_no_handler_has_no_completions_capability() -> None:
|
||||
"""tutorial001: there is something worth completing, but no handler and no advertised capability."""
|
||||
async with Client(tutorial001.mcp) as client:
|
||||
(template,) = (await client.list_resource_templates()).resource_templates
|
||||
assert template.uri_template == "github://repos/{owner}/{repo}"
|
||||
(prompt,) = (await client.list_prompts()).prompts
|
||||
assert prompt.name == "review_code"
|
||||
assert client.server_capabilities.completions is None
|
||||
|
||||
|
||||
async def test_completing_without_a_handler_is_method_not_found() -> None:
|
||||
"""tutorial001: nothing handles `completion/complete`, so the request is a JSON-RPC error."""
|
||||
async with Client(tutorial001.mcp) as client:
|
||||
with pytest.raises(MCPError) as excinfo:
|
||||
await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "py"})
|
||||
assert excinfo.value.error == ErrorData(code=-32601, message="Method not found", data="completion/complete")
|
||||
|
||||
|
||||
async def test_registering_the_handler_advertises_the_capability() -> None:
|
||||
"""tutorial002: `@mcp.completion()` is the whole declaration; the capability is derived from it."""
|
||||
async with Client(tutorial002.mcp) as client:
|
||||
assert client.server_capabilities.completions == CompletionsCapability()
|
||||
|
||||
|
||||
async def test_prompt_argument_completion_filters_on_the_typed_prefix() -> None:
|
||||
"""tutorial002: the handler returns the languages that start with `argument.value`."""
|
||||
async with Client(tutorial002.mcp) as client:
|
||||
result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "py"})
|
||||
assert result.completion == snapshot(Completion(values=["python"]))
|
||||
|
||||
|
||||
async def test_empty_value_returns_every_suggestion() -> None:
|
||||
"""tutorial002: an empty prefix matches everything, so the client gets the whole list."""
|
||||
async with Client(tutorial002.mcp) as client:
|
||||
result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": ""})
|
||||
assert result.completion.values == ["go", "javascript", "python", "rust", "typescript"]
|
||||
|
||||
|
||||
async def test_returning_none_is_an_empty_list_not_an_error() -> None:
|
||||
"""tutorial002: an argument the handler does not recognise produces `values=[]`, never a failure."""
|
||||
async with Client(tutorial002.mcp) as client:
|
||||
result = await client.complete(ref=PROMPT_REF, argument={"name": "code", "value": "x"})
|
||||
assert result.completion == snapshot(Completion(values=[]))
|
||||
result = await client.complete(ref=TEMPLATE_REF, argument={"name": "repo", "value": ""})
|
||||
assert result.completion.values == []
|
||||
|
||||
|
||||
async def test_context_arguments_resolve_a_dependent_parameter() -> None:
|
||||
"""tutorial003: the already-resolved `owner` arrives in `context.arguments` and picks the repo list."""
|
||||
async with Client(tutorial003.mcp) as client:
|
||||
result = await client.complete(
|
||||
ref=TEMPLATE_REF,
|
||||
argument={"name": "repo", "value": ""},
|
||||
context_arguments={"owner": "modelcontextprotocol"},
|
||||
)
|
||||
assert result.completion == snapshot(Completion(values=["python-sdk", "typescript-sdk", "inspector"]))
|
||||
|
||||
|
||||
async def test_the_typed_prefix_still_filters_a_dependent_parameter() -> None:
|
||||
"""tutorial003: `argument.value` narrows the owner's repos exactly as it narrows a prompt argument."""
|
||||
async with Client(tutorial003.mcp) as client:
|
||||
result = await client.complete(
|
||||
ref=TEMPLATE_REF,
|
||||
argument={"name": "repo", "value": "py"},
|
||||
context_arguments={"owner": "modelcontextprotocol"},
|
||||
)
|
||||
assert result.completion.values == ["python-sdk"]
|
||||
|
||||
|
||||
def test_context_arguments_is_optional() -> None:
|
||||
"""tutorial003: `context.arguments` is `dict[str, str] | None`; the handler's `None` guard is required."""
|
||||
assert CompletionContext.model_fields["arguments"].annotation == (dict[str, str] | None)
|
||||
assert CompletionContext().arguments is None
|
||||
|
||||
|
||||
async def test_no_context_means_no_suggestions() -> None:
|
||||
"""tutorial003: without a resolved `owner` (or with an unknown one) the handler has nothing to offer."""
|
||||
async with Client(tutorial003.mcp) as client:
|
||||
result = await client.complete(ref=TEMPLATE_REF, argument={"name": "repo", "value": ""})
|
||||
assert result.completion.values == []
|
||||
result = await client.complete(
|
||||
ref=TEMPLATE_REF,
|
||||
argument={"name": "repo", "value": ""},
|
||||
context_arguments={"owner": "nobody"},
|
||||
)
|
||||
assert result.completion.values == []
|
||||
|
||||
|
||||
async def test_the_prompt_branch_is_untouched_by_the_new_one() -> None:
|
||||
"""tutorial003: adding the resource-template branch leaves prompt-argument completion as it was."""
|
||||
async with Client(tutorial003.mcp) as client:
|
||||
result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "type"})
|
||||
assert result.completion.values == ["typescript"]
|
||||
Reference in New Issue
Block a user