Revert "Revert "Refactor prompt behavior and add meta support (#2600)" (#2608)" (#2610)

This reverts commit f9e29cf58e.
This commit is contained in:
Jeremiah Lowin 2025-12-14 21:52:20 -05:00 committed by GitHub
commit 8abd84bf1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 234 additions and 82 deletions

View file

@ -11,7 +11,7 @@ FastMCP is a comprehensive Python framework (Python ≥3.10) for building Model
```bash
uv sync # Install dependencies
uv run prek run --all-files # Ruff + Prettier + ty
uv run pytest # Run full test suite
uv run pytest -n auto # Run full test suite
```
**All three must pass** - this is enforced by CI. Alternative: `just build && just typecheck && just test`
@ -242,9 +242,9 @@ uv sync # Installs all deps including dev tools
### Testing
- **Standard**: `uv run pytest`
- **Integration**: `uv run pytest -m "integration"`
- **Excluding markers**: `uv run pytest -m "not integration and not client_process"`
- **Standard**: `uv run pytest -n auto`
- **Integration**: `uv run pytest -n auto -m "integration"`
- **Excluding markers**: `uv run pytest -n auto -m "not integration and not client_process"`
### CLI Usage

View file

@ -195,13 +195,14 @@ FastMCP intelligently handles different return types from your prompt function:
- **`str`**: Automatically converted to a single `PromptMessage`.
- **`PromptMessage`**: Used directly as provided. (Note a more user-friendly `Message` constructor is available that can accept raw strings instead of `TextContent` objects.)
- **`list[PromptMessage | str]`**: Used as a sequence of messages (a conversation).
- **`PromptResult`**: Full control over messages, description, and metadata. See [PromptResult](#promptresult) below.
- **`Any`**: If the return type is not one of the above, the return value is attempted to be converted to a string and used as a `PromptMessage`.
```python
from fastmcp.prompts.prompt import Message, PromptResult
from fastmcp.prompts.prompt import Message
@mcp.prompt
def roleplay_scenario(character: str, situation: str) -> PromptResult:
def roleplay_scenario(character: str, situation: str) -> list[Message]:
"""Sets up a roleplaying scenario with initial messages."""
return [
Message(f"Let's roleplay. You are {character}. The situation is: {situation}"),
@ -209,6 +210,43 @@ def roleplay_scenario(character: str, situation: str) -> PromptResult:
]
```
#### PromptResult
<VersionBadge version="2.14.1" />
For complete control over prompt responses, return a `PromptResult` object. This lets you include metadata alongside your prompt messages, which is useful for passing runtime information to clients.
```python
from fastmcp import FastMCP
from fastmcp.prompts import PromptResult, Message
mcp = FastMCP(name="PromptServer")
@mcp.prompt
def code_review(code: str) -> PromptResult:
"""Returns a code review prompt with metadata."""
return PromptResult(
messages=[
Message(f"Please review this code:\n\n```\n{code}\n```"),
],
description="Code review prompt",
meta={"review_type": "security", "priority": "high"}
)
```
`PromptResult` accepts three fields:
**`messages`** - A list of `PromptMessage` or `Message` objects representing the conversation to send to the LLM.
**`description`** - Optional description of the prompt result. If not provided, defaults to the prompt's docstring.
**`meta`** - Optional metadata dictionary that will be included in the MCP response's `_meta` field. Use this for runtime metadata like categorization, priority, or other client-specific data.
<Note>
The `meta` field in `PromptResult` is for runtime metadata specific to this render response. This is separate from the `meta` parameter in `@mcp.prompt(meta={...})`, which provides static metadata about the prompt definition itself (returned when listing prompts).
</Note>
You can still return plain `str`, `PromptMessage`, or lists from your prompt functions—`PromptResult` is opt-in for when you need to include metadata.
### Required vs. Optional Parameters

View file

@ -1,4 +1,4 @@
from .prompt import Prompt, PromptMessage, Message
from .prompt import Message, Prompt, PromptResult, PromptMessage
from .prompt_manager import PromptManager
__all__ = [
@ -6,4 +6,5 @@ __all__ = [
"Prompt",
"PromptManager",
"PromptMessage",
"PromptResult",
]

View file

@ -4,15 +4,18 @@ from __future__ import annotations as _annotations
import inspect
import json
import warnings
from collections.abc import Awaitable, Callable, Sequence
from typing import Annotated, Any
import pydantic_core
from mcp import GetPromptResult
from mcp.types import ContentBlock, Icon, PromptMessage, Role, TextContent
from mcp.types import Prompt as SDKPrompt
from mcp.types import PromptArgument as SDKPromptArgument
from pydantic import Field, TypeAdapter
from fastmcp import settings
from fastmcp.exceptions import PromptError
from fastmcp.server.dependencies import get_context, without_injected_parameters
from fastmcp.server.tasks.config import TaskConfig
@ -40,13 +43,14 @@ def Message(
message_validator = TypeAdapter[PromptMessage](PromptMessage)
SyncPromptResult = (
# Type aliases for what prompt functions can return (before conversion to PromptResult)
_SyncPromptFnReturn = (
str
| PromptMessage
| dict[str, Any]
| Sequence[str | PromptMessage | dict[str, Any]]
)
PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]
_PromptFnReturn = _SyncPromptFnReturn | Awaitable[_SyncPromptFnReturn]
class PromptArgument(FastMCPBaseModel):
@ -61,6 +65,51 @@ class PromptArgument(FastMCPBaseModel):
)
class PromptResult(FastMCPBaseModel):
"""Canonical result type for prompt rendering.
This is the internal type that all prompt renders return. It wraps the
messages with optional description and metadata.
"""
messages: list[PromptMessage] = Field(description="The prompt messages to return")
description: str | None = Field(
default=None, description="Optional description of the prompt result"
)
meta: dict[str, Any] | None = Field(
default=None, description="Optional metadata about the prompt result"
)
@classmethod
def from_value(
cls,
value: list[PromptMessage] | PromptResult,
description: str | None = None,
meta: dict[str, Any] | None = None,
) -> PromptResult:
"""Convert various types to PromptResult."""
if isinstance(value, PromptResult):
# Merge meta if provided
if meta and value.meta:
merged_meta = {**value.meta, **meta}
else:
merged_meta = meta or value.meta
return cls(
messages=value.messages,
description=description or value.description,
meta=merged_meta,
)
return cls(messages=value, description=description, meta=meta)
def to_mcp_prompt_result(self) -> GetPromptResult:
"""Convert to MCP GetPromptResult."""
return GetPromptResult(
description=self.description,
messages=self.messages,
_meta=self.meta,
)
class Prompt(FastMCPComponent):
"""A prompt template that can be rendered with parameters."""
@ -113,7 +162,7 @@ class Prompt(FastMCPComponent):
@staticmethod
def from_function(
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
fn: Callable[..., _PromptFnReturn | Awaitable[_PromptFnReturn]],
name: str | None = None,
title: str | None = None,
description: str | None = None,
@ -146,19 +195,45 @@ class Prompt(FastMCPComponent):
async def render(
self,
arguments: dict[str, Any] | None = None,
) -> list[PromptMessage]:
) -> list[PromptMessage] | PromptResult:
"""Render the prompt with arguments.
This method is not implemented in the base Prompt class and must be
implemented by subclasses.
implemented by subclasses. The preferred return type is PromptResult,
but list[PromptMessage] is still supported for backwards compatibility.
"""
raise NotImplementedError("Subclasses must implement render()")
async def _render(
self,
arguments: dict[str, Any] | None = None,
) -> PromptResult:
"""Internal API that always returns PromptResult.
Calls render() and wraps list[PromptMessage] in PromptResult.
This is what PromptManager calls internally.
"""
result = await self.render(arguments)
if isinstance(result, PromptResult):
return result
# Deprecated in 2.14.1: returning list[PromptMessage] from render()
if settings.deprecation_warnings:
warnings.warn(
f"Prompt.render() returning list[PromptMessage] is deprecated (since 2.14.1). "
f"Return PromptResult instead. "
f"(Prompt: {self.__class__.__name__}, Name: {self.name})",
DeprecationWarning,
stacklevel=2,
)
return PromptResult.from_value(
result, description=self.description, meta=self.meta
)
class FunctionPrompt(Prompt):
"""A prompt that is a function."""
fn: Callable[..., PromptResult | Awaitable[PromptResult]]
fn: Callable[..., _PromptFnReturn | Awaitable[_PromptFnReturn]]
task_config: Annotated[
TaskConfig,
Field(description="Background task execution configuration (SEP-1686)."),
@ -167,7 +242,7 @@ class FunctionPrompt(Prompt):
@classmethod
def from_function(
cls,
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
fn: Callable[..., _PromptFnReturn | Awaitable[_PromptFnReturn]],
name: str | None = None,
title: str | None = None,
description: str | None = None,
@ -322,7 +397,7 @@ class FunctionPrompt(Prompt):
async def render(
self,
arguments: dict[str, Any] | None = None,
) -> list[PromptMessage]:
) -> PromptResult:
"""Render the prompt with arguments."""
# Validate required arguments
if self.arguments:
@ -375,7 +450,11 @@ class FunctionPrompt(Prompt):
"Could not convert prompt result to message."
) from e
return messages
return PromptResult(
messages=messages,
description=self.description,
meta=self.meta,
)
except Exception as e:
logger.exception(f"Error rendering prompt {self.name}")
raise PromptError(f"Error rendering prompt {self.name}.") from e

View file

@ -4,11 +4,14 @@ import warnings
from collections.abc import Awaitable, Callable
from typing import Any
from mcp import GetPromptResult
from fastmcp import settings
from fastmcp.exceptions import NotFoundError, PromptError
from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
from fastmcp.prompts.prompt import (
FunctionPrompt,
Prompt,
PromptResult,
_PromptFnReturn,
)
from fastmcp.settings import DuplicateBehavior
from fastmcp.utilities.logging import get_logger
@ -24,7 +27,11 @@ class PromptManager:
mask_error_details: bool | None = None,
):
self._prompts: dict[str, Prompt] = {}
self.mask_error_details = mask_error_details or settings.mask_error_details
self.mask_error_details = (
settings.mask_error_details
if mask_error_details is None
else mask_error_details
)
# Default to "warn" if None is provided
if duplicate_behavior is None:
@ -58,7 +65,7 @@ class PromptManager:
def add_prompt_from_fn(
self,
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
fn: Callable[..., _PromptFnReturn | Awaitable[_PromptFnReturn]],
name: str | None = None,
description: str | None = None,
tags: set[str] | None = None,
@ -98,18 +105,17 @@ class PromptManager:
self,
name: str,
arguments: dict[str, Any] | None = None,
) -> GetPromptResult:
) -> PromptResult:
"""
Internal API for servers: Finds and renders a prompt, respecting the
filtered protocol path.
"""
prompt = await self.get_prompt(name)
try:
messages = await prompt.render(arguments)
return GetPromptResult(description=prompt.description, messages=messages)
except PromptError as e:
return await prompt._render(arguments)
except PromptError:
logger.exception(f"Error rendering prompt {name!r}")
raise e
raise
except Exception as e:
logger.exception(f"Error rendering prompt {name!r}")
if self.mask_error_details:

View file

@ -17,7 +17,7 @@ from key_value.aio.wrappers.statistics.wrapper import (
from pydantic import BaseModel, Field
from typing_extensions import NotRequired, Self, override
from fastmcp.prompts.prompt import Prompt
from fastmcp.prompts.prompt import Prompt, PromptResult
from fastmcp.resources.resource import Resource, ResourceContent
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
from fastmcp.tools.tool import Tool, ToolResult
@ -220,12 +220,10 @@ class ResponseCachingMiddleware(Middleware):
default_collection="resources/read",
)
self._get_prompt_cache: PydanticAdapter[mcp.types.GetPromptResult] = (
PydanticAdapter(
key_value=self._stats,
pydantic_model=mcp.types.GetPromptResult,
default_collection="prompts/get",
)
self._get_prompt_cache: PydanticAdapter[PromptResult] = PydanticAdapter(
key_value=self._stats,
pydantic_model=PromptResult,
default_collection="prompts/get",
)
self._call_tool_cache: PydanticAdapter[CachableToolResult] = PydanticAdapter(
@ -419,10 +417,8 @@ class ResponseCachingMiddleware(Middleware):
async def on_get_prompt(
self,
context: MiddlewareContext[mcp.types.GetPromptRequestParams],
call_next: CallNext[
mcp.types.GetPromptRequestParams, mcp.types.GetPromptResult
],
) -> mcp.types.GetPromptResult:
call_next: CallNext[mcp.types.GetPromptRequestParams, PromptResult],
) -> PromptResult:
"""Get a prompt from the cache, if caching is enabled, and the result is in the cache. Otherwise,
otherwise call the next middleware and store the result in the cache if caching is enabled."""
if self._get_prompt_settings.get("enabled") is False:
@ -433,7 +429,7 @@ class ResponseCachingMiddleware(Middleware):
if cached_value := await self._get_prompt_cache.get(key=cache_key):
return cached_value
value: mcp.types.GetPromptResult = await call_next(context=context)
value: PromptResult = await call_next(context=context)
await self._get_prompt_cache.put(
key=cache_key,

View file

@ -17,7 +17,7 @@ from typing import (
import mcp.types as mt
from typing_extensions import TypeVar
from fastmcp.prompts.prompt import Prompt
from fastmcp.prompts.prompt import Prompt, PromptResult
from fastmcp.resources.resource import Resource, ResourceContent
from fastmcp.resources.template import ResourceTemplate
from fastmcp.tools.tool import Tool, ToolResult
@ -170,8 +170,8 @@ class Middleware:
async def on_get_prompt(
self,
context: MiddlewareContext[mt.GetPromptRequestParams],
call_next: CallNext[mt.GetPromptRequestParams, mt.GetPromptResult],
) -> mt.GetPromptResult:
call_next: CallNext[mt.GetPromptRequestParams, PromptResult],
) -> PromptResult:
return await call_next(context)
async def on_list_tools(

View file

@ -16,7 +16,6 @@ from mcp.types import (
METHOD_NOT_FOUND,
BlobResourceContents,
ElicitRequestFormParams,
GetPromptResult,
TextResourceContents,
)
from pydantic.networks import AnyUrl
@ -28,7 +27,7 @@ from fastmcp.client.roots import RootsList
from fastmcp.client.transports import ClientTransportT
from fastmcp.exceptions import NotFoundError, ResourceError, ToolError
from fastmcp.mcp_config import MCPConfig
from fastmcp.prompts import Prompt, PromptMessage
from fastmcp.prompts import Prompt, PromptResult
from fastmcp.prompts.prompt import PromptArgument
from fastmcp.prompts.prompt_manager import PromptManager
from fastmcp.resources import Resource, ResourceTemplate
@ -257,7 +256,7 @@ class ProxyPromptManager(PromptManager, ProxyManagerMixin):
self,
name: str,
arguments: dict[str, Any] | None = None,
) -> GetPromptResult:
) -> PromptResult:
"""Renders a prompt, trying local/mounted first, then proxy if not found."""
try:
# First try local and mounted prompts
@ -267,7 +266,12 @@ class ProxyPromptManager(PromptManager, ProxyManagerMixin):
client = await self._get_client()
async with client:
result = await client.get_prompt(name, arguments)
return result
# Convert MCP GetPromptResult to PromptResult
return PromptResult(
messages=result.messages,
description=result.description,
meta=result.meta,
)
class ProxyTool(Tool, MirroredComponent):
@ -524,11 +528,17 @@ class ProxyPrompt(Prompt, MirroredComponent):
_mirrored=True,
)
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]: # type: ignore[override]
async def render(self, arguments: dict[str, Any]) -> PromptResult: # type: ignore[override]
"""Render the prompt by making a call through the client."""
async with self._client:
result = await self._client.get_prompt(self.name, arguments)
return result.messages
# Convert GetPromptResult to PromptResult, preserving runtime meta from the result
# (not the static prompt meta which includes fastmcp tags)
return PromptResult(
messages=result.messages,
description=result.description,
meta=result.meta,
)
class FastMCPProxy(FastMCP):

