diff --git a/docs/clients/client.mdx b/docs/clients/client.mdx index 5602887dc..feb393505 100644 --- a/docs/clients/client.mdx +++ b/docs/clients/client.mdx @@ -104,6 +104,10 @@ You can make multiple calls to the server within the same `async with` block usi The `Client` provides methods corresponding to standard MCP requests: + +The standard client methods return user-friendly representations that may change as the protocol evolves. For consistent access to the complete data structure, use the `*_mcp` methods described later. + + #### Tool Operations * **`list_tools()`**: Retrieves a list of tools available on the server. @@ -153,6 +157,10 @@ The `Client` provides methods corresponding to standard MCP requests: The FastMCP client attempts to provide a "friendly" interface to the MCP protocol, but sometimes you may need access to the raw MCP protocol objects. Each of the main client methods that returns data has a corresponding `*_mcp` method that returns the raw MCP protocol objects directly. + +The standard client methods (without `_mcp`) return user-friendly representations of MCP data, while `*_mcp` methods will always return the complete MCP protocol objects. As the protocol evolves, changes to these user-friendly representations may occur and could potentially be breaking. If you need consistent, stable access to the full data structure, prefer using the `*_mcp` methods. + + ```python # Standard method - returns just the list of tools tools = await client.list_tools() diff --git a/docs/servers/prompts.mdx b/docs/servers/prompts.mdx index 75d0418a5..f1e292dd6 100644 --- a/docs/servers/prompts.mdx +++ b/docs/servers/prompts.mdx @@ -28,11 +28,11 @@ The most common way to define a prompt is by decorating a Python function. The d ```python from fastmcp import FastMCP -from fastmcp.prompts.prompt import UserMessage, AssistantMessage, Message +from fastmcp.prompts.prompt import Message, PromptMessage, TextContent mcp = FastMCP(name="PromptServer") -# Basic prompt returning a string (converted to UserMessage) +# Basic prompt returning a string (converted to user message automatically) @mcp.prompt() def ask_about_topic(topic: str) -> str: """Generates a user message asking for an explanation of a topic.""" @@ -40,10 +40,10 @@ def ask_about_topic(topic: str) -> str: # Prompt returning a specific message type @mcp.prompt() -def generate_code_request(language: str, task_description: str) -> UserMessage: +def generate_code_request(language: str, task_description: str) -> PromptMessage: """Generates a user message requesting code generation.""" content = f"Write a {language} function that performs the following task: {task_description}" - return UserMessage(content=content) + return PromptMessage(role="user", content=TextContent(type="text", text=content)) ``` **Key Concepts:** @@ -61,24 +61,21 @@ Functions with `*args` or `**kwargs` are not supported as prompts. This restrict FastMCP intelligently handles different return types from your prompt function: -- **`str`**: Automatically converted to a single `UserMessage`. -- **`Message`** (e.g., `UserMessage`, `AssistantMessage`): Used directly as provided. -- **`dict`**: Parsed as a `Message` object if it has the correct structure. -- **`list[Message]`**: Used as a sequence of messages (a conversation). +- **`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). +- **`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 + @mcp.prompt() def roleplay_scenario(character: str, situation: str) -> list[Message]: """Sets up a roleplaying scenario with initial messages.""" return [ - UserMessage(f"Let's roleplay. You are {character}. The situation is: {situation}"), - AssistantMessage("Okay, I understand. I am ready. What happens next?") + Message(f"Let's roleplay. You are {character}. The situation is: {situation}"), + Message("Okay, I understand. I am ready. What happens next?", role="assistant") ] - -@mcp.prompt() -def ask_for_feedback() -> dict: - """Generates a user message asking for feedback.""" - return {"role": "user", "content": "What did you think of my previous response?"} ``` ### Type Annotations diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py index 7ca6528d1..32536e13c 100644 --- a/src/fastmcp/client/client.py +++ b/src/fastmcp/client/client.py @@ -286,7 +286,7 @@ class Client: async def get_prompt( self, name: str, arguments: dict[str, str] | None = None - ) -> list[mcp.types.PromptMessage]: + ) -> mcp.types.GetPromptResult: """Retrieve a rendered prompt message list from the server. Args: @@ -294,13 +294,14 @@ class Client: arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None. Returns: - list[mcp.types.PromptMessage]: A list of prompt messages. + mcp.types.GetPromptResult: The complete response object from the protocol, + containing the prompt messages and any additional metadata. Raises: RuntimeError: If called while the client is not connected. """ result = await self.get_prompt_mcp(name=name, arguments=arguments) - return result.messages + return result # --- Completion --- diff --git a/src/fastmcp/prompts/__init__.py b/src/fastmcp/prompts/__init__.py index 21e29a0b4..1a8d91255 100644 --- a/src/fastmcp/prompts/__init__.py +++ b/src/fastmcp/prompts/__init__.py @@ -1,4 +1,9 @@ -from .prompt import Prompt, Message, UserMessage, AssistantMessage +from .prompt import Prompt, PromptMessage, Message from .prompt_manager import PromptManager -__all__ = ["Prompt", "PromptManager", "Message", "UserMessage", "AssistantMessage"] +__all__ = [ + "Prompt", + "PromptManager", + "PromptMessage", + "Message", +] diff --git a/src/fastmcp/prompts/prompt.py b/src/fastmcp/prompts/prompt.py index 801bab62c..763c12021 100644 --- a/src/fastmcp/prompts/prompt.py +++ b/src/fastmcp/prompts/prompt.py @@ -4,10 +4,10 @@ from __future__ import annotations as _annotations import inspect from collections.abc import Awaitable, Callable, Sequence -from typing import TYPE_CHECKING, Annotated, Any, Literal +from typing import TYPE_CHECKING, Annotated, Any import pydantic_core -from mcp.types import EmbeddedResource, ImageContent, TextContent +from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent from mcp.types import Prompt as MCPPrompt from mcp.types import PromptArgument as MCPPromptArgument from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call @@ -28,32 +28,24 @@ if TYPE_CHECKING: CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource -class Message(BaseModel): - """Base class for all prompt messages.""" - - role: Literal["user", "assistant"] - content: CONTENT_TYPES - - def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any): - if isinstance(content, str): - content = TextContent(type="text", text=content) - super().__init__(content=content, **kwargs) +def Message( + content: str | CONTENT_TYPES, role: Role | None = None, **kwargs: Any +) -> PromptMessage: + """A user-friendly constructor for PromptMessage.""" + if isinstance(content, str): + content = TextContent(type="text", text=content) + if role is None: + role = "user" + return PromptMessage(content=content, role=role, **kwargs) -def UserMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message: - """A message from the user.""" - return Message(content=content, role="user", **kwargs) - - -def AssistantMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message: - """A message from the assistant.""" - return Message(content=content, role="assistant", **kwargs) - - -message_validator = TypeAdapter[Message](Message) +message_validator = TypeAdapter[PromptMessage](PromptMessage) SyncPromptResult = ( - str | Message | dict[str, Any] | Sequence[str | Message | dict[str, Any]] + str + | PromptMessage + | dict[str, Any] + | Sequence[str | PromptMessage | dict[str, Any]] ) PromptResult = SyncPromptResult | Awaitable[SyncPromptResult] @@ -156,7 +148,7 @@ class Prompt(BaseModel): self, arguments: dict[str, Any] | None = None, context: Context[ServerSessionT, LifespanContextT] | None = None, - ) -> list[Message]: + ) -> list[PromptMessage]: """Render the prompt with arguments.""" # Validate required arguments if self.arguments: @@ -182,21 +174,28 @@ class Prompt(BaseModel): result = [result] # Convert result to messages - messages: list[Message] = [] - for msg in result: # type: ignore[reportUnknownVariableType] + messages: list[PromptMessage] = [] + for msg in result: try: - if isinstance(msg, Message): + if isinstance(msg, PromptMessage): messages.append(msg) - elif isinstance(msg, dict): - messages.append(message_validator.validate_python(msg)) elif isinstance(msg, str): - content = TextContent(type="text", text=msg) - messages.append(Message(role="user", content=content)) + messages.append( + PromptMessage( + role="user", + content=TextContent(type="text", text=msg), + ) + ) else: content = pydantic_core.to_json( msg, fallback=str, indent=2 ).decode() - messages.append(Message(role="user", content=content)) + messages.append( + PromptMessage( + role="user", + content=TextContent(type="text", text=content), + ) + ) except Exception: raise ValueError( f"Could not convert prompt result to message: {msg}" diff --git a/src/fastmcp/prompts/prompt_manager.py b/src/fastmcp/prompts/prompt_manager.py index db16d4398..4de06c03c 100644 --- a/src/fastmcp/prompts/prompt_manager.py +++ b/src/fastmcp/prompts/prompt_manager.py @@ -5,8 +5,10 @@ from __future__ import annotations as _annotations from collections.abc import Awaitable, Callable from typing import TYPE_CHECKING, Any +from mcp import GetPromptResult + from fastmcp.exceptions import NotFoundError -from fastmcp.prompts.prompt import Message, Prompt, PromptResult +from fastmcp.prompts.prompt import Prompt, PromptResult from fastmcp.settings import DuplicateBehavior from fastmcp.utilities.logging import get_logger @@ -81,13 +83,18 @@ class PromptManager: name: str, arguments: dict[str, Any] | None = None, context: Context[ServerSessionT, LifespanContextT] | None = None, - ) -> list[Message]: + ) -> GetPromptResult: """Render a prompt by name with arguments.""" prompt = self.get_prompt(name) if not prompt: raise NotFoundError(f"Unknown prompt: {name}") - return await prompt.render(arguments, context=context) + messages = await prompt.render(arguments, context=context) + + return GetPromptResult( + description=prompt.description, + messages=messages, + ) def has_prompt(self, key: str) -> bool: """Check if a prompt exists.""" diff --git a/src/fastmcp/server/proxy.py b/src/fastmcp/server/proxy.py index a2f83fa4c..038623aab 100644 --- a/src/fastmcp/server/proxy.py +++ b/src/fastmcp/server/proxy.py @@ -19,7 +19,7 @@ from pydantic.networks import AnyUrl from fastmcp.client import Client from fastmcp.exceptions import NotFoundError -from fastmcp.prompts import Message, Prompt +from fastmcp.prompts import Prompt, PromptMessage from fastmcp.resources import Resource, ResourceTemplate from fastmcp.server.context import Context from fastmcp.server.server import FastMCP @@ -175,10 +175,10 @@ class ProxyPrompt(Prompt): self, arguments: dict[str, Any], context: Context[ServerSessionT, LifespanContextT] | None = None, - ) -> list[Message]: + ) -> list[PromptMessage]: async with self._client: result = await self._client.get_prompt(self.name, arguments) - return [Message(role=m.role, content=m.content) for m in result] + return result.messages class FastMCPProxy(FastMCP): @@ -291,4 +291,4 @@ class FastMCPProxy(FastMCP): except NotFoundError: async with self.client: result = await self.client.get_prompt(name, arguments) - return GetPromptResult(messages=result) + return result diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 1de7dd908..a92d94f95 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -32,7 +32,6 @@ from mcp.types import ( EmbeddedResource, GetPromptResult, ImageContent, - PromptMessage, TextContent, ToolAnnotations, ) @@ -504,15 +503,10 @@ class FastMCP(Generic[LifespanResultT]): """ if self._prompt_manager.has_prompt(name): context = self.get_context() - messages = await self._prompt_manager.render_prompt( + prompt_result = await self._prompt_manager.render_prompt( name, arguments=arguments or {}, context=context ) - - return GetPromptResult( - messages=[ - PromptMessage(role=m.role, content=m.content) for m in messages - ] - ) + return prompt_result else: for server in self._mounted_servers.values(): if server.match_prompt(name): diff --git a/tests/client/test_client.py b/tests/client/test_client.py index c6d671fc4..dcb1624cd 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -5,6 +5,7 @@ from pydantic import AnyUrl from fastmcp.client import Client from fastmcp.client.transports import FastMCPTransport +from fastmcp.prompts.prompt import TextContent from fastmcp.server.server import FastMCP @@ -38,6 +39,7 @@ def fastmcp_server(): # Add a prompt @server.prompt() def welcome(name: str) -> str: + """Example greeting prompt.""" return f"Welcome to FastMCP, {name}!" return server @@ -182,8 +184,9 @@ async def test_get_prompt(fastmcp_server): result = await client.get_prompt("welcome", {"name": "Developer"}) # The result should contain our welcome message - result_str = str(result) - assert "Welcome to FastMCP, Developer!" in result_str + assert isinstance(result.messages[0].content, TextContent) + assert result.messages[0].content.text == "Welcome to FastMCP, Developer!" + assert result.description == "Example greeting prompt." async def test_get_prompt_mcp(fastmcp_server): @@ -193,11 +196,10 @@ async def test_get_prompt_mcp(fastmcp_server): async with client: result = await client.get_prompt_mcp("welcome", {"name": "Developer"}) - # Check that we got the raw MCP GetPromptResult object - assert hasattr(result, "messages") - assert len(result.messages) > 0 - result_str = str(result.messages) - assert "Welcome to FastMCP, Developer!" in result_str + # The result should contain our welcome message + assert isinstance(result.messages[0].content, TextContent) + assert result.messages[0].content.text == "Welcome to FastMCP, Developer!" + assert result.description == "Example greeting prompt." async def test_read_resource(fastmcp_server): diff --git a/tests/prompts/test_base.py b/tests/prompts/test_prompt.py similarity index 62% rename from tests/prompts/test_base.py rename to tests/prompts/test_prompt.py index 24c05ccd8..c77ea0b88 100644 --- a/tests/prompts/test_base.py +++ b/tests/prompts/test_prompt.py @@ -3,11 +3,10 @@ from mcp.types import EmbeddedResource, TextResourceContents from pydantic import FileUrl from fastmcp.prompts.prompt import ( - AssistantMessage, Message, Prompt, + PromptMessage, TextContent, - UserMessage, ) @@ -18,7 +17,9 @@ class TestRenderPrompt: prompt = Prompt.from_function(fn) assert await prompt.render() == [ - UserMessage(content=TextContent(type="text", text="Hello, world!")) + PromptMessage( + role="user", content=TextContent(type="text", text="Hello, world!") + ) ] async def test_async_fn(self): @@ -27,7 +28,9 @@ class TestRenderPrompt: prompt = Prompt.from_function(fn) assert await prompt.render() == [ - UserMessage(content=TextContent(type="text", text="Hello, world!")) + PromptMessage( + role="user", content=TextContent(type="text", text="Hello, world!") + ) ] async def test_fn_with_args(self): @@ -36,10 +39,11 @@ class TestRenderPrompt: prompt = Prompt.from_function(fn) assert await prompt.render(arguments=dict(name="World")) == [ - UserMessage( + PromptMessage( + role="user", content=TextContent( type="text", text="Hello, World! You're 30 years old." - ) + ), ) ] @@ -52,33 +56,42 @@ class TestRenderPrompt: await prompt.render(arguments=dict(age=40)) async def test_fn_returns_message(self): - async def fn() -> Message: - return UserMessage(content="Hello, world!") - - prompt = Prompt.from_function(fn) - assert await prompt.render() == [ - UserMessage(content=TextContent(type="text", text="Hello, world!")) - ] - - async def test_fn_returns_assistant_message(self): - async def fn() -> Message: - return AssistantMessage( - content=TextContent(type="text", text="Hello, world!") + async def fn() -> PromptMessage: + return PromptMessage( + role="user", content=TextContent(type="text", text="Hello, world!") ) prompt = Prompt.from_function(fn) assert await prompt.render() == [ - AssistantMessage(content=TextContent(type="text", text="Hello, world!")) + PromptMessage( + role="user", content=TextContent(type="text", text="Hello, world!") + ) + ] + + async def test_fn_returns_assistant_message(self): + async def fn() -> PromptMessage: + return PromptMessage( + role="assistant", content=TextContent(type="text", text="Hello, world!") + ) + + prompt = Prompt.from_function(fn) + assert await prompt.render() == [ + PromptMessage( + role="assistant", content=TextContent(type="text", text="Hello, world!") + ) ] async def test_fn_returns_multiple_messages(self): expected = [ - UserMessage("Hello, world!"), - AssistantMessage("How can I help you today?"), - UserMessage("I'm looking for a restaurant in the center of town."), + Message(role="user", content="Hello, world!"), + Message(role="assistant", content="How can I help you today?"), + Message( + role="user", + content="I'm looking for a restaurant in the center of town.", + ), ] - async def fn() -> list[Message]: + async def fn() -> list[PromptMessage]: return expected prompt = Prompt.from_function(fn) @@ -94,13 +107,17 @@ class TestRenderPrompt: return expected prompt = Prompt.from_function(fn) - assert await prompt.render() == [UserMessage(t) for t in expected] + assert await prompt.render() == [ + PromptMessage(role="user", content=TextContent(type="text", text=t)) + for t in expected + ] async def test_fn_returns_resource_content(self): """Test returning a message with resource content.""" - async def fn() -> Message: - return UserMessage( + async def fn() -> PromptMessage: + return PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -108,12 +125,13 @@ class TestRenderPrompt: text="File contents", mimeType="text/plain", ), - ) + ), ) prompt = Prompt.from_function(fn) assert await prompt.render() == [ - UserMessage( + PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -121,17 +139,18 @@ class TestRenderPrompt: text="File contents", mimeType="text/plain", ), - ) + ), ) ] async def test_fn_returns_mixed_content(self): """Test returning messages with mixed content types.""" - async def fn() -> list[Message]: + async def fn() -> list[PromptMessage | str]: return [ - UserMessage(content="Please analyze this file:"), - UserMessage( + "Please analyze this file:", + PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -139,17 +158,19 @@ class TestRenderPrompt: text="File contents", mimeType="text/plain", ), - ) + ), ), - AssistantMessage(content="I'll help analyze that file."), + Message(role="assistant", content="I'll help analyze that file."), ] prompt = Prompt.from_function(fn) assert await prompt.render() == [ - UserMessage( - content=TextContent(type="text", text="Please analyze this file:") + PromptMessage( + role="user", + content=TextContent(type="text", text="Please analyze this file:"), ), - UserMessage( + PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -157,32 +178,34 @@ class TestRenderPrompt: text="File contents", mimeType="text/plain", ), - ) + ), ), - AssistantMessage( - content=TextContent(type="text", text="I'll help analyze that file.") + PromptMessage( + role="assistant", + content=TextContent(type="text", text="I'll help analyze that file."), ), ] - async def test_fn_returns_dict_with_resource(self): + async def test_fn_returns_message_with_resource(self): """Test returning a dict with resource content.""" - async def fn() -> dict: - return { - "role": "user", - "content": { - "type": "resource", - "resource": { - "uri": FileUrl("file://file.txt"), - "text": "File contents", - "mimeType": "text/plain", - }, - }, - } + async def fn() -> PromptMessage: + return PromptMessage( + role="user", + content=EmbeddedResource( + type="resource", + resource=TextResourceContents( + uri=FileUrl("file://file.txt"), + text="File contents", + mimeType="text/plain", + ), + ), + ) prompt = Prompt.from_function(fn) assert await prompt.render() == [ - UserMessage( + PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -190,6 +213,6 @@ class TestRenderPrompt: text="File contents", mimeType="text/plain", ), - ) + ), ) ] diff --git a/tests/prompts/test_prompt_manager.py b/tests/prompts/test_prompt_manager.py index c00d27e49..3a41003e7 100644 --- a/tests/prompts/test_prompt_manager.py +++ b/tests/prompts/test_prompt_manager.py @@ -7,7 +7,7 @@ from mcp.shared.context import LifespanContextT from fastmcp import Context from fastmcp.exceptions import NotFoundError from fastmcp.prompts import Prompt -from fastmcp.prompts.prompt import TextContent, UserMessage +from fastmcp.prompts.prompt import PromptMessage, TextContent from fastmcp.prompts.prompt_manager import PromptManager @@ -147,28 +147,36 @@ class TestPromptManager: """Test rendering a prompt.""" def fn() -> str: + """An example prompt.""" return "Hello, world!" manager = PromptManager() prompt = Prompt.from_function(fn) manager.add_prompt(prompt) - messages = await manager.render_prompt("fn") - assert messages == [ - UserMessage(content=TextContent(type="text", text="Hello, world!")) + result = await manager.render_prompt("fn") + assert result.description == "An example prompt." + assert result.messages == [ + PromptMessage( + role="user", content=TextContent(type="text", text="Hello, world!") + ) ] async def test_render_prompt_with_args(self): """Test rendering a prompt with arguments.""" def fn(name: str) -> str: + """An example prompt.""" return f"Hello, {name}!" manager = PromptManager() prompt = Prompt.from_function(fn) manager.add_prompt(prompt) - messages = await manager.render_prompt("fn", arguments={"name": "World"}) - assert messages == [ - UserMessage(content=TextContent(type="text", text="Hello, World!")) + result = await manager.render_prompt("fn", arguments={"name": "World"}) + assert result.description == "An example prompt." + assert result.messages == [ + PromptMessage( + role="user", content=TextContent(type="text", text="Hello, World!") + ) ] async def test_render_unknown_prompt(self): diff --git a/tests/server/test_import_server.py b/tests/server/test_import_server.py index c9160e4b1..a122ee23c 100644 --- a/tests/server/test_import_server.py +++ b/tests/server/test_import_server.py @@ -319,14 +319,16 @@ async def test_import_with_proxy_prompts(): @api_app.prompt() def greeting(name: str) -> str: + """Example greeting prompt.""" return f"Hello, {name} from API!" proxy_app = FastMCP.from_client(Client(api_app)) await main_app.import_server("api", proxy_app) result = await main_app._mcp_get_prompt("api_greeting", {"name": "World"}) - assert result.messages is not None - # Check that the message contains our greeting + assert isinstance(result.messages[0].content, TextContent) + assert result.messages[0].content.text == "Hello, World from API!" + assert result.description == "Example greeting prompt." async def test_import_with_proxy_resources(): diff --git a/tests/server/test_proxy.py b/tests/server/test_proxy.py index 882977527..aef2c0583 100644 --- a/tests/server/test_proxy.py +++ b/tests/server/test_proxy.py @@ -207,7 +207,7 @@ class TestPrompts: async def test_render_prompt_calls_prompt(self, proxy_server): async with Client(proxy_server) as client: result = await client.get_prompt("welcome", {"name": "Alice"}) - assert isinstance(result[0], mcp.types.PromptMessage) - assert result[0].role == "user" - assert isinstance(result[0].content, mcp.types.TextContent) - assert result[0].content.text == "Welcome to FastMCP, Alice!" + assert isinstance(result.messages[0], mcp.types.PromptMessage) + assert result.messages[0].role == "user" + assert isinstance(result.messages[0].content, mcp.types.TextContent) + assert result.messages[0].content.text == "Welcome to FastMCP, Alice!" diff --git a/tests/server/test_server.py b/tests/server/test_server.py index 6a80c03ab..ead1dd673 100644 --- a/tests/server/test_server.py +++ b/tests/server/test_server.py @@ -640,16 +640,16 @@ class TestPromptDecorator: async with Client(mcp) as client: result = await client.get_prompt("test_prompt", {"name": "World"}) - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "Hello, World!" result = await client.get_prompt( "test_prompt", {"name": "World", "greeting": "Hi"} ) - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "Hi, World!" @@ -668,8 +668,8 @@ class TestPromptDecorator: async with Client(mcp) as client: result = await client.get_prompt("test_prompt") - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "My prefix: Hello, world!" @@ -687,8 +687,8 @@ class TestPromptDecorator: async with Client(mcp) as client: result = await client.get_prompt("test_prompt") - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "Class prefix: Hello, world!" @@ -703,8 +703,8 @@ class TestPromptDecorator: async with Client(mcp) as client: result = await client.get_prompt("test_prompt") - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "Static Hello, world!" @@ -717,8 +717,8 @@ class TestPromptDecorator: async with Client(mcp) as client: result = await client.get_prompt("test_prompt") - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert isinstance(message.content, TextContent) assert message.content.text == "Async Hello, world!" diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py index 3489bf86c..1b95e20b7 100644 --- a/tests/server/test_server_interactions.py +++ b/tests/server/test_server_interactions.py @@ -18,7 +18,7 @@ from pydantic import AnyUrl, Field from fastmcp import Client, Context, FastMCP from fastmcp.exceptions import ClientError -from fastmcp.prompts.prompt import EmbeddedResource, Message, UserMessage +from fastmcp.prompts.prompt import EmbeddedResource, PromptMessage from fastmcp.resources import FileResource, FunctionResource from fastmcp.utilities.types import Image @@ -1267,8 +1267,8 @@ class TestPrompts: async with Client(mcp) as client: result = await client.get_prompt("fn", {"name": "World"}) - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert message.role == "user" content = message.content assert isinstance(content, TextContent) @@ -1279,8 +1279,9 @@ class TestPrompts: mcp = FastMCP() @mcp.prompt() - def fn() -> Message: - return UserMessage( + def fn() -> PromptMessage: + return PromptMessage( + role="user", content=EmbeddedResource( type="resource", resource=TextResourceContents( @@ -1288,13 +1289,13 @@ class TestPrompts: text="File contents", mimeType="text/plain", ), - ) + ), ) async with Client(mcp) as client: result = await client.get_prompt("fn") - assert result[0].role == "user" - content = result[0].content + assert result.messages[0].role == "user" + content = result.messages[0].content assert isinstance(content, EmbeddedResource) resource = content.resource assert isinstance(resource, TextResourceContents) @@ -1370,6 +1371,6 @@ class TestPromptContext: async with Client(mcp) as client: result = await client.get_prompt("prompt_fn", {"name": "World"}) - assert len(result) == 1 - message = result[0] + assert len(result.messages) == 1 + message = result.messages[0] assert message.role == "user"