fastmcp 2.12.5__py3-none-any.whl → 2.14.0__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.
Files changed (133) hide show
  1. fastmcp/__init__.py +2 -23
  2. fastmcp/cli/__init__.py +0 -3
  3. fastmcp/cli/__main__.py +5 -0
  4. fastmcp/cli/cli.py +19 -33
  5. fastmcp/cli/install/claude_code.py +6 -6
  6. fastmcp/cli/install/claude_desktop.py +3 -3
  7. fastmcp/cli/install/cursor.py +18 -12
  8. fastmcp/cli/install/gemini_cli.py +3 -3
  9. fastmcp/cli/install/mcp_json.py +3 -3
  10. fastmcp/cli/install/shared.py +0 -15
  11. fastmcp/cli/run.py +13 -8
  12. fastmcp/cli/tasks.py +110 -0
  13. fastmcp/client/__init__.py +9 -9
  14. fastmcp/client/auth/oauth.py +123 -225
  15. fastmcp/client/client.py +697 -95
  16. fastmcp/client/elicitation.py +11 -5
  17. fastmcp/client/logging.py +18 -14
  18. fastmcp/client/messages.py +7 -5
  19. fastmcp/client/oauth_callback.py +85 -171
  20. fastmcp/client/roots.py +2 -1
  21. fastmcp/client/sampling.py +1 -1
  22. fastmcp/client/tasks.py +614 -0
  23. fastmcp/client/transports.py +117 -30
  24. fastmcp/contrib/component_manager/__init__.py +1 -1
  25. fastmcp/contrib/component_manager/component_manager.py +2 -2
  26. fastmcp/contrib/component_manager/component_service.py +10 -26
  27. fastmcp/contrib/mcp_mixin/README.md +32 -1
  28. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  29. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  30. fastmcp/dependencies.py +25 -0
  31. fastmcp/experimental/sampling/handlers/openai.py +3 -3
  32. fastmcp/experimental/server/openapi/__init__.py +20 -21
  33. fastmcp/experimental/utilities/openapi/__init__.py +16 -47
  34. fastmcp/mcp_config.py +3 -4
  35. fastmcp/prompts/__init__.py +1 -1
  36. fastmcp/prompts/prompt.py +54 -51
  37. fastmcp/prompts/prompt_manager.py +16 -101
  38. fastmcp/resources/__init__.py +5 -5
  39. fastmcp/resources/resource.py +43 -21
  40. fastmcp/resources/resource_manager.py +9 -168
  41. fastmcp/resources/template.py +161 -61
  42. fastmcp/resources/types.py +30 -24
  43. fastmcp/server/__init__.py +1 -1
  44. fastmcp/server/auth/__init__.py +9 -14
  45. fastmcp/server/auth/auth.py +197 -46
  46. fastmcp/server/auth/handlers/authorize.py +326 -0
  47. fastmcp/server/auth/jwt_issuer.py +236 -0
  48. fastmcp/server/auth/middleware.py +96 -0
  49. fastmcp/server/auth/oauth_proxy.py +1469 -298
  50. fastmcp/server/auth/oidc_proxy.py +91 -20
  51. fastmcp/server/auth/providers/auth0.py +40 -21
  52. fastmcp/server/auth/providers/aws.py +29 -3
  53. fastmcp/server/auth/providers/azure.py +312 -131
  54. fastmcp/server/auth/providers/debug.py +114 -0
  55. fastmcp/server/auth/providers/descope.py +86 -29
  56. fastmcp/server/auth/providers/discord.py +308 -0
  57. fastmcp/server/auth/providers/github.py +29 -8
  58. fastmcp/server/auth/providers/google.py +48 -9
  59. fastmcp/server/auth/providers/in_memory.py +29 -5
  60. fastmcp/server/auth/providers/introspection.py +281 -0
  61. fastmcp/server/auth/providers/jwt.py +48 -31
  62. fastmcp/server/auth/providers/oci.py +233 -0
  63. fastmcp/server/auth/providers/scalekit.py +238 -0
  64. fastmcp/server/auth/providers/supabase.py +188 -0
  65. fastmcp/server/auth/providers/workos.py +35 -17
  66. fastmcp/server/context.py +236 -116
  67. fastmcp/server/dependencies.py +503 -18
  68. fastmcp/server/elicitation.py +286 -48
  69. fastmcp/server/event_store.py +177 -0
  70. fastmcp/server/http.py +71 -20
  71. fastmcp/server/low_level.py +165 -2
  72. fastmcp/server/middleware/__init__.py +1 -1
  73. fastmcp/server/middleware/caching.py +476 -0
  74. fastmcp/server/middleware/error_handling.py +14 -10
  75. fastmcp/server/middleware/logging.py +50 -39
  76. fastmcp/server/middleware/middleware.py +29 -16
  77. fastmcp/server/middleware/rate_limiting.py +3 -3
  78. fastmcp/server/middleware/tool_injection.py +116 -0
  79. fastmcp/server/openapi/__init__.py +35 -0
  80. fastmcp/{experimental/server → server}/openapi/components.py +15 -10
  81. fastmcp/{experimental/server → server}/openapi/routing.py +3 -3
  82. fastmcp/{experimental/server → server}/openapi/server.py +6 -5
  83. fastmcp/server/proxy.py +72 -48
  84. fastmcp/server/server.py +1415 -733
  85. fastmcp/server/tasks/__init__.py +21 -0
  86. fastmcp/server/tasks/capabilities.py +22 -0
  87. fastmcp/server/tasks/config.py +89 -0
  88. fastmcp/server/tasks/converters.py +205 -0
  89. fastmcp/server/tasks/handlers.py +356 -0
  90. fastmcp/server/tasks/keys.py +93 -0
  91. fastmcp/server/tasks/protocol.py +355 -0
  92. fastmcp/server/tasks/subscriptions.py +205 -0
  93. fastmcp/settings.py +125 -113
  94. fastmcp/tools/__init__.py +1 -1
  95. fastmcp/tools/tool.py +138 -55
  96. fastmcp/tools/tool_manager.py +30 -112
  97. fastmcp/tools/tool_transform.py +12 -21
  98. fastmcp/utilities/cli.py +67 -28
  99. fastmcp/utilities/components.py +10 -5
  100. fastmcp/utilities/inspect.py +79 -23
  101. fastmcp/utilities/json_schema.py +4 -4
  102. fastmcp/utilities/json_schema_type.py +8 -8
  103. fastmcp/utilities/logging.py +118 -8
  104. fastmcp/utilities/mcp_config.py +1 -2
  105. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  106. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  107. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  108. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +5 -5
  109. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  110. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  111. fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
  112. fastmcp/utilities/openapi/__init__.py +63 -0
  113. fastmcp/{experimental/utilities → utilities}/openapi/director.py +14 -15
  114. fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
  115. fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +7 -3
  116. fastmcp/{experimental/utilities → utilities}/openapi/parser.py +37 -16
  117. fastmcp/utilities/tests.py +92 -5
  118. fastmcp/utilities/types.py +86 -16
  119. fastmcp/utilities/ui.py +626 -0
  120. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/METADATA +24 -15
  121. fastmcp-2.14.0.dist-info/RECORD +156 -0
  122. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +1 -1
  123. fastmcp/cli/claude.py +0 -135
  124. fastmcp/server/auth/providers/bearer.py +0 -25
  125. fastmcp/server/openapi.py +0 -1083
  126. fastmcp/utilities/openapi.py +0 -1568
  127. fastmcp/utilities/storage.py +0 -204
  128. fastmcp-2.12.5.dist-info/RECORD +0 -134
  129. fastmcp/{experimental/server → server}/openapi/README.md +0 -0
  130. fastmcp/{experimental/utilities → utilities}/openapi/models.py +3 -3
  131. fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +2 -2
  132. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
  133. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -21,10 +21,10 @@ try:
