fastmcp 2.3.5__py3-none-any.whl → 2.5.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.
- fastmcp/client/client.py +44 -6
- fastmcp/client/logging.py +14 -8
- fastmcp/client/transports.py +202 -57
- fastmcp/prompts/prompt.py +11 -4
- fastmcp/prompts/prompt_manager.py +25 -5
- fastmcp/resources/resource_manager.py +31 -5
- fastmcp/resources/template.py +10 -5
- fastmcp/server/context.py +46 -0
- fastmcp/server/http.py +25 -1
- fastmcp/server/openapi.py +436 -73
- fastmcp/server/server.py +412 -127
- fastmcp/settings.py +46 -1
- fastmcp/tools/tool.py +5 -1
- fastmcp/tools/tool_manager.py +9 -2
- fastmcp/utilities/logging.py +6 -1
- fastmcp/utilities/mcp_config.py +77 -0
- fastmcp/utilities/openapi.py +233 -602
- fastmcp/utilities/tests.py +8 -4
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/METADATA +27 -4
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/RECORD +23 -22
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/resources/template.py
CHANGED
|
@@ -14,7 +14,6 @@ from pydantic import (
|
|
|
14
14
|
BaseModel,
|
|
15
15
|
BeforeValidator,
|
|
16
16
|
Field,
|
|
17
|
-
TypeAdapter,
|
|
18
17
|
field_validator,
|
|
19
18
|
validate_call,
|
|
20
19
|
)
|
|
@@ -25,6 +24,7 @@ from fastmcp.utilities.json_schema import compress_schema
|
|
|
25
24
|
from fastmcp.utilities.types import (
|
|
26
25
|
_convert_set_defaults,
|
|
27
26
|
find_kwarg_by_type,
|
|
27
|
+
get_cached_typeadapter,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
|
|
@@ -97,7 +97,7 @@ class ResourceTemplate(BaseModel):
|
|
|
97
97
|
"""Create a template from a function."""
|
|
98
98
|
from fastmcp.server.context import Context
|
|
99
99
|
|
|
100
|
-
func_name = name or fn.__name__
|
|
100
|
+
func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
|
|
101
101
|
if func_name == "<lambda>":
|
|
102
102
|
raise ValueError("You must provide a name for lambda functions")
|
|
103
103
|
|
|
@@ -148,8 +148,13 @@ class ResourceTemplate(BaseModel):
|
|
|
148
148
|
f"URI parameters {uri_params} must be a subset of the function arguments: {func_params}"
|
|
149
149
|
)
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
description = description or fn.__doc__ or ""
|
|
152
|
+
|
|
153
|
+
if not inspect.isroutine(fn):
|
|
154
|
+
fn = fn.__call__
|
|
155
|
+
|
|
156
|
+
type_adapter = get_cached_typeadapter(fn)
|
|
157
|
+
parameters = type_adapter.json_schema()
|
|
153
158
|
|
|
154
159
|
# compress the schema
|
|
155
160
|
prune_params = [context_kwarg] if context_kwarg else None
|
|
@@ -161,7 +166,7 @@ class ResourceTemplate(BaseModel):
|
|
|
161
166
|
return cls(
|
|
162
167
|
uri_template=uri_template,
|
|
163
168
|
name=func_name,
|
|
164
|
-
description=description
|
|
169
|
+
description=description,
|
|
165
170
|
mime_type=mime_type or "text/plain",
|
|
166
171
|
fn=fn,
|
|
167
172
|
parameters=parameters,
|
fastmcp/server/context.py
CHANGED
|
@@ -12,6 +12,8 @@ from mcp.shared.context import RequestContext
|
|
|
12
12
|
from mcp.types import (
|
|
13
13
|
CreateMessageResult,
|
|
14
14
|
ImageContent,
|
|
15
|
+
ModelHint,
|
|
16
|
+
ModelPreferences,
|
|
15
17
|
Root,
|
|
16
18
|
SamplingMessage,
|
|
17
19
|
TextContent,
|
|
@@ -200,6 +202,7 @@ class Context:
|
|
|
200
202
|
system_prompt: str | None = None,
|
|
201
203
|
temperature: float | None = None,
|
|
202
204
|
max_tokens: int | None = None,
|
|
205
|
+
model_preferences: ModelPreferences | str | list[str] | None = None,
|
|
203
206
|
) -> TextContent | ImageContent:
|
|
204
207
|
"""
|
|
205
208
|
Send a sampling request to the client and await the response.
|
|
@@ -231,6 +234,7 @@ class Context:
|
|
|
231
234
|
system_prompt=system_prompt,
|
|
232
235
|
temperature=temperature,
|
|
233
236
|
max_tokens=max_tokens,
|
|
237
|
+
model_preferences=self._parse_model_preferences(model_preferences),
|
|
234
238
|
)
|
|
235
239
|
|
|
236
240
|
return result.content
|
|
@@ -248,3 +252,45 @@ class Context:
|
|
|
248
252
|
)
|
|
249
253
|
|
|
250
254
|
return fastmcp.server.dependencies.get_http_request()
|
|
255
|
+
|
|
256
|
+
def _parse_model_preferences(
|
|
257
|
+
self, model_preferences: ModelPreferences | str | list[str] | None
|
|
258
|
+
) -> ModelPreferences | None:
|
|
259
|
+
"""
|
|
260
|
+
Validates and converts user input for model_preferences into a ModelPreferences object.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
model_preferences (ModelPreferences | str | list[str] | None):
|
|
264
|
+
The model preferences to use. Accepts:
|
|
265
|
+
- ModelPreferences (returns as-is)
|
|
266
|
+
- str (single model hint)
|
|
267
|
+
- list[str] (multiple model hints)
|
|
268
|
+
- None (no preferences)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
ModelPreferences | None: The parsed ModelPreferences object, or None if not provided.
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
ValueError: If the input is not a supported type or contains invalid values.
|
|
275
|
+
"""
|
|
276
|
+
if model_preferences is None:
|
|
277
|
+
return None
|
|
278
|
+
elif isinstance(model_preferences, ModelPreferences):
|
|
279
|
+
return model_preferences
|
|
280
|
+
elif isinstance(model_preferences, str):
|
|
281
|
+
# Single model hint
|
|
282
|
+
return ModelPreferences(hints=[ModelHint(name=model_preferences)])
|
|
283
|
+
elif isinstance(model_preferences, list):
|
|
284
|
+
# List of model hints (strings)
|
|
285
|
+
if not all(isinstance(h, str) for h in model_preferences):
|
|
286
|
+
raise ValueError(
|
|
287
|
+
"All elements of model_preferences list must be"
|
|
288
|
+
" strings (model name hints)."
|
|
289
|
+
)
|
|
290
|
+
return ModelPreferences(
|
|
291
|
+
hints=[ModelHint(name=h) for h in model_preferences]
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(
|
|
295
|
+
"model_preferences must be one of: ModelPreferences, str, list[str], or None."
|
|
296
|
+
)
|
fastmcp/server/http.py
CHANGED
|
@@ -241,6 +241,7 @@ def create_sse_app(
|
|
|
241
241
|
# Add custom routes with lowest precedence
|
|
242
242
|
if routes:
|
|
243
243
|
server_routes.extend(routes)
|
|
244
|
+
server_routes.extend(server._additional_http_routes)
|
|
244
245
|
|
|
245
246
|
# Add middleware
|
|
246
247
|
if middleware:
|
|
@@ -306,7 +307,29 @@ def create_streamable_http_app(
|
|
|
306
307
|
async def handle_streamable_http(
|
|
307
308
|
scope: Scope, receive: Receive, send: Send
|
|
308
309
|
) -> None:
|
|
309
|
-
|
|
310
|
+
try:
|
|
311
|
+
await session_manager.handle_request(scope, receive, send)
|
|
312
|
+
except RuntimeError as e:
|
|
313
|
+
if str(e) == "Task group is not initialized. Make sure to use run().":
|
|
314
|
+
logger.error(
|
|
315
|
+
f"Original RuntimeError from mcp library: {e}", exc_info=True
|
|
316
|
+
)
|
|
317
|
+
new_error_message = (
|
|
318
|
+
"FastMCP's StreamableHTTPSessionManager task group was not initialized. "
|
|
319
|
+
"This commonly occurs when the FastMCP application's lifespan is not "
|
|
320
|
+
"passed to the parent ASGI application (e.g., FastAPI or Starlette). "
|
|
321
|
+
"Please ensure you are setting `lifespan=mcp_app.lifespan` in your "
|
|
322
|
+
"parent app's constructor, where `mcp_app` is the application instance "
|
|
323
|
+
"returned by `fastmcp_instance.http_app()`. \\n"
|
|
324
|
+
"For more details, see the FastMCP ASGI integration documentation: "
|
|
325
|
+
"https://gofastmcp.com/deployment/asgi"
|
|
326
|
+
)
|
|
327
|
+
# Raise a new RuntimeError that includes the original error's message
|
|
328
|
+
# for full context, but leads with the more helpful guidance.
|
|
329
|
+
raise RuntimeError(f"{new_error_message}\\nOriginal error: {e}") from e
|
|
330
|
+
else:
|
|
331
|
+
# Re-raise other RuntimeErrors if they don't match the specific message
|
|
332
|
+
raise
|
|
310
333
|
|
|
311
334
|
# Get auth middleware and routes
|
|
312
335
|
auth_middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
|
@@ -337,6 +360,7 @@ def create_streamable_http_app(
|
|
|
337
360
|
# Add custom routes with lowest precedence
|
|
338
361
|
if routes:
|
|
339
362
|
server_routes.extend(routes)
|
|
363
|
+
server_routes.extend(server._additional_http_routes)
|
|
340
364
|
|
|
341
365
|
# Add middleware
|
|
342
366
|
if middleware:
|