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,134 @@
|
||||
"""Completion interactions against the low-level Server, driven through the public Client API."""
|
||||
|
||||
import mcp_types as types
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp_types import (
|
||||
INVALID_PARAMS,
|
||||
METHOD_NOT_FOUND,
|
||||
CompleteResult,
|
||||
Completion,
|
||||
ErrorData,
|
||||
PromptReference,
|
||||
ResourceTemplateReference,
|
||||
)
|
||||
|
||||
from mcp import MCPError
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
from tests.interaction._connect import Connect
|
||||
from tests.interaction._requirements import requirement
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
@requirement("completion:prompt-arg")
|
||||
@requirement("completion:result-shape")
|
||||
async def test_complete_prompt_argument(connect: Connect) -> None:
|
||||
"""Completing a prompt argument delivers the ref, argument name, and current value to the handler.
|
||||
|
||||
The returned values are filtered by the argument's value, proving the value reached the handler.
|
||||
"""
|
||||
|
||||
async def completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> CompleteResult:
|
||||
assert isinstance(params.ref, PromptReference)
|
||||
assert params.ref.name == "code_review"
|
||||
assert params.argument.name == "language"
|
||||
candidates = ["python", "pytorch", "ruby"]
|
||||
matches = [candidate for candidate in candidates if candidate.startswith(params.argument.value)]
|
||||
return CompleteResult(completion=Completion(values=matches, total=len(matches), has_more=False))
|
||||
|
||||
server = Server("completer", on_completion=completion)
|
||||
|
||||
async with connect(server) as client:
|
||||
result = await client.complete(
|
||||
PromptReference(name="code_review"), argument={"name": "language", "value": "py"}
|
||||
)
|
||||
|
||||
assert result == snapshot(
|
||||
CompleteResult(completion=Completion(values=["python", "pytorch"], total=2, has_more=False))
|
||||
)
|
||||
|
||||
|
||||
@requirement("completion:resource-template-arg")
|
||||
async def test_complete_resource_template_variable(connect: Connect) -> None:
|
||||
"""Completing a URI template variable delivers the template URI and variable name to the handler."""
|
||||
|
||||
async def completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> CompleteResult:
|
||||
assert isinstance(params.ref, ResourceTemplateReference)
|
||||
assert params.ref.uri == "github://repos/{owner}/{repo}"
|
||||
assert params.argument.name == "owner"
|
||||
return CompleteResult(completion=Completion(values=[f"{params.argument.value}contextprotocol"]))
|
||||
|
||||
server = Server("completer", on_completion=completion)
|
||||
|
||||
async with connect(server) as client:
|
||||
result = await client.complete(
|
||||
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"),
|
||||
argument={"name": "owner", "value": "model"},
|
||||
)
|
||||
|
||||
assert result == snapshot(CompleteResult(completion=Completion(values=["modelcontextprotocol"])))
|
||||
|
||||
|
||||
@requirement("completion:context-arguments")
|
||||
async def test_complete_receives_context_arguments(connect: Connect) -> None:
|
||||
"""Previously-resolved arguments passed as completion context reach the handler.
|
||||
|
||||
The returned value is derived from the context, proving it arrived.
|
||||
"""
|
||||
|
||||
async def completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> CompleteResult:
|
||||
assert params.argument.name == "repo"
|
||||
assert params.context is not None
|
||||
assert params.context.arguments is not None
|
||||
return CompleteResult(completion=Completion(values=[f"{params.context.arguments['owner']}/python-sdk"]))
|
||||
|
||||
server = Server("completer", on_completion=completion)
|
||||
|
||||
async with connect(server) as client:
|
||||
result = await client.complete(
|
||||
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"),
|
||||
argument={"name": "repo", "value": ""},
|
||||
context_arguments={"owner": "modelcontextprotocol"},
|
||||
)
|
||||
|
||||
assert result == snapshot(CompleteResult(completion=Completion(values=["modelcontextprotocol/python-sdk"])))
|
||||
|
||||
|
||||
@requirement("completion:error:invalid-ref")
|
||||
async def test_completion_against_an_unknown_ref_is_rejected_with_invalid_params(connect: Connect) -> None:
|
||||
"""completion/complete with a ref naming an unknown prompt is answered with -32602 Invalid params.
|
||||
|
||||
The lowlevel server does not validate refs itself (it has no prompt/template registry to check
|
||||
against); rejecting an unknown ref is the handler's job, and this test pins the spec-recommended
|
||||
way to do it.
|
||||
"""
|
||||
|
||||
async def completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> CompleteResult:
|
||||
assert isinstance(params.ref, PromptReference)
|
||||
raise MCPError(code=INVALID_PARAMS, message=f"Unknown prompt: {params.ref.name!r}")
|
||||
|
||||
server = Server("completer", on_completion=completion)
|
||||
|
||||
async with connect(server) as client:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.complete(PromptReference(name="ghost"), argument={"name": "x", "value": ""})
|
||||
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
|
||||
|
||||
@requirement("completion:complete:not-supported")
|
||||
@requirement("protocol:error:method-not-found")
|
||||
async def test_complete_without_handler_is_method_not_found(connect: Connect) -> None:
|
||||
"""A server with no completion handler advertises no completions capability and rejects the request."""
|
||||
server = Server("incomplete")
|
||||
|
||||
async with connect(server) as client:
|
||||
assert client.server_capabilities.completions is None
|
||||
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.complete(PromptReference(name="anything"), argument={"name": "topic", "value": ""})
|
||||
|
||||
assert exc_info.value.error == snapshot(
|
||||
ErrorData(code=METHOD_NOT_FOUND, message="Method not found", data="completion/complete")
|
||||
)
|
||||
Reference in New Issue
Block a user