21
21
  ChatCompletionUserMessageParam,
22
22
  )
23
23
  from openai.types.shared.chat_model import ChatModel
24
- except ImportError:
24
+ except ImportError as e:
25
25
  raise ImportError(
26
26
  "The `openai` package is not installed. Please install `fastmcp[openai]` or add `openai` to your dependencies manually."
27
- )
27
+ ) from e
28
28
 
29
29
  from typing_extensions import override
30
30
 
@@ -164,7 +164,7 @@ class OpenAISamplingHandler(BaseLLMSamplingHandler):
164
164
  ) -> ChatModel:
165
165
  for model_option in self._iter_models_from_preferences(model_preferences):
166
166
  if model_option in get_args(ChatModel):
167
- chosen_model: ChatModel = model_option # pyright: ignore[reportAssignmentType]
167
+ chosen_model: ChatModel = model_option # type: ignore[assignment]
168
168
  return chosen_model
169
169
 
170
170
  return self.default_model
@@ -1,38 +1,37 @@
1
- """OpenAPI server implementation for FastMCP - refactored for better maintainability."""
1
+ """Deprecated: Import from fastmcp.server.openapi instead."""
2
2
 
3
- # Import from server
4
- from .server import FastMCPOpenAPI
3
+ import warnings
5
4
 
