Preserve string-compatible prompt arguments (#4730)

This commit is contained in:
Jeremiah Lowin 2026-08-02 14:56:05 -04:00 committed by GitHub
commit 022547ad8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 104 additions and 18 deletions

View file

@ -1,5 +1,9 @@
from pathlib import Path
from typing import Annotated, Any
import pytest
from mcp_types import EmbeddedResource, TextResourceContents
from pydantic import Field
from fastmcp.prompts.base import (
Message,
@ -313,8 +317,75 @@ class TestPromptTypeConversion:
assert result.messages == [Message("Hello world (repeated 3 times)")]
@pytest.mark.parametrize(
("annotation", "value"),
[
(Annotated[str, Field(description="Text")], '"hello"'),
(str | None, "null"),
(Any, "123"),
(object, "true"),
(int | str, "42"),
],
)
async def test_string_compatible_annotations_preserve_wire_strings(
self, annotation: Any, value: str
):
def typed_prompt(value):
return f"{type(value).__name__}:{value!r}"
typed_prompt.__annotations__ = {"value": annotation, "return": str}
prompt = Prompt.from_function(typed_prompt)
result = await prompt.render(arguments={"value": value})
assert result.messages == [Message(f"str:{value!r}")]
async def test_optional_non_string_still_decodes_json_null(self):
def optional_integer_prompt(value: int | None) -> str:
return f"{type(value).__name__}:{value!r}"
prompt = Prompt.from_function(optional_integer_prompt)
result = await prompt.render(arguments={"value": "null"})
assert result.messages == [Message("NoneType:None")]
@pytest.mark.parametrize(
("annotation", "value", "expected"),
[
(bytes, '"hello"', b"hello"),
(Path, '"folder/file.txt"', Path("folder/file.txt")),
],
)
async def test_string_coercible_non_string_annotations_decode_json(
self, annotation: Any, value: str, expected: Any
):
def typed_prompt(value):
return f"{type(value).__name__}:{value!r}"
typed_prompt.__annotations__ = {"value": annotation, "return": str}
prompt = Prompt.from_function(typed_prompt)
result = await prompt.render(arguments={"value": value})
assert result.messages == [Message(f"{type(expected).__name__}:{expected!r}")]
class TestPromptArgumentDescriptions:
def test_string_compatible_annotation_guidance_preserves_raw_strings(self):
def documented_prompt(
text: Annotated[str, Field(description="Text")],
) -> str:
return text
prompt = Prompt.from_function(documented_prompt)
assert prompt.arguments is not None
text_arg = next(arg for arg in prompt.arguments if arg.name == "text")
assert text_arg.description is not None
assert "Provide as a JSON string" not in text_arg.description
assert "Encode non-string values as JSON." in text_arg.description
def test_enhanced_descriptions_for_non_string_types(self):
"""Test that non-string argument types get enhanced descriptions with JSON schema."""
@ -343,7 +414,7 @@ class TestPromptArgumentDescriptions:
assert numbers_arg is not None
assert numbers_arg.description is not None
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
in numbers_arg.description
)
assert '{"items":{"type":"integer"},"type":"array"}' in numbers_arg.description
@ -354,7 +425,7 @@ class TestPromptArgumentDescriptions:
assert metadata_arg is not None
assert metadata_arg.description is not None
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
in metadata_arg.description
)
assert (
@ -368,7 +439,7 @@ class TestPromptArgumentDescriptions:
assert threshold_arg is not None
assert threshold_arg.description is not None
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
in threshold_arg.description
)
assert '{"type":"number"}' in threshold_arg.description
@ -379,7 +450,7 @@ class TestPromptArgumentDescriptions:
assert active_arg is not None
assert active_arg.description is not None
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
in active_arg.description
)
assert '{"type":"boolean"}' in active_arg.description
@ -410,7 +481,7 @@ class TestPromptArgumentDescriptions:
assert "A list of integers to process" in numbers_arg.description
assert "\n\n" in numbers_arg.description # Should have newline separator
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
in numbers_arg.description
)
@ -427,7 +498,7 @@ class TestPromptArgumentDescriptions:
# String parameters should not have schema enhancement
if arg.description is not None:
assert (
"Provide as a JSON string matching the following schema:"
"Provide a value matching the following JSON schema:"
not in arg.description
)