Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
3becd4d
1
Parent(s): 1cd0ac1
Ensure prompts return descriptions
Browse files- docs/clients/client.mdx +8 -0
- docs/servers/prompts.mdx +12 -15
- src/fastmcp/client/client.py +4 -3
- src/fastmcp/prompts/__init__.py +7 -2
- src/fastmcp/prompts/prompt.py +32 -33
- src/fastmcp/prompts/prompt_manager.py +10 -3
- src/fastmcp/server/proxy.py +4 -4
- src/fastmcp/server/server.py +2 -8
- tests/client/test_client.py +9 -7
- tests/prompts/{test_base.py → test_prompt.py} +72 -49
- tests/prompts/test_prompt_manager.py +15 -7
- tests/server/test_import_server.py +4 -2
- tests/server/test_proxy.py +4 -4
- tests/server/test_server.py +12 -12
- tests/server/test_server_interactions.py +11 -10
docs/clients/client.mdx
CHANGED
|
@@ -104,6 +104,10 @@ You can make multiple calls to the server within the same `async with` block usi
|
|
| 104 |
|
| 105 |
The `Client` provides methods corresponding to standard MCP requests:
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
#### Tool Operations
|
| 108 |
|
| 109 |
* **`list_tools()`**: Retrieves a list of tools available on the server.
|
|
@@ -153,6 +157,10 @@ The `Client` provides methods corresponding to standard MCP requests:
|
|
| 153 |
|
| 154 |
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.
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
```python
|
| 157 |
# Standard method - returns just the list of tools
|
| 158 |
tools = await client.list_tools()
|
|
|
|
| 104 |
|
| 105 |
The `Client` provides methods corresponding to standard MCP requests:
|
| 106 |
|
| 107 |
+
<Warning>
|
| 108 |
+
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.
|
| 109 |
+
</Warning>
|
| 110 |
+
|
| 111 |
#### Tool Operations
|
| 112 |
|
| 113 |
* **`list_tools()`**: Retrieves a list of tools available on the server.
|
|
|
|
| 157 |
|
| 158 |
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.
|
| 159 |
|
| 160 |
+
<Warning>
|
| 161 |
+
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.
|
| 162 |
+
</Warning>
|
| 163 |
+
|
| 164 |
```python
|
| 165 |
# Standard method - returns just the list of tools
|
| 166 |
tools = await client.list_tools()
|
docs/servers/prompts.mdx
CHANGED
|
@@ -28,11 +28,11 @@ The most common way to define a prompt is by decorating a Python function. The d
|
|
| 28 |
|
| 29 |
```python
|
| 30 |
from fastmcp import FastMCP
|
| 31 |
-
from fastmcp.prompts.prompt import
|
| 32 |
|
| 33 |
mcp = FastMCP(name="PromptServer")
|
| 34 |
|
| 35 |
-
# Basic prompt returning a string (converted to
|
| 36 |
@mcp.prompt()
|
| 37 |
def ask_about_topic(topic: str) -> str:
|
| 38 |
"""Generates a user message asking for an explanation of a topic."""
|
|
@@ -40,10 +40,10 @@ def ask_about_topic(topic: str) -> str:
|
|
| 40 |
|
| 41 |
# Prompt returning a specific message type
|
| 42 |
@mcp.prompt()
|
| 43 |
-
def generate_code_request(language: str, task_description: str) ->
|
| 44 |
"""Generates a user message requesting code generation."""
|
| 45 |
content = f"Write a {language} function that performs the following task: {task_description}"
|
| 46 |
-
return
|
| 47 |
```
|
| 48 |
|
| 49 |
**Key Concepts:**
|
|
@@ -61,24 +61,21 @@ Functions with `*args` or `**kwargs` are not supported as prompts. This restrict
|
|
| 61 |
|
| 62 |
FastMCP intelligently handles different return types from your prompt function:
|
| 63 |
|
| 64 |
-
- **`str`**: Automatically converted to a single `
|
| 65 |
-
- **`
|
| 66 |
-
- **`
|
| 67 |
-
- **`
|
| 68 |
|
| 69 |
```python
|
|
|
|
|
|
|
| 70 |
@mcp.prompt()
|
| 71 |
def roleplay_scenario(character: str, situation: str) -> list[Message]:
|
| 72 |
"""Sets up a roleplaying scenario with initial messages."""
|
| 73 |
return [
|
| 74 |
-
|
| 75 |
-
|
| 76 |
]
|
| 77 |
-
|
| 78 |
-
@mcp.prompt()
|
| 79 |
-
def ask_for_feedback() -> dict:
|
| 80 |
-
"""Generates a user message asking for feedback."""
|
| 81 |
-
return {"role": "user", "content": "What did you think of my previous response?"}
|
| 82 |
```
|
| 83 |
|
| 84 |
### Type Annotations
|
|
|
|
| 28 |
|
| 29 |
```python
|
| 30 |
from fastmcp import FastMCP
|
| 31 |
+
from fastmcp.prompts.prompt import Message, PromptMessage, TextContent
|
| 32 |
|
| 33 |
mcp = FastMCP(name="PromptServer")
|
| 34 |
|
| 35 |
+
# Basic prompt returning a string (converted to user message automatically)
|
| 36 |
@mcp.prompt()
|
| 37 |
def ask_about_topic(topic: str) -> str:
|
| 38 |
"""Generates a user message asking for an explanation of a topic."""
|
|
|
|
| 40 |
|
| 41 |
# Prompt returning a specific message type
|
| 42 |
@mcp.prompt()
|
| 43 |
+
def generate_code_request(language: str, task_description: str) -> PromptMessage:
|
| 44 |
"""Generates a user message requesting code generation."""
|
| 45 |
content = f"Write a {language} function that performs the following task: {task_description}"
|
| 46 |
+
return PromptMessage(role="user", content=TextContent(type="text", text=content))
|
| 47 |
```
|
| 48 |
|
| 49 |
**Key Concepts:**
|
|
|
|
| 61 |
|
| 62 |
FastMCP intelligently handles different return types from your prompt function:
|
| 63 |
|
| 64 |
+
- **`str`**: Automatically converted to a single `PromptMessage`.
|
| 65 |
+
- **`PromptMessage`**: Used directly as provided. (Note a more user-friendly `Message` constructor is available that can accept raw strings instead of `TextContent` objects.)
|
| 66 |
+
- **`list[PromptMessage | str]`**: Used as a sequence of messages (a conversation).
|
| 67 |
+
- **`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`.
|
| 68 |
|
| 69 |
```python
|
| 70 |
+
from fastmcp.prompts.prompt import Message
|
| 71 |
+
|
| 72 |
@mcp.prompt()
|
| 73 |
def roleplay_scenario(character: str, situation: str) -> list[Message]:
|
| 74 |
"""Sets up a roleplaying scenario with initial messages."""
|
| 75 |
return [
|
| 76 |
+
Message(f"Let's roleplay. You are {character}. The situation is: {situation}"),
|
| 77 |
+
Message("Okay, I understand. I am ready. What happens next?", role="assistant")
|
| 78 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
```
|
| 80 |
|
| 81 |
### Type Annotations
|
src/fastmcp/client/client.py
CHANGED
|
@@ -286,7 +286,7 @@ class Client:
|
|
| 286 |
|
| 287 |
async def get_prompt(
|
| 288 |
self, name: str, arguments: dict[str, str] | None = None
|
| 289 |
-
) ->
|
| 290 |
"""Retrieve a rendered prompt message list from the server.
|
| 291 |
|
| 292 |
Args:
|
|
@@ -294,13 +294,14 @@ class Client:
|
|
| 294 |
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
|
| 295 |
|
| 296 |
Returns:
|
| 297 |
-
|
|
|
|
| 298 |
|
| 299 |
Raises:
|
| 300 |
RuntimeError: If called while the client is not connected.
|
| 301 |
"""
|
| 302 |
result = await self.get_prompt_mcp(name=name, arguments=arguments)
|
| 303 |
-
return result
|
| 304 |
|
| 305 |
# --- Completion ---
|
| 306 |
|
|
|
|
| 286 |
|
| 287 |
async def get_prompt(
|
| 288 |
self, name: str, arguments: dict[str, str] | None = None
|
| 289 |
+
) -> mcp.types.GetPromptResult:
|
| 290 |
"""Retrieve a rendered prompt message list from the server.
|
| 291 |
|
| 292 |
Args:
|
|
|
|
| 294 |
arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
|
| 295 |
|
| 296 |
Returns:
|
| 297 |
+
mcp.types.GetPromptResult: The complete response object from the protocol,
|
| 298 |
+
containing the prompt messages and any additional metadata.
|
| 299 |
|
| 300 |
Raises:
|
| 301 |
RuntimeError: If called while the client is not connected.
|
| 302 |
"""
|
| 303 |
result = await self.get_prompt_mcp(name=name, arguments=arguments)
|
| 304 |
+
return result
|
| 305 |
|
| 306 |
# --- Completion ---
|
| 307 |
|
src/fastmcp/prompts/__init__.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
| 1 |
-
from .prompt import Prompt,
|
| 2 |
from .prompt_manager import PromptManager
|
| 3 |
|
| 4 |
-
__all__ = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .prompt import Prompt, PromptMessage, Message
|
| 2 |
from .prompt_manager import PromptManager
|
| 3 |
|
| 4 |
+
__all__ = [
|
| 5 |
+
"Prompt",
|
| 6 |
+
"PromptManager",
|
| 7 |
+
"PromptMessage",
|
| 8 |
+
"Message",
|
| 9 |
+
]
|
src/fastmcp/prompts/prompt.py
CHANGED
|
@@ -4,10 +4,10 @@ from __future__ import annotations as _annotations
|
|
| 4 |
|
| 5 |
import inspect
|
| 6 |
from collections.abc import Awaitable, Callable, Sequence
|
| 7 |
-
from typing import TYPE_CHECKING, Annotated, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
-
from mcp.types import EmbeddedResource, ImageContent, TextContent
|
| 11 |
from mcp.types import Prompt as MCPPrompt
|
| 12 |
from mcp.types import PromptArgument as MCPPromptArgument
|
| 13 |
from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
|
|
@@ -28,32 +28,24 @@ if TYPE_CHECKING:
|
|
| 28 |
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
|
| 29 |
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
role: Literal["user", "assistant"]
|
| 35 |
-
content: CONTENT_TYPES
|
| 36 |
|
| 37 |
-
|
| 38 |
-
if isinstance(content, str):
|
| 39 |
-
content = TextContent(type="text", text=content)
|
| 40 |
-
super().__init__(content=content, **kwargs)
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def UserMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message:
|
| 44 |
-
"""A message from the user."""
|
| 45 |
-
return Message(content=content, role="user", **kwargs)
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def AssistantMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message:
|
| 49 |
-
"""A message from the assistant."""
|
| 50 |
-
return Message(content=content, role="assistant", **kwargs)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
message_validator = TypeAdapter[Message](Message)
|
| 54 |
|
| 55 |
SyncPromptResult = (
|
| 56 |
-
str
|
|
|
|
|
|
|
|
|
|
| 57 |
)
|
| 58 |
PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]
|
| 59 |
|
|
@@ -156,7 +148,7 @@ class Prompt(BaseModel):
|
|
| 156 |
self,
|
| 157 |
arguments: dict[str, Any] | None = None,
|
| 158 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 159 |
-
) -> list[
|
| 160 |
"""Render the prompt with arguments."""
|
| 161 |
# Validate required arguments
|
| 162 |
if self.arguments:
|
|
@@ -182,21 +174,28 @@ class Prompt(BaseModel):
|
|
| 182 |
result = [result]
|
| 183 |
|
| 184 |
# Convert result to messages
|
| 185 |
-
messages: list[
|
| 186 |
-
for msg in result:
|
| 187 |
try:
|
| 188 |
-
if isinstance(msg,
|
| 189 |
messages.append(msg)
|
| 190 |
-
elif isinstance(msg, dict):
|
| 191 |
-
messages.append(message_validator.validate_python(msg))
|
| 192 |
elif isinstance(msg, str):
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
else:
|
| 196 |
content = pydantic_core.to_json(
|
| 197 |
msg, fallback=str, indent=2
|
| 198 |
).decode()
|
| 199 |
-
messages.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
except Exception:
|
| 201 |
raise ValueError(
|
| 202 |
f"Could not convert prompt result to message: {msg}"
|
|
|
|
| 4 |
|
| 5 |
import inspect
|
| 6 |
from collections.abc import Awaitable, Callable, Sequence
|
| 7 |
+
from typing import TYPE_CHECKING, Annotated, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
+
from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
|
| 11 |
from mcp.types import Prompt as MCPPrompt
|
| 12 |
from mcp.types import PromptArgument as MCPPromptArgument
|
| 13 |
from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
|
|
|
|
| 28 |
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
|
| 29 |
|
| 30 |
|
| 31 |
+
def Message(
|
| 32 |
+
content: str | CONTENT_TYPES, role: Role | None = None, **kwargs: Any
|
| 33 |
+
) -> PromptMessage:
|
| 34 |
+
"""A user-friendly constructor for PromptMessage."""
|
| 35 |
+
if isinstance(content, str):
|
| 36 |
+
content = TextContent(type="text", text=content)
|
| 37 |
+
if role is None:
|
| 38 |
+
role = "user"
|
| 39 |
+
return PromptMessage(content=content, role=role, **kwargs)
|
| 40 |
|
|
|
|
|
|
|
| 41 |
|
| 42 |
+
message_validator = TypeAdapter[PromptMessage](PromptMessage)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
SyncPromptResult = (
|
| 45 |
+
str
|
| 46 |
+
| PromptMessage
|
| 47 |
+
| dict[str, Any]
|
| 48 |
+
| Sequence[str | PromptMessage | dict[str, Any]]
|
| 49 |
)
|
| 50 |
PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]
|
| 51 |
|
|
|
|
| 148 |
self,
|
| 149 |
arguments: dict[str, Any] | None = None,
|
| 150 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 151 |
+
) -> list[PromptMessage]:
|
| 152 |
"""Render the prompt with arguments."""
|
| 153 |
# Validate required arguments
|
| 154 |
if self.arguments:
|
|
|
|
| 174 |
result = [result]
|
| 175 |
|
| 176 |
# Convert result to messages
|
| 177 |
+
messages: list[PromptMessage] = []
|
| 178 |
+
for msg in result:
|
| 179 |
try:
|
| 180 |
+
if isinstance(msg, PromptMessage):
|
| 181 |
messages.append(msg)
|
|
|
|
|
|
|
| 182 |
elif isinstance(msg, str):
|
| 183 |
+
messages.append(
|
| 184 |
+
PromptMessage(
|
| 185 |
+
role="user",
|
| 186 |
+
content=TextContent(type="text", text=msg),
|
| 187 |
+
)
|
| 188 |
+
)
|
| 189 |
else:
|
| 190 |
content = pydantic_core.to_json(
|
| 191 |
msg, fallback=str, indent=2
|
| 192 |
).decode()
|
| 193 |
+
messages.append(
|
| 194 |
+
PromptMessage(
|
| 195 |
+
role="user",
|
| 196 |
+
content=TextContent(type="text", text=content),
|
| 197 |
+
)
|
| 198 |
+
)
|
| 199 |
except Exception:
|
| 200 |
raise ValueError(
|
| 201 |
f"Could not convert prompt result to message: {msg}"
|
src/fastmcp/prompts/prompt_manager.py
CHANGED
|
@@ -5,8 +5,10 @@ from __future__ import annotations as _annotations
|
|
| 5 |
from collections.abc import Awaitable, Callable
|
| 6 |
from typing import TYPE_CHECKING, Any
|
| 7 |
|
|
|
|
|
|
|
| 8 |
from fastmcp.exceptions import NotFoundError
|
| 9 |
-
from fastmcp.prompts.prompt import
|
| 10 |
from fastmcp.settings import DuplicateBehavior
|
| 11 |
from fastmcp.utilities.logging import get_logger
|
| 12 |
|
|
@@ -81,13 +83,18 @@ class PromptManager:
|
|
| 81 |
name: str,
|
| 82 |
arguments: dict[str, Any] | None = None,
|
| 83 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 84 |
-
) ->
|
| 85 |
"""Render a prompt by name with arguments."""
|
| 86 |
prompt = self.get_prompt(name)
|
| 87 |
if not prompt:
|
| 88 |
raise NotFoundError(f"Unknown prompt: {name}")
|
| 89 |
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
def has_prompt(self, key: str) -> bool:
|
| 93 |
"""Check if a prompt exists."""
|
|
|
|
| 5 |
from collections.abc import Awaitable, Callable
|
| 6 |
from typing import TYPE_CHECKING, Any
|
| 7 |
|
| 8 |
+
from mcp import GetPromptResult
|
| 9 |
+
|
| 10 |
from fastmcp.exceptions import NotFoundError
|
| 11 |
+
from fastmcp.prompts.prompt import Prompt, PromptResult
|
| 12 |
from fastmcp.settings import DuplicateBehavior
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
|
|
|
|
| 83 |
name: str,
|
| 84 |
arguments: dict[str, Any] | None = None,
|
| 85 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 86 |
+
) -> GetPromptResult:
|
| 87 |
"""Render a prompt by name with arguments."""
|
| 88 |
prompt = self.get_prompt(name)
|
| 89 |
if not prompt:
|
| 90 |
raise NotFoundError(f"Unknown prompt: {name}")
|
| 91 |
|
| 92 |
+
messages = await prompt.render(arguments, context=context)
|
| 93 |
+
|
| 94 |
+
return GetPromptResult(
|
| 95 |
+
description=prompt.description,
|
| 96 |
+
messages=messages,
|
| 97 |
+
)
|
| 98 |
|
| 99 |
def has_prompt(self, key: str) -> bool:
|
| 100 |
"""Check if a prompt exists."""
|
src/fastmcp/server/proxy.py
CHANGED
|
@@ -19,7 +19,7 @@ from pydantic.networks import AnyUrl
|
|
| 19 |
|
| 20 |
from fastmcp.client import Client
|
| 21 |
from fastmcp.exceptions import NotFoundError
|
| 22 |
-
from fastmcp.prompts import
|
| 23 |
from fastmcp.resources import Resource, ResourceTemplate
|
| 24 |
from fastmcp.server.context import Context
|
| 25 |
from fastmcp.server.server import FastMCP
|
|
@@ -175,10 +175,10 @@ class ProxyPrompt(Prompt):
|
|
| 175 |
self,
|
| 176 |
arguments: dict[str, Any],
|
| 177 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 178 |
-
) -> list[
|
| 179 |
async with self._client:
|
| 180 |
result = await self._client.get_prompt(self.name, arguments)
|
| 181 |
-
return
|
| 182 |
|
| 183 |
|
| 184 |
class FastMCPProxy(FastMCP):
|
|
@@ -291,4 +291,4 @@ class FastMCPProxy(FastMCP):
|
|
| 291 |
except NotFoundError:
|
| 292 |
async with self.client:
|
| 293 |
result = await self.client.get_prompt(name, arguments)
|
| 294 |
-
return
|
|
|
|
| 19 |
|
| 20 |
from fastmcp.client import Client
|
| 21 |
from fastmcp.exceptions import NotFoundError
|
| 22 |
+
from fastmcp.prompts import Prompt, PromptMessage
|
| 23 |
from fastmcp.resources import Resource, ResourceTemplate
|
| 24 |
from fastmcp.server.context import Context
|
| 25 |
from fastmcp.server.server import FastMCP
|
|
|
|
| 175 |
self,
|
| 176 |
arguments: dict[str, Any],
|
| 177 |
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
| 178 |
+
) -> list[PromptMessage]:
|
| 179 |
async with self._client:
|
| 180 |
result = await self._client.get_prompt(self.name, arguments)
|
| 181 |
+
return result.messages
|
| 182 |
|
| 183 |
|
| 184 |
class FastMCPProxy(FastMCP):
|
|
|
|
| 291 |
except NotFoundError:
|
| 292 |
async with self.client:
|
| 293 |
result = await self.client.get_prompt(name, arguments)
|
| 294 |
+
return result
|
src/fastmcp/server/server.py
CHANGED
|
@@ -32,7 +32,6 @@ from mcp.types import (
|
|
| 32 |
EmbeddedResource,
|
| 33 |
GetPromptResult,
|
| 34 |
ImageContent,
|
| 35 |
-
PromptMessage,
|
| 36 |
TextContent,
|
| 37 |
ToolAnnotations,
|
| 38 |
)
|
|
@@ -504,15 +503,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 504 |
"""
|
| 505 |
if self._prompt_manager.has_prompt(name):
|
| 506 |
context = self.get_context()
|
| 507 |
-
|
| 508 |
name, arguments=arguments or {}, context=context
|
| 509 |
)
|
| 510 |
-
|
| 511 |
-
return GetPromptResult(
|
| 512 |
-
messages=[
|
| 513 |
-
PromptMessage(role=m.role, content=m.content) for m in messages
|
| 514 |
-
]
|
| 515 |
-
)
|
| 516 |
else:
|
| 517 |
for server in self._mounted_servers.values():
|
| 518 |
if server.match_prompt(name):
|
|
|
|
| 32 |
EmbeddedResource,
|
| 33 |
GetPromptResult,
|
| 34 |
ImageContent,
|
|
|
|
| 35 |
TextContent,
|
| 36 |
ToolAnnotations,
|
| 37 |
)
|
|
|
|
| 503 |
"""
|
| 504 |
if self._prompt_manager.has_prompt(name):
|
| 505 |
context = self.get_context()
|
| 506 |
+
prompt_result = await self._prompt_manager.render_prompt(
|
| 507 |
name, arguments=arguments or {}, context=context
|
| 508 |
)
|
| 509 |
+
return prompt_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
else:
|
| 511 |
for server in self._mounted_servers.values():
|
| 512 |
if server.match_prompt(name):
|
tests/client/test_client.py
CHANGED
|
@@ -5,6 +5,7 @@ from pydantic import AnyUrl
|
|
| 5 |
|
| 6 |
from fastmcp.client import Client
|
| 7 |
from fastmcp.client.transports import FastMCPTransport
|
|
|
|
| 8 |
from fastmcp.server.server import FastMCP
|
| 9 |
|
| 10 |
|
|
@@ -38,6 +39,7 @@ def fastmcp_server():
|
|
| 38 |
# Add a prompt
|
| 39 |
@server.prompt()
|
| 40 |
def welcome(name: str) -> str:
|
|
|
|
| 41 |
return f"Welcome to FastMCP, {name}!"
|
| 42 |
|
| 43 |
return server
|
|
@@ -182,8 +184,9 @@ async def test_get_prompt(fastmcp_server):
|
|
| 182 |
result = await client.get_prompt("welcome", {"name": "Developer"})
|
| 183 |
|
| 184 |
# The result should contain our welcome message
|
| 185 |
-
|
| 186 |
-
assert "Welcome to FastMCP, Developer!"
|
|
|
|
| 187 |
|
| 188 |
|
| 189 |
async def test_get_prompt_mcp(fastmcp_server):
|
|
@@ -193,11 +196,10 @@ async def test_get_prompt_mcp(fastmcp_server):
|
|
| 193 |
async with client:
|
| 194 |
result = await client.get_prompt_mcp("welcome", {"name": "Developer"})
|
| 195 |
|
| 196 |
-
#
|
| 197 |
-
assert
|
| 198 |
-
assert
|
| 199 |
-
|
| 200 |
-
assert "Welcome to FastMCP, Developer!" in result_str
|
| 201 |
|
| 202 |
|
| 203 |
async def test_read_resource(fastmcp_server):
|
|
|
|
| 5 |
|
| 6 |
from fastmcp.client import Client
|
| 7 |
from fastmcp.client.transports import FastMCPTransport
|
| 8 |
+
from fastmcp.prompts.prompt import TextContent
|
| 9 |
from fastmcp.server.server import FastMCP
|
| 10 |
|
| 11 |
|
|
|
|
| 39 |
# Add a prompt
|
| 40 |
@server.prompt()
|
| 41 |
def welcome(name: str) -> str:
|
| 42 |
+
"""Example greeting prompt."""
|
| 43 |
return f"Welcome to FastMCP, {name}!"
|
| 44 |
|
| 45 |
return server
|
|
|
|
| 184 |
result = await client.get_prompt("welcome", {"name": "Developer"})
|
| 185 |
|
| 186 |
# The result should contain our welcome message
|
| 187 |
+
assert isinstance(result.messages[0].content, TextContent)
|
| 188 |
+
assert result.messages[0].content.text == "Welcome to FastMCP, Developer!"
|
| 189 |
+
assert result.description == "Example greeting prompt."
|
| 190 |
|
| 191 |
|
| 192 |
async def test_get_prompt_mcp(fastmcp_server):
|
|
|
|
| 196 |
async with client:
|
| 197 |
result = await client.get_prompt_mcp("welcome", {"name": "Developer"})
|
| 198 |
|
| 199 |
+
# The result should contain our welcome message
|
| 200 |
+
assert isinstance(result.messages[0].content, TextContent)
|
| 201 |
+
assert result.messages[0].content.text == "Welcome to FastMCP, Developer!"
|
| 202 |
+
assert result.description == "Example greeting prompt."
|
|
|
|
| 203 |
|
| 204 |
|
| 205 |
async def test_read_resource(fastmcp_server):
|
tests/prompts/{test_base.py → test_prompt.py}
RENAMED
|
@@ -3,11 +3,10 @@ from mcp.types import EmbeddedResource, TextResourceContents
|
|
| 3 |
from pydantic import FileUrl
|
| 4 |
|
| 5 |
from fastmcp.prompts.prompt import (
|
| 6 |
-
AssistantMessage,
|
| 7 |
Message,
|
| 8 |
Prompt,
|
|
|
|
| 9 |
TextContent,
|
| 10 |
-
UserMessage,
|
| 11 |
)
|
| 12 |
|
| 13 |
|
|
@@ -18,7 +17,9 @@ class TestRenderPrompt:
|
|
| 18 |
|
| 19 |
prompt = Prompt.from_function(fn)
|
| 20 |
assert await prompt.render() == [
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
]
|
| 23 |
|
| 24 |
async def test_async_fn(self):
|
|
@@ -27,7 +28,9 @@ class TestRenderPrompt:
|
|
| 27 |
|
| 28 |
prompt = Prompt.from_function(fn)
|
| 29 |
assert await prompt.render() == [
|
| 30 |
-
|
|
|
|
|
|
|
| 31 |
]
|
| 32 |
|
| 33 |
async def test_fn_with_args(self):
|
|
@@ -36,10 +39,11 @@ class TestRenderPrompt:
|
|
| 36 |
|
| 37 |
prompt = Prompt.from_function(fn)
|
| 38 |
assert await prompt.render(arguments=dict(name="World")) == [
|
| 39 |
-
|
|
|
|
| 40 |
content=TextContent(
|
| 41 |
type="text", text="Hello, World! You're 30 years old."
|
| 42 |
-
)
|
| 43 |
)
|
| 44 |
]
|
| 45 |
|
|
@@ -52,33 +56,42 @@ class TestRenderPrompt:
|
|
| 52 |
await prompt.render(arguments=dict(age=40))
|
| 53 |
|
| 54 |
async def test_fn_returns_message(self):
|
| 55 |
-
async def fn() ->
|
| 56 |
-
return
|
|
|
|
|
|
|
| 57 |
|
| 58 |
prompt = Prompt.from_function(fn)
|
| 59 |
assert await prompt.render() == [
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
]
|
| 62 |
|
| 63 |
async def test_fn_returns_assistant_message(self):
|
| 64 |
-
async def fn() ->
|
| 65 |
-
return
|
| 66 |
-
content=TextContent(type="text", text="Hello, world!")
|
| 67 |
)
|
| 68 |
|
| 69 |
prompt = Prompt.from_function(fn)
|
| 70 |
assert await prompt.render() == [
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
]
|
| 73 |
|
| 74 |
async def test_fn_returns_multiple_messages(self):
|
| 75 |
expected = [
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
| 79 |
]
|
| 80 |
|
| 81 |
-
async def fn() -> list[
|
| 82 |
return expected
|
| 83 |
|
| 84 |
prompt = Prompt.from_function(fn)
|
|
@@ -94,13 +107,17 @@ class TestRenderPrompt:
|
|
| 94 |
return expected
|
| 95 |
|
| 96 |
prompt = Prompt.from_function(fn)
|
| 97 |
-
assert await prompt.render() == [
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
async def test_fn_returns_resource_content(self):
|
| 100 |
"""Test returning a message with resource content."""
|
| 101 |
|
| 102 |
-
async def fn() ->
|
| 103 |
-
return
|
|
|
|
| 104 |
content=EmbeddedResource(
|
| 105 |
type="resource",
|
| 106 |
resource=TextResourceContents(
|
|
@@ -108,12 +125,13 @@ class TestRenderPrompt:
|
|
| 108 |
text="File contents",
|
| 109 |
mimeType="text/plain",
|
| 110 |
),
|
| 111 |
-
)
|
| 112 |
)
|
| 113 |
|
| 114 |
prompt = Prompt.from_function(fn)
|
| 115 |
assert await prompt.render() == [
|
| 116 |
-
|
|
|
|
| 117 |
content=EmbeddedResource(
|
| 118 |
type="resource",
|
| 119 |
resource=TextResourceContents(
|
|
@@ -121,17 +139,18 @@ class TestRenderPrompt:
|
|
| 121 |
text="File contents",
|
| 122 |
mimeType="text/plain",
|
| 123 |
),
|
| 124 |
-
)
|
| 125 |
)
|
| 126 |
]
|
| 127 |
|
| 128 |
async def test_fn_returns_mixed_content(self):
|
| 129 |
"""Test returning messages with mixed content types."""
|
| 130 |
|
| 131 |
-
async def fn() -> list[
|
| 132 |
return [
|
| 133 |
-
|
| 134 |
-
|
|
|
|
| 135 |
content=EmbeddedResource(
|
| 136 |
type="resource",
|
| 137 |
resource=TextResourceContents(
|
|
@@ -139,17 +158,19 @@ class TestRenderPrompt:
|
|
| 139 |
text="File contents",
|
| 140 |
mimeType="text/plain",
|
| 141 |
),
|
| 142 |
-
)
|
| 143 |
),
|
| 144 |
-
|
| 145 |
]
|
| 146 |
|
| 147 |
prompt = Prompt.from_function(fn)
|
| 148 |
assert await prompt.render() == [
|
| 149 |
-
|
| 150 |
-
|
|
|
|
| 151 |
),
|
| 152 |
-
|
|
|
|
| 153 |
content=EmbeddedResource(
|
| 154 |
type="resource",
|
| 155 |
resource=TextResourceContents(
|
|
@@ -157,32 +178,34 @@ class TestRenderPrompt:
|
|
| 157 |
text="File contents",
|
| 158 |
mimeType="text/plain",
|
| 159 |
),
|
| 160 |
-
)
|
| 161 |
),
|
| 162 |
-
|
| 163 |
-
|
|
|
|
| 164 |
),
|
| 165 |
]
|
| 166 |
|
| 167 |
-
async def
|
| 168 |
"""Test returning a dict with resource content."""
|
| 169 |
|
| 170 |
-
async def fn() ->
|
| 171 |
-
return
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
|
| 183 |
prompt = Prompt.from_function(fn)
|
| 184 |
assert await prompt.render() == [
|
| 185 |
-
|
|
|
|
| 186 |
content=EmbeddedResource(
|
| 187 |
type="resource",
|
| 188 |
resource=TextResourceContents(
|
|
@@ -190,6 +213,6 @@ class TestRenderPrompt:
|
|
| 190 |
text="File contents",
|
| 191 |
mimeType="text/plain",
|
| 192 |
),
|
| 193 |
-
)
|
| 194 |
)
|
| 195 |
]
|
|
|
|
| 3 |
from pydantic import FileUrl
|
| 4 |
|
| 5 |
from fastmcp.prompts.prompt import (
|
|
|
|
| 6 |
Message,
|
| 7 |
Prompt,
|
| 8 |
+
PromptMessage,
|
| 9 |
TextContent,
|
|
|
|
| 10 |
)
|
| 11 |
|
| 12 |
|
|
|
|
| 17 |
|
| 18 |
prompt = Prompt.from_function(fn)
|
| 19 |
assert await prompt.render() == [
|
| 20 |
+
PromptMessage(
|
| 21 |
+
role="user", content=TextContent(type="text", text="Hello, world!")
|
| 22 |
+
)
|
| 23 |
]
|
| 24 |
|
| 25 |
async def test_async_fn(self):
|
|
|
|
| 28 |
|
| 29 |
prompt = Prompt.from_function(fn)
|
| 30 |
assert await prompt.render() == [
|
| 31 |
+
PromptMessage(
|
| 32 |
+
role="user", content=TextContent(type="text", text="Hello, world!")
|
| 33 |
+
)
|
| 34 |
]
|
| 35 |
|
| 36 |
async def test_fn_with_args(self):
|
|
|
|
| 39 |
|
| 40 |
prompt = Prompt.from_function(fn)
|
| 41 |
assert await prompt.render(arguments=dict(name="World")) == [
|
| 42 |
+
PromptMessage(
|
| 43 |
+
role="user",
|
| 44 |
content=TextContent(
|
| 45 |
type="text", text="Hello, World! You're 30 years old."
|
| 46 |
+
),
|
| 47 |
)
|
| 48 |
]
|
| 49 |
|
|
|
|
| 56 |
await prompt.render(arguments=dict(age=40))
|
| 57 |
|
| 58 |
async def test_fn_returns_message(self):
|
| 59 |
+
async def fn() -> PromptMessage:
|
| 60 |
+
return PromptMessage(
|
| 61 |
+
role="user", content=TextContent(type="text", text="Hello, world!")
|
| 62 |
+
)
|
| 63 |
|
| 64 |
prompt = Prompt.from_function(fn)
|
| 65 |
assert await prompt.render() == [
|
| 66 |
+
PromptMessage(
|
| 67 |
+
role="user", content=TextContent(type="text", text="Hello, world!")
|
| 68 |
+
)
|
| 69 |
]
|
| 70 |
|
| 71 |
async def test_fn_returns_assistant_message(self):
|
| 72 |
+
async def fn() -> PromptMessage:
|
| 73 |
+
return PromptMessage(
|
| 74 |
+
role="assistant", content=TextContent(type="text", text="Hello, world!")
|
| 75 |
)
|
| 76 |
|
| 77 |
prompt = Prompt.from_function(fn)
|
| 78 |
assert await prompt.render() == [
|
| 79 |
+
PromptMessage(
|
| 80 |
+
role="assistant", content=TextContent(type="text", text="Hello, world!")
|
| 81 |
+
)
|
| 82 |
]
|
| 83 |
|
| 84 |
async def test_fn_returns_multiple_messages(self):
|
| 85 |
expected = [
|
| 86 |
+
Message(role="user", content="Hello, world!"),
|
| 87 |
+
Message(role="assistant", content="How can I help you today?"),
|
| 88 |
+
Message(
|
| 89 |
+
role="user",
|
| 90 |
+
content="I'm looking for a restaurant in the center of town.",
|
| 91 |
+
),
|
| 92 |
]
|
| 93 |
|
| 94 |
+
async def fn() -> list[PromptMessage]:
|
| 95 |
return expected
|
| 96 |
|
| 97 |
prompt = Prompt.from_function(fn)
|
|
|
|
| 107 |
return expected
|
| 108 |
|
| 109 |
prompt = Prompt.from_function(fn)
|
| 110 |
+
assert await prompt.render() == [
|
| 111 |
+
PromptMessage(role="user", content=TextContent(type="text", text=t))
|
| 112 |
+
for t in expected
|
| 113 |
+
]
|
| 114 |
|
| 115 |
async def test_fn_returns_resource_content(self):
|
| 116 |
"""Test returning a message with resource content."""
|
| 117 |
|
| 118 |
+
async def fn() -> PromptMessage:
|
| 119 |
+
return PromptMessage(
|
| 120 |
+
role="user",
|
| 121 |
content=EmbeddedResource(
|
| 122 |
type="resource",
|
| 123 |
resource=TextResourceContents(
|
|
|
|
| 125 |
text="File contents",
|
| 126 |
mimeType="text/plain",
|
| 127 |
),
|
| 128 |
+
),
|
| 129 |
)
|
| 130 |
|
| 131 |
prompt = Prompt.from_function(fn)
|
| 132 |
assert await prompt.render() == [
|
| 133 |
+
PromptMessage(
|
| 134 |
+
role="user",
|
| 135 |
content=EmbeddedResource(
|
| 136 |
type="resource",
|
| 137 |
resource=TextResourceContents(
|
|
|
|
| 139 |
text="File contents",
|
| 140 |
mimeType="text/plain",
|
| 141 |
),
|
| 142 |
+
),
|
| 143 |
)
|
| 144 |
]
|
| 145 |
|
| 146 |
async def test_fn_returns_mixed_content(self):
|
| 147 |
"""Test returning messages with mixed content types."""
|
| 148 |
|
| 149 |
+
async def fn() -> list[PromptMessage | str]:
|
| 150 |
return [
|
| 151 |
+
"Please analyze this file:",
|
| 152 |
+
PromptMessage(
|
| 153 |
+
role="user",
|
| 154 |
content=EmbeddedResource(
|
| 155 |
type="resource",
|
| 156 |
resource=TextResourceContents(
|
|
|
|
| 158 |
text="File contents",
|
| 159 |
mimeType="text/plain",
|
| 160 |
),
|
| 161 |
+
),
|
| 162 |
),
|
| 163 |
+
Message(role="assistant", content="I'll help analyze that file."),
|
| 164 |
]
|
| 165 |
|
| 166 |
prompt = Prompt.from_function(fn)
|
| 167 |
assert await prompt.render() == [
|
| 168 |
+
PromptMessage(
|
| 169 |
+
role="user",
|
| 170 |
+
content=TextContent(type="text", text="Please analyze this file:"),
|
| 171 |
),
|
| 172 |
+
PromptMessage(
|
| 173 |
+
role="user",
|
| 174 |
content=EmbeddedResource(
|
| 175 |
type="resource",
|
| 176 |
resource=TextResourceContents(
|
|
|
|
| 178 |
text="File contents",
|
| 179 |
mimeType="text/plain",
|
| 180 |
),
|
| 181 |
+
),
|
| 182 |
),
|
| 183 |
+
PromptMessage(
|
| 184 |
+
role="assistant",
|
| 185 |
+
content=TextContent(type="text", text="I'll help analyze that file."),
|
| 186 |
),
|
| 187 |
]
|
| 188 |
|
| 189 |
+
async def test_fn_returns_message_with_resource(self):
|
| 190 |
"""Test returning a dict with resource content."""
|
| 191 |
|
| 192 |
+
async def fn() -> PromptMessage:
|
| 193 |
+
return PromptMessage(
|
| 194 |
+
role="user",
|
| 195 |
+
content=EmbeddedResource(
|
| 196 |
+
type="resource",
|
| 197 |
+
resource=TextResourceContents(
|
| 198 |
+
uri=FileUrl("file://file.txt"),
|
| 199 |
+
text="File contents",
|
| 200 |
+
mimeType="text/plain",
|
| 201 |
+
),
|
| 202 |
+
),
|
| 203 |
+
)
|
| 204 |
|
| 205 |
prompt = Prompt.from_function(fn)
|
| 206 |
assert await prompt.render() == [
|
| 207 |
+
PromptMessage(
|
| 208 |
+
role="user",
|
| 209 |
content=EmbeddedResource(
|
| 210 |
type="resource",
|
| 211 |
resource=TextResourceContents(
|
|
|
|
| 213 |
text="File contents",
|
| 214 |
mimeType="text/plain",
|
| 215 |
),
|
| 216 |
+
),
|
| 217 |
)
|
| 218 |
]
|
tests/prompts/test_prompt_manager.py
CHANGED
|
@@ -7,7 +7,7 @@ from mcp.shared.context import LifespanContextT
|
|
| 7 |
from fastmcp import Context
|
| 8 |
from fastmcp.exceptions import NotFoundError
|
| 9 |
from fastmcp.prompts import Prompt
|
| 10 |
-
from fastmcp.prompts.prompt import
|
| 11 |
from fastmcp.prompts.prompt_manager import PromptManager
|
| 12 |
|
| 13 |
|
|
@@ -147,28 +147,36 @@ class TestPromptManager:
|
|
| 147 |
"""Test rendering a prompt."""
|
| 148 |
|
| 149 |
def fn() -> str:
|
|
|
|
| 150 |
return "Hello, world!"
|
| 151 |
|
| 152 |
manager = PromptManager()
|
| 153 |
prompt = Prompt.from_function(fn)
|
| 154 |
manager.add_prompt(prompt)
|
| 155 |
-
|
| 156 |
-
assert
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
| 158 |
]
|
| 159 |
|
| 160 |
async def test_render_prompt_with_args(self):
|
| 161 |
"""Test rendering a prompt with arguments."""
|
| 162 |
|
| 163 |
def fn(name: str) -> str:
|
|
|
|
| 164 |
return f"Hello, {name}!"
|
| 165 |
|
| 166 |
manager = PromptManager()
|
| 167 |
prompt = Prompt.from_function(fn)
|
| 168 |
manager.add_prompt(prompt)
|
| 169 |
-
|
| 170 |
-
assert
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
| 172 |
]
|
| 173 |
|
| 174 |
async def test_render_unknown_prompt(self):
|
|
|
|
| 7 |
from fastmcp import Context
|
| 8 |
from fastmcp.exceptions import NotFoundError
|
| 9 |
from fastmcp.prompts import Prompt
|
| 10 |
+
from fastmcp.prompts.prompt import PromptMessage, TextContent
|
| 11 |
from fastmcp.prompts.prompt_manager import PromptManager
|
| 12 |
|
| 13 |
|
|
|
|
| 147 |
"""Test rendering a prompt."""
|
| 148 |
|
| 149 |
def fn() -> str:
|
| 150 |
+
"""An example prompt."""
|
| 151 |
return "Hello, world!"
|
| 152 |
|
| 153 |
manager = PromptManager()
|
| 154 |
prompt = Prompt.from_function(fn)
|
| 155 |
manager.add_prompt(prompt)
|
| 156 |
+
result = await manager.render_prompt("fn")
|
| 157 |
+
assert result.description == "An example prompt."
|
| 158 |
+
assert result.messages == [
|
| 159 |
+
PromptMessage(
|
| 160 |
+
role="user", content=TextContent(type="text", text="Hello, world!")
|
| 161 |
+
)
|
| 162 |
]
|
| 163 |
|
| 164 |
async def test_render_prompt_with_args(self):
|
| 165 |
"""Test rendering a prompt with arguments."""
|
| 166 |
|
| 167 |
def fn(name: str) -> str:
|
| 168 |
+
"""An example prompt."""
|
| 169 |
return f"Hello, {name}!"
|
| 170 |
|
| 171 |
manager = PromptManager()
|
| 172 |
prompt = Prompt.from_function(fn)
|
| 173 |
manager.add_prompt(prompt)
|
| 174 |
+
result = await manager.render_prompt("fn", arguments={"name": "World"})
|
| 175 |
+
assert result.description == "An example prompt."
|
| 176 |
+
assert result.messages == [
|
| 177 |
+
PromptMessage(
|
| 178 |
+
role="user", content=TextContent(type="text", text="Hello, World!")
|
| 179 |
+
)
|
| 180 |
]
|
| 181 |
|
| 182 |
async def test_render_unknown_prompt(self):
|
tests/server/test_import_server.py
CHANGED
|
@@ -319,14 +319,16 @@ async def test_import_with_proxy_prompts():
|
|
| 319 |
|
| 320 |
@api_app.prompt()
|
| 321 |
def greeting(name: str) -> str:
|
|
|
|
| 322 |
return f"Hello, {name} from API!"
|
| 323 |
|
| 324 |
proxy_app = FastMCP.from_client(Client(api_app))
|
| 325 |
await main_app.import_server("api", proxy_app)
|
| 326 |
|
| 327 |
result = await main_app._mcp_get_prompt("api_greeting", {"name": "World"})
|
| 328 |
-
assert result.messages
|
| 329 |
-
|
|
|
|
| 330 |
|
| 331 |
|
| 332 |
async def test_import_with_proxy_resources():
|
|
|
|
| 319 |
|
| 320 |
@api_app.prompt()
|
| 321 |
def greeting(name: str) -> str:
|
| 322 |
+
"""Example greeting prompt."""
|
| 323 |
return f"Hello, {name} from API!"
|
| 324 |
|
| 325 |
proxy_app = FastMCP.from_client(Client(api_app))
|
| 326 |
await main_app.import_server("api", proxy_app)
|
| 327 |
|
| 328 |
result = await main_app._mcp_get_prompt("api_greeting", {"name": "World"})
|
| 329 |
+
assert isinstance(result.messages[0].content, TextContent)
|
| 330 |
+
assert result.messages[0].content.text == "Hello, World from API!"
|
| 331 |
+
assert result.description == "Example greeting prompt."
|
| 332 |
|
| 333 |
|
| 334 |
async def test_import_with_proxy_resources():
|
tests/server/test_proxy.py
CHANGED
|
@@ -207,7 +207,7 @@ class TestPrompts:
|
|
| 207 |
async def test_render_prompt_calls_prompt(self, proxy_server):
|
| 208 |
async with Client(proxy_server) as client:
|
| 209 |
result = await client.get_prompt("welcome", {"name": "Alice"})
|
| 210 |
-
assert isinstance(result[0], mcp.types.PromptMessage)
|
| 211 |
-
assert result[0].role == "user"
|
| 212 |
-
assert isinstance(result[0].content, mcp.types.TextContent)
|
| 213 |
-
assert result[0].content.text == "Welcome to FastMCP, Alice!"
|
|
|
|
| 207 |
async def test_render_prompt_calls_prompt(self, proxy_server):
|
| 208 |
async with Client(proxy_server) as client:
|
| 209 |
result = await client.get_prompt("welcome", {"name": "Alice"})
|
| 210 |
+
assert isinstance(result.messages[0], mcp.types.PromptMessage)
|
| 211 |
+
assert result.messages[0].role == "user"
|
| 212 |
+
assert isinstance(result.messages[0].content, mcp.types.TextContent)
|
| 213 |
+
assert result.messages[0].content.text == "Welcome to FastMCP, Alice!"
|
tests/server/test_server.py
CHANGED
|
@@ -640,16 +640,16 @@ class TestPromptDecorator:
|
|
| 640 |
|
| 641 |
async with Client(mcp) as client:
|
| 642 |
result = await client.get_prompt("test_prompt", {"name": "World"})
|
| 643 |
-
assert len(result) == 1
|
| 644 |
-
message = result[0]
|
| 645 |
assert isinstance(message.content, TextContent)
|
| 646 |
assert message.content.text == "Hello, World!"
|
| 647 |
|
| 648 |
result = await client.get_prompt(
|
| 649 |
"test_prompt", {"name": "World", "greeting": "Hi"}
|
| 650 |
)
|
| 651 |
-
assert len(result) == 1
|
| 652 |
-
message = result[0]
|
| 653 |
assert isinstance(message.content, TextContent)
|
| 654 |
assert message.content.text == "Hi, World!"
|
| 655 |
|
|
@@ -668,8 +668,8 @@ class TestPromptDecorator:
|
|
| 668 |
|
| 669 |
async with Client(mcp) as client:
|
| 670 |
result = await client.get_prompt("test_prompt")
|
| 671 |
-
assert len(result) == 1
|
| 672 |
-
message = result[0]
|
| 673 |
assert isinstance(message.content, TextContent)
|
| 674 |
assert message.content.text == "My prefix: Hello, world!"
|
| 675 |
|
|
@@ -687,8 +687,8 @@ class TestPromptDecorator:
|
|
| 687 |
|
| 688 |
async with Client(mcp) as client:
|
| 689 |
result = await client.get_prompt("test_prompt")
|
| 690 |
-
assert len(result) == 1
|
| 691 |
-
message = result[0]
|
| 692 |
assert isinstance(message.content, TextContent)
|
| 693 |
assert message.content.text == "Class prefix: Hello, world!"
|
| 694 |
|
|
@@ -703,8 +703,8 @@ class TestPromptDecorator:
|
|
| 703 |
|
| 704 |
async with Client(mcp) as client:
|
| 705 |
result = await client.get_prompt("test_prompt")
|
| 706 |
-
assert len(result) == 1
|
| 707 |
-
message = result[0]
|
| 708 |
assert isinstance(message.content, TextContent)
|
| 709 |
assert message.content.text == "Static Hello, world!"
|
| 710 |
|
|
@@ -717,8 +717,8 @@ class TestPromptDecorator:
|
|
| 717 |
|
| 718 |
async with Client(mcp) as client:
|
| 719 |
result = await client.get_prompt("test_prompt")
|
| 720 |
-
assert len(result) == 1
|
| 721 |
-
message = result[0]
|
| 722 |
assert isinstance(message.content, TextContent)
|
| 723 |
assert message.content.text == "Async Hello, world!"
|
| 724 |
|
|
|
|
| 640 |
|
| 641 |
async with Client(mcp) as client:
|
| 642 |
result = await client.get_prompt("test_prompt", {"name": "World"})
|
| 643 |
+
assert len(result.messages) == 1
|
| 644 |
+
message = result.messages[0]
|
| 645 |
assert isinstance(message.content, TextContent)
|
| 646 |
assert message.content.text == "Hello, World!"
|
| 647 |
|
| 648 |
result = await client.get_prompt(
|
| 649 |
"test_prompt", {"name": "World", "greeting": "Hi"}
|
| 650 |
)
|
| 651 |
+
assert len(result.messages) == 1
|
| 652 |
+
message = result.messages[0]
|
| 653 |
assert isinstance(message.content, TextContent)
|
| 654 |
assert message.content.text == "Hi, World!"
|
| 655 |
|
|
|
|
| 668 |
|
| 669 |
async with Client(mcp) as client:
|
| 670 |
result = await client.get_prompt("test_prompt")
|
| 671 |
+
assert len(result.messages) == 1
|
| 672 |
+
message = result.messages[0]
|
| 673 |
assert isinstance(message.content, TextContent)
|
| 674 |
assert message.content.text == "My prefix: Hello, world!"
|
| 675 |
|
|
|
|
| 687 |
|
| 688 |
async with Client(mcp) as client:
|
| 689 |
result = await client.get_prompt("test_prompt")
|
| 690 |
+
assert len(result.messages) == 1
|
| 691 |
+
message = result.messages[0]
|
| 692 |
assert isinstance(message.content, TextContent)
|
| 693 |
assert message.content.text == "Class prefix: Hello, world!"
|
| 694 |
|
|
|
|
| 703 |
|
| 704 |
async with Client(mcp) as client:
|
| 705 |
result = await client.get_prompt("test_prompt")
|
| 706 |
+
assert len(result.messages) == 1
|
| 707 |
+
message = result.messages[0]
|
| 708 |
assert isinstance(message.content, TextContent)
|
| 709 |
assert message.content.text == "Static Hello, world!"
|
| 710 |
|
|
|
|
| 717 |
|
| 718 |
async with Client(mcp) as client:
|
| 719 |
result = await client.get_prompt("test_prompt")
|
| 720 |
+
assert len(result.messages) == 1
|
| 721 |
+
message = result.messages[0]
|
| 722 |
assert isinstance(message.content, TextContent)
|
| 723 |
assert message.content.text == "Async Hello, world!"
|
| 724 |
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -18,7 +18,7 @@ from pydantic import AnyUrl, Field
|
|
| 18 |
|
| 19 |
from fastmcp import Client, Context, FastMCP
|
| 20 |
from fastmcp.exceptions import ClientError
|
| 21 |
-
from fastmcp.prompts.prompt import EmbeddedResource,
|
| 22 |
from fastmcp.resources import FileResource, FunctionResource
|
| 23 |
from fastmcp.utilities.types import Image
|
| 24 |
|
|
@@ -1267,8 +1267,8 @@ class TestPrompts:
|
|
| 1267 |
|
| 1268 |
async with Client(mcp) as client:
|
| 1269 |
result = await client.get_prompt("fn", {"name": "World"})
|
| 1270 |
-
assert len(result) == 1
|
| 1271 |
-
message = result[0]
|
| 1272 |
assert message.role == "user"
|
| 1273 |
content = message.content
|
| 1274 |
assert isinstance(content, TextContent)
|
|
@@ -1279,8 +1279,9 @@ class TestPrompts:
|
|
| 1279 |
mcp = FastMCP()
|
| 1280 |
|
| 1281 |
@mcp.prompt()
|
| 1282 |
-
def fn() ->
|
| 1283 |
-
return
|
|
|
|
| 1284 |
content=EmbeddedResource(
|
| 1285 |
type="resource",
|
| 1286 |
resource=TextResourceContents(
|
|
@@ -1288,13 +1289,13 @@ class TestPrompts:
|
|
| 1288 |
text="File contents",
|
| 1289 |
mimeType="text/plain",
|
| 1290 |
),
|
| 1291 |
-
)
|
| 1292 |
)
|
| 1293 |
|
| 1294 |
async with Client(mcp) as client:
|
| 1295 |
result = await client.get_prompt("fn")
|
| 1296 |
-
assert result[0].role == "user"
|
| 1297 |
-
content = result[0].content
|
| 1298 |
assert isinstance(content, EmbeddedResource)
|
| 1299 |
resource = content.resource
|
| 1300 |
assert isinstance(resource, TextResourceContents)
|
|
@@ -1370,6 +1371,6 @@ class TestPromptContext:
|
|
| 1370 |
|
| 1371 |
async with Client(mcp) as client:
|
| 1372 |
result = await client.get_prompt("prompt_fn", {"name": "World"})
|
| 1373 |
-
assert len(result) == 1
|
| 1374 |
-
message = result[0]
|
| 1375 |
assert message.role == "user"
|
|
|
|
| 18 |
|
| 19 |
from fastmcp import Client, Context, FastMCP
|
| 20 |
from fastmcp.exceptions import ClientError
|
| 21 |
+
from fastmcp.prompts.prompt import EmbeddedResource, PromptMessage
|
| 22 |
from fastmcp.resources import FileResource, FunctionResource
|
| 23 |
from fastmcp.utilities.types import Image
|
| 24 |
|
|
|
|
| 1267 |
|
| 1268 |
async with Client(mcp) as client:
|
| 1269 |
result = await client.get_prompt("fn", {"name": "World"})
|
| 1270 |
+
assert len(result.messages) == 1
|
| 1271 |
+
message = result.messages[0]
|
| 1272 |
assert message.role == "user"
|
| 1273 |
content = message.content
|
| 1274 |
assert isinstance(content, TextContent)
|
|
|
|
| 1279 |
mcp = FastMCP()
|
| 1280 |
|
| 1281 |
@mcp.prompt()
|
| 1282 |
+
def fn() -> PromptMessage:
|
| 1283 |
+
return PromptMessage(
|
| 1284 |
+
role="user",
|
| 1285 |
content=EmbeddedResource(
|
| 1286 |
type="resource",
|
| 1287 |
resource=TextResourceContents(
|
|
|
|
| 1289 |
text="File contents",
|
| 1290 |
mimeType="text/plain",
|
| 1291 |
),
|
| 1292 |
+
),
|
| 1293 |
)
|
| 1294 |
|
| 1295 |
async with Client(mcp) as client:
|
| 1296 |
result = await client.get_prompt("fn")
|
| 1297 |
+
assert result.messages[0].role == "user"
|
| 1298 |
+
content = result.messages[0].content
|
| 1299 |
assert isinstance(content, EmbeddedResource)
|
| 1300 |
resource = content.resource
|
| 1301 |
assert isinstance(resource, TextResourceContents)
|
|
|
|
| 1371 |
|
| 1372 |
async with Client(mcp) as client:
|
| 1373 |
result = await client.get_prompt("prompt_fn", {"name": "World"})
|
| 1374 |
+
assert len(result.messages) == 1
|
| 1375 |
+
message = result.messages[0]
|
| 1376 |
assert message.role == "user"
|