6
- # Import from routing
7
- from .routing import (
5
+ from fastmcp.server.openapi import (
6
+ ComponentFn,
7
+ DEFAULT_ROUTE_MAPPINGS,
8
+ FastMCPOpenAPI,
8
9
  MCPType,
10
+ OpenAPIResource,
11
+ OpenAPIResourceTemplate,
12
+ OpenAPITool,
9
13
  RouteMap,
10
14
  RouteMapFn,
11
- ComponentFn,
12
- DEFAULT_ROUTE_MAPPINGS,
13
15
  _determine_route_type,
14
16
  )
15
17
 
16
- # Import from components
17
- from .components import (
18
- OpenAPITool,
19
- OpenAPIResource,
20
- OpenAPIResourceTemplate,
18
+ # Deprecated in 2.14 when OpenAPI support was promoted out of experimental
19
+ warnings.warn(
20
+ "Importing from fastmcp.experimental.server.openapi is deprecated. "
21
+ "Import from fastmcp.server.openapi instead.",
22
+ DeprecationWarning,
23
+ stacklevel=2,
21
24
  )
22
25
 
23
- # Export public symbols - maintaining backward compatibility
24
26
  __all__ = [
25
- # Server
27
+ "DEFAULT_ROUTE_MAPPINGS",
28
+ "ComponentFn",
26
29
  "FastMCPOpenAPI",
27
- # Routing
28
30
  "MCPType",
31
+ "OpenAPIResource",
32
+ "OpenAPIResourceTemplate",
33
+ "OpenAPITool",
29
34
  "RouteMap",
30
35
  "RouteMapFn",
31
- "ComponentFn",
32
- "DEFAULT_ROUTE_MAPPINGS",
33
36
  "_determine_route_type",
34
- # Components
35
- "OpenAPITool",
36
- "OpenAPIResource",
37
- "OpenAPIResourceTemplate",
38
37
  ]
@@ -1,68 +1,37 @@
1
- """OpenAPI utilities for FastMCP - refactored for better maintainability."""
1
+ """Deprecated: Import from fastmcp.utilities.openapi instead."""
2
2
 
3
- # Import from models
4
- from .models import (
3
+ import warnings
4
+
5
+ from fastmcp.utilities.openapi import (
5
6
  HTTPRoute,
6
7
  HttpMethod,
7
- JsonSchema,
8
8
  ParameterInfo,
9
9
  ParameterLocation,
10
10
  RequestBodyInfo,
11
11
  ResponseInfo,
12
- )
13
-
14
- # Import from parser
15
- from .parser import parse_openapi_to_http_routes
16
-
17
- # Import from formatters
18
- from .formatters import (
19
- format_array_parameter,
20
- format_deep_object_parameter,
21
- format_description_with_responses,
22
- format_json_for_description,
12
+ extract_output_schema_from_responses,
23
13
  format_simple_description,
24
- generate_example_from_schema,
25
- )
26
-
27
- # Import from schemas
28
- from .schemas import (
14
+ parse_openapi_to_http_routes,
29
15
  _combine_schemas,
30
- extract_output_schema_from_responses,
31
- clean_schema_for_display,
32
- _make_optional_parameter_nullable,
33
16
  )
34
17
 
35
- # Import from json_schema_converter
36
- from .json_schema_converter import (
37
- convert_openapi_schema_to_json_schema,
38
- convert_schema_definitions,
18
+ # Deprecated in 2.14 when OpenAPI support was promoted out of experimental
19
+ warnings.warn(
20
+ "Importing from fastmcp.experimental.utilities.openapi is deprecated. "
21
+ "Import from fastmcp.utilities.openapi instead.",
22
+ DeprecationWarning,
23
+ stacklevel=2,
39
24
  )
40
25
 
41
- # Export public symbols - maintaining backward compatibility
42
26
  __all__ = [
43
- # Models
44
27
  "HTTPRoute",
28
+ "HttpMethod",
45
29
  "ParameterInfo",
30
+ "ParameterLocation",
46
31
  "RequestBodyInfo",
47
32
  "ResponseInfo",
48
- "HttpMethod",
49
- "ParameterLocation",
50
- "JsonSchema",
51
- # Parser
52
- "parse_openapi_to_http_routes",
53
- # Formatters
54
- "format_array_parameter",
55
- "format_deep_object_parameter",
56
- "format_description_with_responses",
57
- "format_json_for_description",
58
- "format_simple_description",
59
- "generate_example_from_schema",
60
- # Schemas
61
33
  "_combine_schemas",
62
34
  "extract_output_schema_from_responses",
63
- "clean_schema_for_display",
64
- "_make_optional_parameter_nullable",
65
- # JSON Schema Converter
66
- "convert_openapi_schema_to_json_schema",
67
- "convert_schema_definitions",
35
+ "format_simple_description",
36
+ "parse_openapi_to_http_routes",
68
37
  ]
fastmcp/mcp_config.py CHANGED
@@ -101,7 +101,7 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
101
101
  ClientTransport, # pyright: ignore[reportUnusedImport]
102
102
  )
103
103
 
104
- transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
104
+ transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] # ty: ignore[unresolved-attribute]
105
105
  transport = cast(ClientTransport, transport)
106
106
 
107
107
  client: Client[ClientTransport] = Client(transport=transport, name=client_name)
@@ -288,9 +288,8 @@ class MCPConfig(BaseModel):
288
288
  @classmethod
289
289
  def from_file(cls, file_path: Path) -> Self:
290
290
  """Load configuration from JSON file."""
291
- if file_path.exists():
292
- if content := file_path.read_text().strip():
293
- return cls.model_validate_json(content)
291
+ if file_path.exists() and (content := file_path.read_text().strip()):
292
+ return cls.model_validate_json(content)
294
293
 
295
294
  raise ValueError(f"No MCP servers defined in the config: {file_path}")
296
295
 
@@ -2,8 +2,8 @@ from .prompt import Prompt, PromptMessage, Message
2
2
  from .prompt_manager import PromptManager
3
3
 
4
4
  __all__ = [
5
+ "Message",
5
6
  "Prompt",
6
7
  "PromptManager",
7
8
  "PromptMessage",
8
- "Message",
9
9
  ]
fastmcp/prompts/prompt.py CHANGED
@@ -4,24 +4,23 @@ from __future__ import annotations as _annotations
4
4
 
5
5
  import inspect
6
6
  import json
7
- from abc import ABC, abstractmethod
8
7
  from collections.abc import Awaitable, Callable, Sequence
9
- from typing import Any
8
+ from typing import Annotated, Any
10
9
 
11
10
  import pydantic_core
12
- from mcp.types import ContentBlock, PromptMessage, Role, TextContent
11
+ from mcp.types import ContentBlock, Icon, PromptMessage, Role, TextContent
13
12
  from mcp.types import Prompt as MCPPrompt
14
13
  from mcp.types import PromptArgument as MCPPromptArgument
15
14
  from pydantic import Field, TypeAdapter
16
15
 
17
16
  from fastmcp.exceptions import PromptError
18
- from fastmcp.server.dependencies import get_context
17
+ from fastmcp.server.dependencies import get_context, without_injected_parameters
18
+ from fastmcp.server.tasks.config import TaskConfig
19
19
  from fastmcp.utilities.components import FastMCPComponent
20
20
  from fastmcp.utilities.json_schema import compress_schema
21
21
  from fastmcp.utilities.logging import get_logger
22
22
  from fastmcp.utilities.types import (
23
23
  FastMCPBaseModel,
24
- find_kwarg_by_type,
25
24
  get_cached_typeadapter,
26
25
  )
27
26
 
@@ -62,7 +61,7 @@ class PromptArgument(FastMCPBaseModel):
62
61
  )
63
62
 
64
63
 
65
- class Prompt(FastMCPComponent, ABC):
64
+ class Prompt(FastMCPComponent):
66
65
  """A prompt template that can be rendered with parameters."""
67
66
 
68
67
  arguments: list[PromptArgument] | None = Field(
@@ -106,6 +105,7 @@ class Prompt(FastMCPComponent, ABC):
106
105
  description=overrides.get("description", self.description),
107
106
  arguments=arguments,
108
107
  title=overrides.get("title", self.title),
108
+ icons=overrides.get("icons", self.icons),
109
109
  _meta=overrides.get(
110
110
  "_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
111
111
  ),
@@ -117,9 +117,11 @@ class Prompt(FastMCPComponent, ABC):
117
117
  name: str | None = None,
118
118
  title: str | None = None,
119
119
  description: str | None = None,
120
+ icons: list[Icon] | None = None,
120
121
  tags: set[str] | None = None,
121
122
  enabled: bool | None = None,
122
123
  meta: dict[str, Any] | None = None,
124
+ task: bool | TaskConfig | None = None,
123
125
  ) -> FunctionPrompt:
124
126
  """Create a Prompt from a function.
125
127
 
@@ -134,24 +136,33 @@ class Prompt(FastMCPComponent, ABC):
134
136
  name=name,
135
137
  title=title,
136
138
  description=description,
139
+ icons=icons,
137
140
  tags=tags,
138
141
  enabled=enabled,
139
142
  meta=meta,
143
+ task=task,
140
144
  )
141
145
 
142
- @abstractmethod
143
146
  async def render(
144
147
  self,
145
148
  arguments: dict[str, Any] | None = None,
146
149
  ) -> list[PromptMessage]:
147
- """Render the prompt with arguments."""
148
- raise NotImplementedError("Prompt.render() must be implemented by subclasses")
150
+ """Render the prompt with arguments.
151
+
152
+ This method is not implemented in the base Prompt class and must be
153
+ implemented by subclasses.
154
+ """
155
+ raise NotImplementedError("Subclasses must implement render()")
149
156
 
150
157
 
151
158
  class FunctionPrompt(Prompt):
152
159
  """A prompt that is a function."""
153
160
 
154
161
  fn: Callable[..., PromptResult | Awaitable[PromptResult]]
162
+ task_config: Annotated[
163
+ TaskConfig,
164
+ Field(description="Background task execution configuration (SEP-1686)."),
165
+ ] = Field(default_factory=lambda: TaskConfig(mode="forbidden"))
155
166
 
156
167
  @classmethod
157
168
  def from_function(
@@ -160,9 +171,11 @@ class FunctionPrompt(Prompt):
160
171
  name: str | None = None,
161
172
  title: str | None = None,
162
173
  description: str | None = None,
174
+ icons: list[Icon] | None = None,
163
175
  tags: set[str] | None = None,
164
176
  enabled: bool | None = None,
165
177
  meta: dict[str, Any] | None = None,
178
+ task: bool | TaskConfig | None = None,
166
179
  ) -> FunctionPrompt:
167
180
  """Create a Prompt from a function.
168
181
 
@@ -172,7 +185,6 @@ class FunctionPrompt(Prompt):
172
185
  - A dict (converted to a message)
173
186
  - A sequence of any of the above
174
187
  """
175
- from fastmcp.server.context import Context
176
188
 
177
189
  func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
178
190
 
@@ -188,25 +200,27 @@ class FunctionPrompt(Prompt):
188
200
 
189
201
  description = description or inspect.getdoc(fn)
190
202
 
203
+ # Normalize task to TaskConfig and validate
204
+ if task is None:
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
+
191
212
  # if the fn is a callable class, we need to get the __call__ method from here out
192
213
  if not inspect.isroutine(fn):
193
214
  fn = fn.__call__
194
215
  # if the fn is a staticmethod, we need to work with the underlying function
195
216
  if isinstance(fn, staticmethod):
196
- fn = fn.__func__
217
+ fn = fn.__func__ # type: ignore[assignment]
197
218
 
198
- type_adapter = get_cached_typeadapter(fn)
219
+ # Wrap fn to handle dependency resolution internally
220
+ wrapped_fn = without_injected_parameters(fn)
221
+ type_adapter = get_cached_typeadapter(wrapped_fn)
199
222
  parameters = type_adapter.json_schema()
200
-
201
- # Auto-detect context parameter if not provided
202
-
203
- context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
204
- if context_kwarg:
205
- prune_params = [context_kwarg]
206
- else:
207
- prune_params = None
208
-
209
- parameters = compress_schema(parameters, prune_params=prune_params)
223
+ parameters = compress_schema(parameters, prune_titles=True)
210
224
 
211
225
  # Convert parameters to PromptArguments
212
226
  arguments: list[PromptArgument] = []
@@ -221,7 +235,6 @@ class FunctionPrompt(Prompt):
221
235
  if (
222
236
  sig_param.annotation != inspect.Parameter.empty
223
237
  and sig_param.annotation is not str
224
- and param_name != context_kwarg
225
238
  ):
226
239
  # Get the JSON schema for this specific parameter type
227
240
  try:
@@ -253,40 +266,32 @@ class FunctionPrompt(Prompt):
253
266
  name=func_name,
254
267
  title=title,
255
268
  description=description,
269
+ icons=icons,
256
270
  arguments=arguments,
257
271
  tags=tags or set(),
258
272
  enabled=enabled if enabled is not None else True,
259
- fn=fn,
273
+ fn=wrapped_fn,
260
274
  meta=meta,
275
+ task_config=task_config,
261
276
  )
262
277
 
263
278
  def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
264
279
  """Convert string arguments to expected types based on function signature."""
265
- from fastmcp.server.context import Context
280
+ from fastmcp.server.dependencies import without_injected_parameters
266
281
 
267
- sig = inspect.signature(self.fn)
282
+ wrapper_fn = without_injected_parameters(self.fn)
283
+ sig = inspect.signature(wrapper_fn)
268
284
  converted_kwargs = {}
269
285
 
270
- # Find context parameter name if any
271
- context_param_name = find_kwarg_by_type(self.fn, kwarg_type=Context)
272
-
273
286
  for param_name, param_value in kwargs.items():
274
287
  if param_name in sig.parameters:
275
288
  param = sig.parameters[param_name]
276
289
 
277
- # Skip Context parameters - they're handled separately
278
- if param_name == context_param_name:
279
- converted_kwargs[param_name] = param_value
280
- continue
281
-
282
290
  # If parameter has no annotation or annotation is str, pass as-is
283
291
  if (
284
292
  param.annotation == inspect.Parameter.empty
285
293
  or param.annotation is str
286
- ):
287
- converted_kwargs[param_name] = param_value
288
- # If argument is not a string, pass as-is (already properly typed)
289
- elif not isinstance(param_value, str):
294
+ ) or not isinstance(param_value, str):
290
295
  converted_kwargs[param_name] = param_value
291
296
  else:
292
297
  # Try to convert string argument using type adapter
@@ -307,7 +312,7 @@ class FunctionPrompt(Prompt):
307
312
  raise PromptError(
308
313
  f"Could not convert argument '{param_name}' with value '{param_value}' "
309
314
  f"to expected type {param.annotation}. Error: {e}"
310
- )
315
+ ) from e
311
316
  else:
312
317
  # Parameter not in function signature, pass as-is
313
318
  converted_kwargs[param_name] = param_value
@@ -319,8 +324,6 @@ class FunctionPrompt(Prompt):
319
324
  arguments: dict[str, Any] | None = None,
320
325
  ) -> list[PromptMessage]:
321
326
  """Render the prompt with arguments."""
322
- from fastmcp.server.context import Context
323
-
324
327
  # Validate required arguments
325
328
  if self.arguments:
326
329
  required = {arg.name for arg in self.arguments if arg.required}
@@ -330,16 +333,14 @@ class FunctionPrompt(Prompt):
330
333
  raise ValueError(f"Missing required arguments: {missing}")
331
334
 
332
335
  try:
333
- # Prepare arguments with context
336
+ # Prepare arguments
334
337
  kwargs = arguments.copy() if arguments else {}
335
- context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
336
- if context_kwarg and context_kwarg not in kwargs:
337
- kwargs[context_kwarg] = get_context()
338
338
 
339
- # Convert string arguments to expected types when needed
339
+ # Convert string arguments to expected types BEFORE validation
340
340
  kwargs = self._convert_string_arguments(kwargs)
341
341
 
342
- # Call function and check if result is a coroutine
342
+ # self.fn is wrapped by without_injected_parameters which handles
343
+ # dependency resolution internally
343
344
  result = self.fn(**kwargs)
344
345
  if inspect.isawaitable(result):
345
346
  result = await result
@@ -369,10 +370,12 @@ class FunctionPrompt(Prompt):
369
370
  content=TextContent(type="text", text=content),
370
371
  )
371
372
  )
372
- except Exception:
373
- raise PromptError("Could not convert prompt result to message.")
373
+ except Exception as e:
374
+ raise PromptError(
375
+ "Could not convert prompt result to message."
376
+ ) from e
374
377
 
375
378
  return messages
376
- except Exception:
379
+ except Exception as e:
377
380
  logger.exception(f"Error rendering prompt {self.name}")
378
- raise PromptError(f"Error rendering prompt {self.name}.")
381
+ raise PromptError(f"Error rendering prompt {self.name}.") from e
@@ -2,7 +2,7 @@ from __future__ import annotations as _annotations
2
2
 
3
3
  import warnings
4
4
  from collections.abc import Awaitable, Callable
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import Any
6
6
 
7
7
  from mcp import GetPromptResult
8
8
 
@@ -12,9 +12,6 @@ from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
12
12
  from fastmcp.settings import DuplicateBehavior
13
13
  from fastmcp.utilities.logging import get_logger
14
14
 
15
- if TYPE_CHECKING:
16
- from fastmcp.server.server import MountedServer
17
-
18
15
  logger = get_logger(__name__)
19
16
 
20
17
 
@@ -27,7 +24,6 @@ class PromptManager:
27
24
  mask_error_details: bool | None = None,
28
25
  ):
29
26
  self._prompts: dict[str, Prompt] = {}
30
- self._mounted_servers: list[MountedServer] = []
31
27
  self.mask_error_details = mask_error_details or settings.mask_error_details
32
28
 
33
29
  # Default to "warn" if None is provided
@@ -42,52 +38,6 @@ class PromptManager:
42
38
 
43
39
  self.duplicate_behavior = duplicate_behavior
44
40
 
45
- def mount(self, server: MountedServer) -> None:
46
- """Adds a mounted server as a source for prompts."""
47
- self._mounted_servers.append(server)
48
-
49
- async def _load_prompts(self, *, via_server: bool = False) -> dict[str, Prompt]:
50
- """
51
- The single, consolidated recursive method for fetching prompts. The 'via_server'
52
- parameter determines the communication path.
53
-
54
- - via_server=False: Manager-to-manager path for complete, unfiltered inventory
55
- - via_server=True: Server-to-server path for filtered MCP requests
56
- """
57
- all_prompts: dict[str, Prompt] = {}
58
-
59
- for mounted in self._mounted_servers:
60
- try:
61
- if via_server:
62
- # Use the server-to-server filtered path
63
- child_results = await mounted.server._list_prompts()
64
- else:
65
- # Use the manager-to-manager unfiltered path
66
- child_results = await mounted.server._prompt_manager.list_prompts()
67
-
68
- # The combination logic is the same for both paths
69
- child_dict = {p.key: p for p in child_results}
70
- if mounted.prefix:
71
- for prompt in child_dict.values():
72
- prefixed_prompt = prompt.model_copy(
73
- key=f"{mounted.prefix}_{prompt.key}"
74
- )
75
- all_prompts[prefixed_prompt.key] = prefixed_prompt
76
- else:
77
- all_prompts.update(child_dict)
78
- except Exception as e:
79
- # Skip failed mounts silently, matches existing behavior
80
- logger.warning(
81
- f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
82
- )
83
- if settings.mounted_components_raise_on_load_error:
84
- raise
85
- continue
86
-
87
- # Finally, add local prompts, which always take precedence
88
- all_prompts.update(self._prompts)
89
- return all_prompts
90
-
91
41
  async def has_prompt(self, key: str) -> bool:
92
42
  """Check if a prompt exists."""
93
43
  prompts = await self.get_prompts()
@@ -102,16 +52,9 @@ class PromptManager:
102
52
 
103
53
  async def get_prompts(self) -> dict[str, Prompt]:
104
54
  """
105
- Gets the complete, unfiltered inventory of all prompts.
106
- """
107
- return await self._load_prompts(via_server=False)
108
-
109
- async def list_prompts(self) -> list[Prompt]:
110
- """
111
- Lists all prompts, applying protocol filtering.
55
+ Gets the complete, unfiltered inventory of local prompts.
112
56
  """
113
- prompts_dict = await self._load_prompts(via_server=True)
114
- return list(prompts_dict.values())
57
+ return dict(self._prompts)
115
58
 
116
59
  def add_prompt_from_fn(
117
60
  self,
@@ -160,44 +103,16 @@ class PromptManager:
160
103
  Internal API for servers: Finds and renders a prompt, respecting the
161
104
  filtered protocol path.
162
105
  """
163
- # 1. Check local prompts first. The server will have already applied its filter.
164
- if name in self._prompts:
165
- prompt = await self.get_prompt(name)
166
- if not prompt:
167
- raise NotFoundError(f"Unknown prompt: {name}")
168
-
169
- try:
170
- messages = await prompt.render(arguments)
171
- return GetPromptResult(
172
- description=prompt.description, messages=messages
173
- )
174
-
175
- # Pass through PromptErrors as-is
176
- except PromptError as e:
177
- logger.exception(f"Error rendering prompt {name!r}")
178
- raise e
179
-
180
- # Handle other exceptions
181
- except Exception as e:
182
- logger.exception(f"Error rendering prompt {name!r}")
183
- if self.mask_error_details:
184
- # Mask internal details
185
- raise PromptError(f"Error rendering prompt {name!r}") from e
186
- else:
187
- # Include original error details
188
- raise PromptError(f"Error rendering prompt {name!r}: {e}") from e
189
-
190
- # 2. Check mounted servers using the filtered protocol path.
191
- for mounted in reversed(self._mounted_servers):
192
- prompt_key = name
193
- if mounted.prefix:
194
- if name.startswith(f"{mounted.prefix}_"):
195
- prompt_key = name.removeprefix(f"{mounted.prefix}_")
196
- else:
197
- continue
198
- try:
199
- return await mounted.server._get_prompt(prompt_key, arguments)
200
- except NotFoundError:
201
- continue
202
-
203
- raise NotFoundError(f"Unknown prompt: {name}")
106
+ prompt = await self.get_prompt(name)
107
+ try:
108
+ messages = await prompt.render(arguments)
109
+ return GetPromptResult(description=prompt.description, messages=messages)
110
+ except PromptError as e:
111
+ logger.exception(f"Error rendering prompt {name!r}")
112
+ raise e
113
+ except Exception as e:
114
+ logger.exception(f"Error rendering prompt {name!r}")
115
+ if self.mask_error_details:
116
+ raise PromptError(f"Error rendering prompt {name!r}") from e
117
+ else:
118
+ raise PromptError(f"Error rendering prompt {name!r}: {e}") from e
@@ -10,13 +10,13 @@ from .types import (
10
10
  from .resource_manager import ResourceManager
11
11
 
12
12
  __all__ = [
13
- "Resource",
14
- "TextResource",
15
13
  "BinaryResource",
16
- "FunctionResource",
14
+ "DirectoryResource",
17
15
  "FileResource",
16
+ "FunctionResource",
18
17
  "HttpResource",
19
- "DirectoryResource",
20
- "ResourceTemplate",
18
+ "Resource",
21
19
  "ResourceManager",
20
+ "ResourceTemplate",
21
+ "TextResource",
22
22
  ]