fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
fastmcp/prompts/prompt.py
CHANGED
|
@@ -2,51 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations as _annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
from
|
|
8
|
-
from typing import Annotated, Any
|
|
5
|
+
import warnings
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, overload
|
|
9
8
|
|
|
9
|
+
import pydantic
|
|
10
10
|
import pydantic_core
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from docket import Docket
|
|
14
|
+
from docket.execution import Execution
|
|
15
|
+
|
|
16
|
+
from fastmcp.prompts.function_prompt import FunctionPrompt
|
|
17
|
+
import mcp.types
|
|
18
|
+
from mcp import GetPromptResult
|
|
19
|
+
from mcp.types import (
|
|
20
|
+
EmbeddedResource,
|
|
21
|
+
Icon,
|
|
22
|
+
PromptMessage,
|
|
23
|
+
TextContent,
|
|
24
|
+
)
|
|
12
25
|
from mcp.types import Prompt as SDKPrompt
|
|
13
26
|
from mcp.types import PromptArgument as SDKPromptArgument
|
|
14
|
-
from pydantic import Field
|
|
27
|
+
from pydantic import Field
|
|
15
28
|
|
|
16
|
-
from fastmcp.
|
|
17
|
-
from fastmcp.
|
|
18
|
-
from fastmcp.server.tasks.config import TaskConfig
|
|
29
|
+
from fastmcp.server.tasks.config import TaskConfig, TaskMeta
|
|
30
|
+
from fastmcp.tools.tool import AuthCheckCallable
|
|
19
31
|
from fastmcp.utilities.components import FastMCPComponent
|
|
20
|
-
from fastmcp.utilities.json_schema import compress_schema
|
|
21
32
|
from fastmcp.utilities.logging import get_logger
|
|
22
33
|
from fastmcp.utilities.types import (
|
|
23
34
|
FastMCPBaseModel,
|
|
24
|
-
get_cached_typeadapter,
|
|
25
35
|
)
|
|
26
36
|
|
|
27
37
|
logger = get_logger(__name__)
|
|
28
38
|
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
) -> PromptMessage:
|
|
33
|
-
"""A user-friendly constructor for PromptMessage."""
|
|
34
|
-
if isinstance(content, str):
|
|
35
|
-
content = TextContent(type="text", text=content)
|
|
36
|
-
if role is None:
|
|
37
|
-
role = "user"
|
|
38
|
-
return PromptMessage(content=content, role=role, **kwargs)
|
|
40
|
+
class Message(pydantic.BaseModel):
|
|
41
|
+
"""Wrapper for prompt message with auto-serialization.
|
|
39
42
|
|
|
43
|
+
Accepts any content - strings pass through, other types
|
|
44
|
+
(dict, list, BaseModel) are JSON-serialized to text.
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
Example:
|
|
47
|
+
```python
|
|
48
|
+
from fastmcp.prompts import Message
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
# String content (user role by default)
|
|
51
|
+
Message("Hello, world!")
|
|
52
|
+
|
|
53
|
+
# Explicit role
|
|
54
|
+
Message("I can help with that.", role="assistant")
|
|
55
|
+
|
|
56
|
+
# Auto-serialized to JSON
|
|
57
|
+
Message({"key": "value"})
|
|
58
|
+
Message(["item1", "item2"])
|
|
59
|
+
```
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
role: Literal["user", "assistant"]
|
|
63
|
+
content: TextContent | EmbeddedResource
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
content: Any,
|
|
68
|
+
role: Literal["user", "assistant"] = "user",
|
|
69
|
+
):
|
|
70
|
+
"""Create Message with automatic serialization.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
content: The message content. str passes through directly.
|
|
74
|
+
TextContent and EmbeddedResource pass through.
|
|
75
|
+
Other types (dict, list, BaseModel) are JSON-serialized.
|
|
76
|
+
role: The message role, either "user" or "assistant".
|
|
77
|
+
"""
|
|
78
|
+
# Handle already-wrapped content types
|
|
79
|
+
if isinstance(content, (TextContent, EmbeddedResource)):
|
|
80
|
+
normalized_content: TextContent | EmbeddedResource = content
|
|
81
|
+
elif isinstance(content, str):
|
|
82
|
+
normalized_content = TextContent(type="text", text=content)
|
|
83
|
+
else:
|
|
84
|
+
# dict, list, BaseModel → JSON string
|
|
85
|
+
serialized = pydantic_core.to_json(content, fallback=str).decode()
|
|
86
|
+
normalized_content = TextContent(type="text", text=serialized)
|
|
87
|
+
|
|
88
|
+
super().__init__(role=role, content=normalized_content)
|
|
89
|
+
|
|
90
|
+
def to_mcp_prompt_message(self) -> PromptMessage:
|
|
91
|
+
"""Convert to MCP PromptMessage."""
|
|
92
|
+
return PromptMessage(role=self.role, content=self.content)
|
|
50
93
|
|
|
51
94
|
|
|
52
95
|
class PromptArgument(FastMCPBaseModel):
|
|
@@ -61,33 +104,102 @@ class PromptArgument(FastMCPBaseModel):
|
|
|
61
104
|
)
|
|
62
105
|
|
|
63
106
|
|
|
107
|
+
class PromptResult(pydantic.BaseModel):
|
|
108
|
+
"""Canonical result type for prompt rendering.
|
|
109
|
+
|
|
110
|
+
Provides explicit control over prompt responses: multiple messages,
|
|
111
|
+
roles, and metadata at both the message and result level.
|
|
112
|
+
|
|
113
|
+
Accepts:
|
|
114
|
+
- str: Wrapped as single Message (user role)
|
|
115
|
+
- list[Message]: Used directly for multiple messages or custom roles
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```python
|
|
119
|
+
from fastmcp import FastMCP
|
|
120
|
+
from fastmcp.prompts import PromptResult, Message
|
|
121
|
+
|
|
122
|
+
mcp = FastMCP()
|
|
123
|
+
|
|
124
|
+
# Simple string content
|
|
125
|
+
@mcp.prompt
|
|
126
|
+
def greet() -> PromptResult:
|
|
127
|
+
return PromptResult("Hello!")
|
|
128
|
+
|
|
129
|
+
# Multiple messages with roles
|
|
130
|
+
@mcp.prompt
|
|
131
|
+
def conversation() -> PromptResult:
|
|
132
|
+
return PromptResult([
|
|
133
|
+
Message("What's the weather?"),
|
|
134
|
+
Message("It's sunny today.", role="assistant"),
|
|
135
|
+
])
|
|
136
|
+
```
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
messages: list[Message]
|
|
140
|
+
description: str | None = None
|
|
141
|
+
meta: dict[str, Any] | None = None
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
messages: str | list[Message],
|
|
146
|
+
description: str | None = None,
|
|
147
|
+
meta: dict[str, Any] | None = None,
|
|
148
|
+
):
|
|
149
|
+
"""Create PromptResult.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
messages: String or list of Message objects.
|
|
153
|
+
description: Optional description of the prompt result.
|
|
154
|
+
meta: Optional metadata about the prompt result.
|
|
155
|
+
"""
|
|
156
|
+
normalized = self._normalize_messages(messages)
|
|
157
|
+
super().__init__(messages=normalized, description=description, meta=meta)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _normalize_messages(
|
|
161
|
+
messages: str | list[Message],
|
|
162
|
+
) -> list[Message]:
|
|
163
|
+
"""Normalize input to list[Message]."""
|
|
164
|
+
if isinstance(messages, str):
|
|
165
|
+
return [Message(messages)]
|
|
166
|
+
if isinstance(messages, list):
|
|
167
|
+
# Validate all items are Message
|
|
168
|
+
for i, item in enumerate(messages):
|
|
169
|
+
if not isinstance(item, Message):
|
|
170
|
+
raise TypeError(
|
|
171
|
+
f"messages[{i}] must be Message, got {type(item).__name__}. "
|
|
172
|
+
f"Use Message({item!r}) to wrap the value."
|
|
173
|
+
)
|
|
174
|
+
return messages
|
|
175
|
+
raise TypeError(
|
|
176
|
+
f"messages must be str or list[Message], got {type(messages).__name__}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def to_mcp_prompt_result(self) -> GetPromptResult:
|
|
180
|
+
"""Convert to MCP GetPromptResult."""
|
|
181
|
+
mcp_messages = [m.to_mcp_prompt_message() for m in self.messages]
|
|
182
|
+
return GetPromptResult(
|
|
183
|
+
description=self.description,
|
|
184
|
+
messages=mcp_messages,
|
|
185
|
+
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
64
189
|
class Prompt(FastMCPComponent):
|
|
65
190
|
"""A prompt template that can be rendered with parameters."""
|
|
66
191
|
|
|
192
|
+
KEY_PREFIX: ClassVar[str] = "prompt"
|
|
193
|
+
|
|
67
194
|
arguments: list[PromptArgument] | None = Field(
|
|
68
195
|
default=None, description="Arguments that can be passed to the prompt"
|
|
69
196
|
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
context = get_context()
|
|
75
|
-
context._queue_prompt_list_changed() # type: ignore[private-use]
|
|
76
|
-
except RuntimeError:
|
|
77
|
-
pass # No context available
|
|
78
|
-
|
|
79
|
-
def disable(self) -> None:
|
|
80
|
-
super().disable()
|
|
81
|
-
try:
|
|
82
|
-
context = get_context()
|
|
83
|
-
context._queue_prompt_list_changed() # type: ignore[private-use]
|
|
84
|
-
except RuntimeError:
|
|
85
|
-
pass # No context available
|
|
197
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = Field(
|
|
198
|
+
default=None, description="Authorization checks for this prompt", exclude=True
|
|
199
|
+
)
|
|
86
200
|
|
|
87
201
|
def to_mcp_prompt(
|
|
88
202
|
self,
|
|
89
|
-
*,
|
|
90
|
-
include_fastmcp_meta: bool | None = None,
|
|
91
203
|
**overrides: Any,
|
|
92
204
|
) -> SDKPrompt:
|
|
93
205
|
"""Convert the prompt to an MCP prompt."""
|
|
@@ -106,276 +218,214 @@ class Prompt(FastMCPComponent):
|
|
|
106
218
|
arguments=arguments,
|
|
107
219
|
title=overrides.get("title", self.title),
|
|
108
220
|
icons=overrides.get("icons", self.icons),
|
|
109
|
-
_meta=overrides.get(
|
|
110
|
-
"_meta", self.get_meta(
|
|
221
|
+
_meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
|
222
|
+
"_meta", self.get_meta()
|
|
111
223
|
),
|
|
112
224
|
)
|
|
113
225
|
|
|
114
|
-
@
|
|
226
|
+
@classmethod
|
|
115
227
|
def from_function(
|
|
116
|
-
|
|
228
|
+
cls,
|
|
229
|
+
fn: Callable[..., Any],
|
|
230
|
+
*,
|
|
117
231
|
name: str | None = None,
|
|
232
|
+
version: str | int | None = None,
|
|
118
233
|
title: str | None = None,
|
|
119
234
|
description: str | None = None,
|
|
120
235
|
icons: list[Icon] | None = None,
|
|
121
236
|
tags: set[str] | None = None,
|
|
122
|
-
enabled: bool | None = None,
|
|
123
237
|
meta: dict[str, Any] | None = None,
|
|
124
238
|
task: bool | TaskConfig | None = None,
|
|
239
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
125
240
|
) -> FunctionPrompt:
|
|
126
241
|
"""Create a Prompt from a function.
|
|
127
242
|
|
|
128
243
|
The function can return:
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- A sequence of any of the above
|
|
244
|
+
- str: wrapped as single user Message
|
|
245
|
+
- list[Message | str]: converted to list[Message]
|
|
246
|
+
- PromptResult: used directly
|
|
133
247
|
"""
|
|
248
|
+
from fastmcp.prompts.function_prompt import FunctionPrompt
|
|
249
|
+
|
|
134
250
|
return FunctionPrompt.from_function(
|
|
135
251
|
fn=fn,
|
|
136
252
|
name=name,
|
|
253
|
+
version=version,
|
|
137
254
|
title=title,
|
|
138
255
|
description=description,
|
|
139
256
|
icons=icons,
|
|
140
257
|
tags=tags,
|
|
141
|
-
enabled=enabled,
|
|
142
258
|
meta=meta,
|
|
143
259
|
task=task,
|
|
260
|
+
auth=auth,
|
|
144
261
|
)
|
|
145
262
|
|
|
146
263
|
async def render(
|
|
147
264
|
self,
|
|
148
265
|
arguments: dict[str, Any] | None = None,
|
|
149
|
-
) -> list[
|
|
266
|
+
) -> str | list[Message | str] | PromptResult:
|
|
150
267
|
"""Render the prompt with arguments.
|
|
151
268
|
|
|
152
|
-
|
|
153
|
-
|
|
269
|
+
Subclasses must implement this method. Return one of:
|
|
270
|
+
- str: Wrapped as single user Message
|
|
271
|
+
- list[Message | str]: Converted to list[Message]
|
|
272
|
+
- PromptResult: Used directly
|
|
154
273
|
"""
|
|
155
274
|
raise NotImplementedError("Subclasses must implement render()")
|
|
156
275
|
|
|
276
|
+
def convert_result(self, raw_value: Any) -> PromptResult:
|
|
277
|
+
"""Convert a raw return value to PromptResult.
|
|
157
278
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
task_config: Annotated[
|
|
163
|
-
TaskConfig,
|
|
164
|
-
Field(description="Background task execution configuration (SEP-1686)."),
|
|
165
|
-
] = Field(default_factory=lambda: TaskConfig(mode="forbidden"))
|
|
279
|
+
Accepts:
|
|
280
|
+
- PromptResult: passed through
|
|
281
|
+
- str: wrapped as single Message
|
|
282
|
+
- list[Message | str]: converted to list[Message]
|
|
166
283
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
cls,
|
|
170
|
-
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
|
171
|
-
name: str | None = None,
|
|
172
|
-
title: str | None = None,
|
|
173
|
-
description: str | None = None,
|
|
174
|
-
icons: list[Icon] | None = None,
|
|
175
|
-
tags: set[str] | None = None,
|
|
176
|
-
enabled: bool | None = None,
|
|
177
|
-
meta: dict[str, Any] | None = None,
|
|
178
|
-
task: bool | TaskConfig | None = None,
|
|
179
|
-
) -> FunctionPrompt:
|
|
180
|
-
"""Create a Prompt from a function.
|
|
181
|
-
|
|
182
|
-
The function can return:
|
|
183
|
-
- A string (converted to a message)
|
|
184
|
-
- A Message object
|
|
185
|
-
- A dict (converted to a message)
|
|
186
|
-
- A sequence of any of the above
|
|
284
|
+
Raises:
|
|
285
|
+
TypeError: for unsupported types
|
|
187
286
|
"""
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
task_config = TaskConfig(mode="forbidden")
|
|
206
|
-
elif isinstance(task, bool):
|
|
207
|
-
task_config = TaskConfig.from_bool(task)
|
|
208
|
-
else:
|
|
209
|
-
task_config = task
|
|
210
|
-
task_config.validate_function(fn, func_name)
|
|
211
|
-
|
|
212
|
-
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
213
|
-
if not inspect.isroutine(fn):
|
|
214
|
-
fn = fn.__call__
|
|
215
|
-
# if the fn is a staticmethod, we need to work with the underlying function
|
|
216
|
-
if isinstance(fn, staticmethod):
|
|
217
|
-
fn = fn.__func__ # type: ignore[assignment]
|
|
218
|
-
|
|
219
|
-
# Wrap fn to handle dependency resolution internally
|
|
220
|
-
wrapped_fn = without_injected_parameters(fn)
|
|
221
|
-
type_adapter = get_cached_typeadapter(wrapped_fn)
|
|
222
|
-
parameters = type_adapter.json_schema()
|
|
223
|
-
parameters = compress_schema(parameters, prune_titles=True)
|
|
224
|
-
|
|
225
|
-
# Convert parameters to PromptArguments
|
|
226
|
-
arguments: list[PromptArgument] = []
|
|
227
|
-
if "properties" in parameters:
|
|
228
|
-
for param_name, param in parameters["properties"].items():
|
|
229
|
-
arg_description = param.get("description")
|
|
230
|
-
|
|
231
|
-
# For non-string parameters, append JSON schema info to help users
|
|
232
|
-
# understand the expected format when passing as strings (MCP requirement)
|
|
233
|
-
if param_name in sig.parameters:
|
|
234
|
-
sig_param = sig.parameters[param_name]
|
|
235
|
-
if (
|
|
236
|
-
sig_param.annotation != inspect.Parameter.empty
|
|
237
|
-
and sig_param.annotation is not str
|
|
238
|
-
):
|
|
239
|
-
# Get the JSON schema for this specific parameter type
|
|
240
|
-
try:
|
|
241
|
-
param_adapter = get_cached_typeadapter(sig_param.annotation)
|
|
242
|
-
param_schema = param_adapter.json_schema()
|
|
243
|
-
|
|
244
|
-
# Create compact schema representation
|
|
245
|
-
schema_str = json.dumps(param_schema, separators=(",", ":"))
|
|
246
|
-
|
|
247
|
-
# Append schema info to description
|
|
248
|
-
schema_note = f"Provide as a JSON string matching the following schema: {schema_str}"
|
|
249
|
-
if arg_description:
|
|
250
|
-
arg_description = f"{arg_description}\n\n{schema_note}"
|
|
251
|
-
else:
|
|
252
|
-
arg_description = schema_note
|
|
253
|
-
except Exception:
|
|
254
|
-
# If schema generation fails, skip enhancement
|
|
255
|
-
pass
|
|
256
|
-
|
|
257
|
-
arguments.append(
|
|
258
|
-
PromptArgument(
|
|
259
|
-
name=param_name,
|
|
260
|
-
description=arg_description,
|
|
261
|
-
required=param_name in parameters.get("required", []),
|
|
287
|
+
if isinstance(raw_value, PromptResult):
|
|
288
|
+
return raw_value
|
|
289
|
+
|
|
290
|
+
if isinstance(raw_value, str):
|
|
291
|
+
return PromptResult(raw_value, description=self.description, meta=self.meta)
|
|
292
|
+
|
|
293
|
+
if isinstance(raw_value, list | tuple):
|
|
294
|
+
messages: list[Message] = []
|
|
295
|
+
for i, item in enumerate(raw_value):
|
|
296
|
+
if isinstance(item, Message):
|
|
297
|
+
messages.append(item)
|
|
298
|
+
elif isinstance(item, str):
|
|
299
|
+
messages.append(Message(item))
|
|
300
|
+
else:
|
|
301
|
+
raise TypeError(
|
|
302
|
+
f"messages[{i}] must be Message or str, got {type(item).__name__}. "
|
|
303
|
+
f"Use Message({item!r}) to wrap the value."
|
|
262
304
|
)
|
|
263
|
-
|
|
305
|
+
return PromptResult(messages, description=self.description, meta=self.meta)
|
|
264
306
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
description=description,
|
|
269
|
-
icons=icons,
|
|
270
|
-
arguments=arguments,
|
|
271
|
-
tags=tags or set(),
|
|
272
|
-
enabled=enabled if enabled is not None else True,
|
|
273
|
-
fn=wrapped_fn,
|
|
274
|
-
meta=meta,
|
|
275
|
-
task_config=task_config,
|
|
307
|
+
raise TypeError(
|
|
308
|
+
f"Prompt must return str, list[Message], or PromptResult, "
|
|
309
|
+
f"got {type(raw_value).__name__}"
|
|
276
310
|
)
|
|
277
311
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
312
|
+
@overload
|
|
313
|
+
async def _render(
|
|
314
|
+
self,
|
|
315
|
+
arguments: dict[str, Any] | None = None,
|
|
316
|
+
task_meta: None = None,
|
|
317
|
+
) -> PromptResult: ...
|
|
281
318
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
319
|
+
@overload
|
|
320
|
+
async def _render(
|
|
321
|
+
self,
|
|
322
|
+
arguments: dict[str, Any] | None,
|
|
323
|
+
task_meta: TaskMeta,
|
|
324
|
+
) -> mcp.types.CreateTaskResult: ...
|
|
285
325
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
326
|
+
async def _render(
|
|
327
|
+
self,
|
|
328
|
+
arguments: dict[str, Any] | None = None,
|
|
329
|
+
task_meta: TaskMeta | None = None,
|
|
330
|
+
) -> PromptResult | mcp.types.CreateTaskResult:
|
|
331
|
+
"""Server entry point that handles task routing.
|
|
332
|
+
|
|
333
|
+
This allows ANY Prompt subclass to support background execution by setting
|
|
334
|
+
task_config.mode to "supported" or "required". The server calls this
|
|
335
|
+
method instead of render() directly.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
arguments: Prompt arguments
|
|
339
|
+
task_meta: If provided, execute as background task and return
|
|
340
|
+
CreateTaskResult. If None (default), execute synchronously and
|
|
341
|
+
return PromptResult.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
PromptResult when task_meta is None.
|
|
345
|
+
CreateTaskResult when task_meta is provided.
|
|
346
|
+
|
|
347
|
+
Subclasses can override this to customize task routing behavior.
|
|
348
|
+
For example, FastMCPProviderPrompt overrides to delegate to child
|
|
349
|
+
middleware without submitting to Docket.
|
|
350
|
+
"""
|
|
351
|
+
from fastmcp.server.tasks.routing import check_background_task
|
|
289
352
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
try:
|
|
299
|
-
adapter = get_cached_typeadapter(param.annotation)
|
|
300
|
-
# Try JSON parsing first for complex types
|
|
301
|
-
try:
|
|
302
|
-
converted_kwargs[param_name] = adapter.validate_json(
|
|
303
|
-
param_value
|
|
304
|
-
)
|
|
305
|
-
except (ValueError, TypeError, pydantic_core.ValidationError):
|
|
306
|
-
# Fallback to direct validation
|
|
307
|
-
converted_kwargs[param_name] = adapter.validate_python(
|
|
308
|
-
param_value
|
|
309
|
-
)
|
|
310
|
-
except (ValueError, TypeError, pydantic_core.ValidationError) as e:
|
|
311
|
-
# If conversion fails, provide informative error
|
|
312
|
-
raise PromptError(
|
|
313
|
-
f"Could not convert argument '{param_name}' with value '{param_value}' "
|
|
314
|
-
f"to expected type {param.annotation}. Error: {e}"
|
|
315
|
-
) from e
|
|
316
|
-
else:
|
|
317
|
-
# Parameter not in function signature, pass as-is
|
|
318
|
-
converted_kwargs[param_name] = param_value
|
|
319
|
-
|
|
320
|
-
return converted_kwargs
|
|
353
|
+
task_result = await check_background_task(
|
|
354
|
+
component=self,
|
|
355
|
+
task_type="prompt",
|
|
356
|
+
arguments=arguments,
|
|
357
|
+
task_meta=task_meta,
|
|
358
|
+
)
|
|
359
|
+
if task_result:
|
|
360
|
+
return task_result
|
|
321
361
|
|
|
322
|
-
|
|
362
|
+
# Synchronous execution
|
|
363
|
+
result = await self.render(arguments)
|
|
364
|
+
return self.convert_result(result)
|
|
365
|
+
|
|
366
|
+
def register_with_docket(self, docket: Docket) -> None:
|
|
367
|
+
"""Register this prompt with docket for background execution."""
|
|
368
|
+
if not self.task_config.supports_tasks():
|
|
369
|
+
return
|
|
370
|
+
docket.register(self.render, names=[self.key])
|
|
371
|
+
|
|
372
|
+
async def add_to_docket( # type: ignore[override]
|
|
323
373
|
self,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
docket: Docket,
|
|
375
|
+
arguments: dict[str, Any] | None,
|
|
376
|
+
*,
|
|
377
|
+
fn_key: str | None = None,
|
|
378
|
+
task_key: str | None = None,
|
|
379
|
+
**kwargs: Any,
|
|
380
|
+
) -> Execution:
|
|
381
|
+
"""Schedule this prompt for background execution via docket.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
docket: The Docket instance
|
|
385
|
+
arguments: Prompt arguments
|
|
386
|
+
fn_key: Function lookup key in Docket registry (defaults to self.key)
|
|
387
|
+
task_key: Redis storage key for the result
|
|
388
|
+
**kwargs: Additional kwargs passed to docket.add()
|
|
389
|
+
"""
|
|
390
|
+
lookup_key = fn_key or self.key
|
|
391
|
+
if task_key:
|
|
392
|
+
kwargs["key"] = task_key
|
|
393
|
+
return await docket.add(lookup_key, **kwargs)(arguments)
|
|
394
|
+
|
|
395
|
+
def get_span_attributes(self) -> dict[str, Any]:
|
|
396
|
+
return super().get_span_attributes() | {
|
|
397
|
+
"fastmcp.component.type": "prompt",
|
|
398
|
+
"fastmcp.provider.type": "LocalProvider",
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
__all__ = [
|
|
403
|
+
"Message",
|
|
404
|
+
"Prompt",
|
|
405
|
+
"PromptArgument",
|
|
406
|
+
"PromptResult",
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def __getattr__(name: str) -> Any:
|
|
411
|
+
"""Deprecated re-exports for backwards compatibility."""
|
|
412
|
+
deprecated_exports = {
|
|
413
|
+
"FunctionPrompt": "FunctionPrompt",
|
|
414
|
+
"prompt": "prompt",
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if name in deprecated_exports:
|
|
418
|
+
import fastmcp
|
|
419
|
+
|
|
420
|
+
if fastmcp.settings.deprecation_warnings:
|
|
421
|
+
warnings.warn(
|
|
422
|
+
f"Importing {name} from fastmcp.prompts.prompt is deprecated. "
|
|
423
|
+
f"Import from fastmcp.prompts.function_prompt instead.",
|
|
424
|
+
DeprecationWarning,
|
|
425
|
+
stacklevel=2,
|
|
426
|
+
)
|
|
427
|
+
from fastmcp.prompts import function_prompt
|
|
377
428
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
raise PromptError(f"Error rendering prompt {self.name}.") from e
|
|
429
|
+
return getattr(function_prompt, name)
|
|
430
|
+
|
|
431
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|