View file

@ -60,7 +60,7 @@ import fastmcp.server
from fastmcp.exceptions import DisabledError, NotFoundError
from fastmcp.mcp_config import MCPConfig
from fastmcp.prompts import Prompt
from fastmcp.prompts.prompt import FunctionPrompt
from fastmcp.prompts.prompt import FunctionPrompt, PromptResult
from fastmcp.prompts.prompt_manager import PromptManager
from fastmcp.resources.resource import FunctionResource, Resource, ResourceContent
from fastmcp.resources.resource_manager import ResourceManager
@ -1781,8 +1781,18 @@ class FastMCP(Generic[LifespanResultT]):
) -> GetPromptResult:
"""
Applies this server's middleware and delegates the filtered call to the manager.
Converts PromptResult to GetPromptResult for MCP protocol.
"""
result = await self._get_prompt_content_middleware(name, arguments)
return result.to_mcp_prompt_result()
async def _get_prompt_content_middleware(
self, name: str, arguments: dict[str, Any] | None = None
) -> PromptResult:
"""
Applies this server's middleware and returns PromptResult.
Used internally and by parent servers for mounted prompts.
"""
mw_context = MiddlewareContext(
message=mcp.types.GetPromptRequestParams(name=name, arguments=arguments),
source="client",
@ -1797,7 +1807,7 @@ class FastMCP(Generic[LifespanResultT]):
async def _get_prompt(
self,
context: MiddlewareContext[mcp.types.GetPromptRequestParams],
) -> GetPromptResult:
) -> PromptResult:
name = context.message.name
# Try mounted servers in reverse order (later wins)
@ -1816,7 +1826,7 @@ class FastMCP(Generic[LifespanResultT]):
if not self._should_enable_component(prompt):
# Parent filter blocks this prompt, continue searching
continue
return await mounted.server._get_prompt_middleware(
return await mounted.server._get_prompt_content_middleware(
try_name, context.message.arguments
)
except NotFoundError:

View file

@ -16,7 +16,8 @@ class TestRenderPrompt:
return "Hello, world!"
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Hello, world!")
)
@ -27,7 +28,8 @@ class TestRenderPrompt:
return "Hello, world!"
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Hello, world!")
)
@ -38,7 +40,8 @@ class TestRenderPrompt:
return f"Hello, {name}! You're {age} years old."
prompt = Prompt.from_function(fn)
assert await prompt.render(arguments=dict(name="World")) == [
result = await prompt.render(arguments=dict(name="World"))
assert result.messages == [
PromptMessage(
role="user",
content=TextContent(
@ -53,7 +56,8 @@ class TestRenderPrompt:
return f"Hello, {name}!"
prompt = Prompt.from_function(MyPrompt())
assert await prompt.render(arguments=dict(name="World")) == [
result = await prompt.render(arguments=dict(name="World"))
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Hello, World!")
)
@ -65,7 +69,8 @@ class TestRenderPrompt:
return f"Hello, {name}!"
prompt = Prompt.from_function(MyPrompt())
assert await prompt.render(arguments=dict(name="World")) == [
result = await prompt.render(arguments=dict(name="World"))
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Hello, World!")
)
@ -86,7 +91,8 @@ class TestRenderPrompt:
)
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Hello, world!")
)
@ -99,7 +105,8 @@ class TestRenderPrompt:
)
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="assistant", content=TextContent(type="text", text="Hello, world!")
)
@ -119,7 +126,8 @@ class TestRenderPrompt:
return expected
prompt = Prompt.from_function(fn)
assert await prompt.render() == expected
result = await prompt.render()
assert result.messages == expected
async def test_fn_returns_list_of_strings(self):
expected = [
@ -131,7 +139,8 @@ class TestRenderPrompt:
return expected
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(role="user", content=TextContent(type="text", text=t))
for t in expected
]
@ -153,7 +162,8 @@ class TestRenderPrompt:
)
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user",
content=EmbeddedResource(
@ -188,7 +198,8 @@ class TestRenderPrompt:
]
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user",
content=TextContent(type="text", text="Please analyze this file:"),
@ -227,7 +238,8 @@ class TestRenderPrompt:
)
prompt = Prompt.from_function(fn)
assert await prompt.render() == [
result = await prompt.render()
assert result.messages == [
PromptMessage(
role="user",
content=EmbeddedResource(
@ -258,7 +270,7 @@ class TestPromptTypeConversion:
result_from_string = await prompt.render(
arguments={"numbers": "[1, 2, 3, 4, 5]"}
)
assert result_from_string == [
assert result_from_string.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="The sum is: 15")
)
@ -268,7 +280,7 @@ class TestPromptTypeConversion:
result_from_list_string = await prompt.render(
arguments={"numbers": "[1, 2, 3, 4, 5]"}
)
assert result_from_list_string == result_from_string
assert result_from_list_string.messages == result_from_string.messages
async def test_various_type_conversions(self):
"""Test type conversion for various data types."""
@ -298,7 +310,7 @@ class TestPromptTypeConversion:
expected_text = (
"Alice (25): 3 scores, active=True, metadata keys=['project', 'version']"
)
assert result == [
assert result.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text=expected_text)
)
@ -329,7 +341,7 @@ class TestPromptTypeConversion:
# This should work with JSON parsing (integer as string)
result1 = await prompt.render(arguments={"value": "42"})
assert result1 == [
assert result1.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Value: 42")
)
@ -337,7 +349,7 @@ class TestPromptTypeConversion:
# This should work with direct validation (already an integer string)
result2 = await prompt.render(arguments={"value": "123"})
assert result2 == [
assert result2.messages == [
PromptMessage(
role="user", content=TextContent(type="text", text="Value: 123")
)
@ -358,7 +370,7 @@ class TestPromptTypeConversion:
}
)
assert result == [
assert result.messages == [
PromptMessage(
role="user",
content=TextContent(type="text", text="Hello world (repeated 3 times)"),

View file

@ -400,10 +400,10 @@ class TestContextHandling:
context = Context(fastmcp=mcp)
async with context:
messages = await prompt.render(arguments={"x": 42})
result = await prompt.render(arguments={"x": 42})
assert len(messages) == 1
assert messages[0].content.text == "42" # type: ignore[attr-defined]
assert len(result.messages) == 1
assert result.messages[0].content.text == "42" # type: ignore[attr-defined]
async def test_context_optional(self):
"""Test that context is optional when rendering prompts."""
@ -420,12 +420,12 @@ class TestContextHandling:
context = Context(fastmcp=mcp)
async with context:
messages = await prompt.render(
result = await prompt.render(
arguments={"x": 42},
)
assert len(messages) == 1
assert messages[0].content.text == "42" # type: ignore[attr-defined]
assert len(result.messages) == 1
assert result.messages[0].content.text == "42" # type: ignore[attr-defined]
async def test_annotated_context_parameter_detection(self):
"""Test that annotated context parameters are properly detected in
@ -460,5 +460,5 @@ class TestContextHandling:
context = Context(fastmcp=mcp)
async with context:
messages = await prompt.render(arguments={"topic": "cats"})
assert messages[0].content.text == "Write about cats" # type: ignore[attr-defined]
result = await prompt.render(arguments={"topic": "cats"})
assert result.messages[0].content.text == "Write about cats" # type: ignore[attr-defined]

View file

@ -848,7 +848,7 @@ class TestPromptDecorator:
assert prompt.name == "fn"
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_without_parentheses(self):
mcp = FastMCP()
@ -880,7 +880,7 @@ class TestPromptDecorator:
prompt = prompts_dict["custom_name"]
assert prompt.name == "custom_name"
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_with_description(self):
mcp = FastMCP()
@ -894,7 +894,7 @@ class TestPromptDecorator:
prompt = prompts_dict["fn"]
assert prompt.description == "A custom description"
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_with_parameters(self):
mcp = FastMCP()

View file

@ -2194,7 +2194,7 @@ class TestPrompts:
assert prompt.name == "fn"
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_with_name(self):
"""Test prompt decorator with custom name."""
@ -2209,7 +2209,7 @@ class TestPrompts:
prompt = prompts_dict["custom_name"]
assert prompt.name == "custom_name"
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_with_description(self):
"""Test prompt decorator with custom description."""
@ -2224,7 +2224,7 @@ class TestPrompts:
prompt = prompts_dict["fn"]
assert prompt.description == "A custom description"
content = await prompt.render()
assert content[0].content.text == "Hello, world!" # type: ignore[attr-defined]
assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
async def test_prompt_decorator_with_parens(self):
mcp = FastMCP()