fastmcp 2.13.0rc3__py3-none-any.whl → 2.13.0.1__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 (65) hide show
  1. fastmcp/__init__.py +2 -2
  2. fastmcp/cli/cli.py +2 -2
  3. fastmcp/client/__init__.py +9 -9
  4. fastmcp/client/auth/oauth.py +7 -6
  5. fastmcp/client/client.py +10 -10
  6. fastmcp/client/sampling.py +1 -1
  7. fastmcp/client/transports.py +34 -34
  8. fastmcp/contrib/component_manager/__init__.py +1 -1
  9. fastmcp/contrib/component_manager/component_manager.py +2 -2
  10. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  11. fastmcp/experimental/sampling/handlers/openai.py +2 -2
  12. fastmcp/experimental/server/openapi/__init__.py +5 -8
  13. fastmcp/experimental/server/openapi/components.py +11 -7
  14. fastmcp/experimental/server/openapi/routing.py +2 -2
  15. fastmcp/experimental/utilities/openapi/__init__.py +10 -15
  16. fastmcp/experimental/utilities/openapi/director.py +1 -1
  17. fastmcp/experimental/utilities/openapi/json_schema_converter.py +2 -2
  18. fastmcp/experimental/utilities/openapi/models.py +3 -3
  19. fastmcp/experimental/utilities/openapi/parser.py +3 -5
  20. fastmcp/experimental/utilities/openapi/schemas.py +2 -2
  21. fastmcp/mcp_config.py +2 -3
  22. fastmcp/prompts/__init__.py +1 -1
  23. fastmcp/prompts/prompt.py +9 -13
  24. fastmcp/resources/__init__.py +5 -5
  25. fastmcp/resources/resource.py +1 -3
  26. fastmcp/resources/resource_manager.py +1 -1
  27. fastmcp/server/__init__.py +1 -1
  28. fastmcp/server/auth/__init__.py +5 -5
  29. fastmcp/server/auth/auth.py +2 -2
  30. fastmcp/server/auth/oidc_proxy.py +2 -2
  31. fastmcp/server/auth/providers/azure.py +48 -25
  32. fastmcp/server/auth/providers/bearer.py +1 -1
  33. fastmcp/server/auth/providers/in_memory.py +2 -2
  34. fastmcp/server/auth/providers/introspection.py +2 -2
  35. fastmcp/server/auth/providers/jwt.py +17 -18
  36. fastmcp/server/auth/providers/supabase.py +1 -1
  37. fastmcp/server/auth/providers/workos.py +2 -2
  38. fastmcp/server/context.py +8 -10
  39. fastmcp/server/dependencies.py +5 -6
  40. fastmcp/server/elicitation.py +1 -1
  41. fastmcp/server/http.py +2 -3
  42. fastmcp/server/middleware/__init__.py +1 -1
  43. fastmcp/server/middleware/caching.py +1 -1
  44. fastmcp/server/middleware/error_handling.py +8 -8
  45. fastmcp/server/middleware/middleware.py +1 -1
  46. fastmcp/server/openapi.py +10 -6
  47. fastmcp/server/proxy.py +5 -4
  48. fastmcp/server/server.py +27 -29
  49. fastmcp/tools/__init__.py +1 -1
  50. fastmcp/tools/tool.py +12 -12
  51. fastmcp/tools/tool_transform.py +6 -6
  52. fastmcp/utilities/cli.py +5 -6
  53. fastmcp/utilities/inspect.py +2 -2
  54. fastmcp/utilities/json_schema_type.py +4 -4
  55. fastmcp/utilities/logging.py +14 -18
  56. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  57. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  58. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  59. fastmcp/utilities/openapi.py +9 -9
  60. fastmcp/utilities/tests.py +2 -4
  61. {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.1.dist-info}/METADATA +1 -1
  62. {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.1.dist-info}/RECORD +65 -65
  63. {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.1.dist-info}/WHEEL +0 -0
  64. {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.1.dist-info}/entry_points.txt +0 -0
  65. {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py CHANGED
@@ -48,9 +48,9 @@ def __getattr__(name: str):
48
48
 
49
49
 
50
50
  __all__ = [
51
- "FastMCP",
51
+ "Client",
52
52
  "Context",
53
+ "FastMCP",
53
54
  "client",
54
- "Client",
55
55
  "settings",
56
56
  ]
fastmcp/cli/cli.py CHANGED
@@ -78,7 +78,7 @@ def with_argv(args: list[str] | None):
78
78
  original = sys.argv[:]
79
79
  try:
80
80
  # Preserve the script name (sys.argv[0]) and replace the rest
81
- sys.argv = [sys.argv[0]] + args
81
+ sys.argv = [sys.argv[0], *args]
82
82
  yield
83
83
  finally:
84
84
  sys.argv = original
@@ -277,7 +277,7 @@ async def dev(
277
277
 
278
278
  # Run the MCP Inspector command
279
279
  process = subprocess.run(
280
- [npx_cmd, inspector_cmd] + uv_cmd,
280
+ [npx_cmd, inspector_cmd, *uv_cmd],
281
281
  check=True,
282
282
  env=env,
283
283
  )
@@ -15,18 +15,18 @@ from .transports import (
15
15
  from .auth import OAuth, BearerAuth
16
16
 
17
17
  __all__ = [
18
+ "BearerAuth",
18
19
  "Client",
19
20
  "ClientTransport",
20
- "WSTransport",
21
- "SSETransport",
22
- "StdioTransport",
23
- "PythonStdioTransport",
21
+ "FastMCPTransport",
24
22
  "NodeStdioTransport",
25
- "UvxStdioTransport",
26
- "UvStdioTransport",
27
23
  "NpxStdioTransport",
28
- "FastMCPTransport",
29
- "StreamableHttpTransport",
30
24
  "OAuth",
31
- "BearerAuth",
25
+ "PythonStdioTransport",
26
+ "SSETransport",
27
+ "StdioTransport",
28
+ "StreamableHttpTransport",
29
+ "UvStdioTransport",
30
+ "UvxStdioTransport",
31
+ "WSTransport",
32
32
  ]
@@ -36,8 +36,6 @@ logger = get_logger(__name__)
36
36
  class ClientNotFoundError(Exception):
37
37
  """Raised when OAuth client credentials are not found on the server."""
38
38
 
39
- pass
40
-
41
39
 
42
40
  async def check_if_auth_required(
43
41
  mcp_url: str, httpx_kwargs: dict[str, Any] | None = None
@@ -58,7 +56,7 @@ async def check_if_auth_required(
58
56
  return True
59
57
 
60
58
  # Check for WWW-Authenticate header
61
- if "WWW-Authenticate" in response.headers:
59
+ if "WWW-Authenticate" in response.headers: # noqa: SIM103
62
60
  return True
63
61
 
64
62
  # If we get a successful response, auth may not be required
@@ -195,7 +193,8 @@ class OAuth(OAuthClientProvider):
195
193
 
196
194
  warn(
197
195
  message="Using in-memory token storage is not recommended for production use -- "
198
- + "tokens will be lost on server restart."
196
+ + "tokens will be lost on server restart.",
197
+ stacklevel=2,
199
198
  )
200
199
 
201
200
  self.token_storage_adapter: TokenStorageAdapter = TokenStorageAdapter(
@@ -272,8 +271,10 @@ class OAuth(OAuthClientProvider):
272
271
  if result.error:
273
272
  raise result.error
274
273
  return result.code, result.state # type: ignore
275
- except TimeoutError:
276
- raise TimeoutError(f"OAuth callback timed out after {TIMEOUT} seconds")
274
+ except TimeoutError as e:
275
+ raise TimeoutError(
276
+ f"OAuth callback timed out after {TIMEOUT} seconds"
277
+ ) from e
277
278
  finally:
278
279
  server.should_exit = True
279
280
  await anyio.sleep(0.1) # Allow server to shut down gracefully
fastmcp/client/client.py CHANGED
@@ -61,15 +61,15 @@ from .transports import (
61
61
 
62
62
  __all__ = [
63
63
  "Client",
64
- "SessionKwargs",
65
- "RootsHandler",
66
- "RootsList",
67
- "LogHandler",
68
- "MessageHandler",
69
64
  "ClientSamplingHandler",
70
- "SamplingHandler",
71
65
  "ElicitationHandler",
66
+ "LogHandler",
67
+ "MessageHandler",
72
68
  "ProgressHandler",
69
+ "RootsHandler",
70
+ "RootsList",
71
+ "SamplingHandler",
72
+ "SessionKwargs",
73
73
  ]
74
74
 
75
75
  logger = get_logger(__name__)
@@ -362,10 +362,10 @@ class Client(Generic[ClientTransportT]):
362
362
  await self._session_state.session.initialize()
363
363
  )
364
364
  yield
365
- except anyio.ClosedResourceError:
366
- raise RuntimeError("Server session was closed unexpectedly")
367
- except TimeoutError:
368
- raise RuntimeError("Failed to initialize server session")
365
+ except anyio.ClosedResourceError as e:
366
+ raise RuntimeError("Server session was closed unexpectedly") from e
367
+ except TimeoutError as e:
368
+ raise RuntimeError("Failed to initialize server session") from e
369
369
  finally:
370
370
  self._session_state.session = None
371
371
  self._session_state.initialize_result = None
@@ -11,7 +11,7 @@ from mcp.types import SamplingMessage
11
11
 
12
12
  from fastmcp.server.sampling.handler import ServerSamplingHandler
13
13
 
14
- __all__ = ["SamplingMessage", "SamplingParams", "SamplingHandler"]
14
+ __all__ = ["SamplingHandler", "SamplingMessage", "SamplingParams"]
15
15
 
16
16
 
17
17
  ClientSamplingHandler: TypeAlias = Callable[
@@ -46,16 +46,16 @@ ClientTransportT = TypeVar("ClientTransportT", bound="ClientTransport")
46
46
 
47
47
  __all__ = [
48
48
  "ClientTransport",
49
- "SSETransport",
50
- "StreamableHttpTransport",
51
- "StdioTransport",
52
- "PythonStdioTransport",
53
49
  "FastMCPStdioTransport",
50
+ "FastMCPTransport",
54
51
  "NodeStdioTransport",
55
- "UvxStdioTransport",
56
- "UvStdioTransport",
57
52
  "NpxStdioTransport",
58
- "FastMCPTransport",
53
+ "PythonStdioTransport",
54
+ "SSETransport",
55
+ "StdioTransport",
56
+ "StreamableHttpTransport",
57
+ "UvStdioTransport",
58
+ "UvxStdioTransport",
59
59
  "infer_transport",
60
60
  ]
61
61
 
@@ -109,9 +109,8 @@ class ClientTransport(abc.ABC):
109
109
  # Basic representation for subclasses
110
110
  return f"<{self.__class__.__name__}>"
111
111
 
112
- async def close(self):
112
+ async def close(self): # noqa: B027
113
113
  """Close the transport."""
114
- pass
115
114
 
116
115
  def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
117
116
  if auth is not None:
@@ -141,10 +140,10 @@ class WSTransport(ClientTransport):
141
140
  ) -> AsyncIterator[ClientSession]:
142
141
  try:
143
142
  from mcp.client.websocket import websocket_client
144
- except ImportError:
143
+ except ImportError as e:
145
144
  raise ImportError(
146
145
  "The websocket transport is not available. Please install fastmcp[websockets] or install the websockets package manually."
147
- )
146
+ ) from e
148
147
 
149
148
  async with websocket_client(self.url) as transport:
150
149
  read_stream, write_stream = transport
@@ -207,7 +206,7 @@ class SSETransport(ClientTransport):
207
206
  # instead we simply leave the kwarg out if it's not provided
208
207
  if self.sse_read_timeout is not None:
209
208
  client_kwargs["sse_read_timeout"] = self.sse_read_timeout.total_seconds()
210
- if session_kwargs.get("read_timeout_seconds", None) is not None:
209
+ if session_kwargs.get("read_timeout_seconds") is not None:
211
210
  read_timeout_seconds = cast(
212
211
  datetime.timedelta, session_kwargs.get("read_timeout_seconds")
213
212
  )
@@ -277,7 +276,7 @@ class StreamableHttpTransport(ClientTransport):
277
276
  # instead we simply leave the kwarg out if it's not provided
278
277
  if self.sse_read_timeout is not None:
279
278
  client_kwargs["sse_read_timeout"] = self.sse_read_timeout
280
- if session_kwargs.get("read_timeout_seconds", None) is not None:
279
+ if session_kwargs.get("read_timeout_seconds") is not None:
281
280
  client_kwargs["timeout"] = session_kwargs.get("read_timeout_seconds")
282
281
 
283
282
  if self.httpx_client_factory is not None:
@@ -451,8 +450,7 @@ async def _stdio_transport_connect_task(
451
450
  if log_file is None:
452
451
  log_file_handle = sys.stderr
453
452
  elif isinstance(log_file, Path):
454
- log_file_handle = open(log_file, "a")
455
- stack.callback(log_file_handle.close)
453
+ log_file_handle = stack.enter_context(log_file.open("a"))
456
454
  else:
457
455
  # Must be TextIO - use it directly
458
456
  log_file_handle = log_file
@@ -852,26 +850,28 @@ class FastMCPTransport(ClientTransport):
852
850
  server_read, server_write = server_streams
853
851
 
854
852
  # Create a cancel scope for the server task
855
- async with anyio.create_task_group() as tg:
856
- async with _enter_server_lifespan(server=self.server):
857
- tg.start_soon(
858
- lambda: self.server._mcp_server.run(
859
- server_read,
860
- server_write,
861
- self.server._mcp_server.create_initialization_options(),
862
- raise_exceptions=self.raise_exceptions,
863
- )
853
+ async with (
854
+ anyio.create_task_group() as tg,
855
+ _enter_server_lifespan(server=self.server),
856
+ ):
857
+ tg.start_soon(
858
+ lambda: self.server._mcp_server.run(
859
+ server_read,
860
+ server_write,
861
+ self.server._mcp_server.create_initialization_options(),
862
+ raise_exceptions=self.raise_exceptions,
864
863
  )
864
+ )
865
865
 
866
- try:
867
- async with ClientSession(
868
- read_stream=client_read,
869
- write_stream=client_write,
870
- **session_kwargs,
871
- ) as client_session:
872
- yield client_session
873
- finally:
874
- tg.cancel_scope.cancel()
866
+ try:
867
+ async with ClientSession(
868
+ read_stream=client_read,
869
+ write_stream=client_write,
870
+ **session_kwargs,
871
+ ) as client_session:
872
+ yield client_session
873
+ finally:
874
+ tg.cancel_scope.cancel()
875
875
 
876
876
  def __repr__(self) -> str:
877
877
  return f"<FastMCPTransport(server='{self.server.name}')>"
@@ -952,7 +952,7 @@ class MCPConfigTransport(ClientTransport):
952
952
 
953
953
  # if there's exactly one server, create a client for that server
954
954
  elif len(self.config.mcpServers) == 1:
955
- self.transport = list(self.config.mcpServers.values())[0].to_transport()
955
+ self.transport = next(iter(self.config.mcpServers.values())).to_transport()
956
956
  self._underlying_transports.append(self.transport)
957
957
 
958
958
  # otherwise create a composite client
@@ -1,4 +1,4 @@
1
1
  from .component_manager import set_up_component_manager
2
2
  from .component_service import ComponentService
3
3
 
4
- __all__ = ["set_up_component_manager", "ComponentService"]
4
+ __all__ = ["ComponentService", "set_up_component_manager"]
@@ -97,11 +97,11 @@ def make_endpoint(action, component, config):
97
97
  return JSONResponse(
98
98
  {"message": f"{action.capitalize()}d {component}: {name}"}
99
99
  )
100
- except NotFoundError:
100
+ except NotFoundError as e:
101
101
  raise StarletteHTTPException(
102
102
  status_code=404,
103
103
  detail=f"Unknown {component}: {name}",
104
- )
104
+ ) from e
105
105
 
106
106
  return endpoint
107
107
 
@@ -2,7 +2,7 @@ from .mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt
2
2
 
3
3
  __all__ = [
4
4
  "MCPMixin",
5
- "mcp_tool",
6
- "mcp_resource",
7
5
  "mcp_prompt",
6
+ "mcp_resource",
7
+ "mcp_tool",
8
8
  ]
@@ -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
 
@@ -22,17 +22,14 @@ from .components import (
22
22
 
23
23
  # Export public symbols - maintaining backward compatibility
24
24
  __all__ = [
25
- # Server
25
+ "DEFAULT_ROUTE_MAPPINGS",
26
+ "ComponentFn",
26
27
  "FastMCPOpenAPI",
27
- # Routing
28
28
  "MCPType",
29
+ "OpenAPIResource",
30
+ "OpenAPIResourceTemplate",
31
+ "OpenAPITool",
29
32
  "RouteMap",
30
33
  "RouteMapFn",
31
- "ComponentFn",
32
- "DEFAULT_ROUTE_MAPPINGS",
33
34
  "_determine_route_type",
34
- # Components
35
- "OpenAPITool",
36
- "OpenAPIResource",
37
- "OpenAPIResourceTemplate",
38
35
  ]
@@ -146,11 +146,11 @@ class OpenAPITool(Tool):
146
146
  if e.response.text:
147
147
  error_message += f" - {e.response.text}"
148
148
 
149
- raise ValueError(error_message)
149
+ raise ValueError(error_message) from e
150
150
 
151
151
  except httpx.RequestError as e:
152
152
  # Handle request errors (connection, timeout, etc.)
153
- raise ValueError(f"Request error: {str(e)}")
153
+ raise ValueError(f"Request error: {e!s}") from e
154
154
 
155
155
 
156
156
  class OpenAPIResource(Resource):
@@ -165,9 +165,11 @@ class OpenAPIResource(Resource):
165
165
  name: str,
166
166
  description: str,
167
167
  mime_type: str = "application/json",
168
- tags: set[str] = set(),
168
+ tags: set[str] | None = None,
169
169
  timeout: float | None = None,
170
170
  ):
171
+ if tags is None:
172
+ tags = set()
171
173
  super().__init__(
172
174
  uri=AnyUrl(uri), # Convert string to AnyUrl
173
175
  name=name,
@@ -276,11 +278,11 @@ class OpenAPIResource(Resource):
276
278
  if e.response.text:
277
279
  error_message += f" - {e.response.text}"
278
280
 
279
- raise ValueError(error_message)
281
+ raise ValueError(error_message) from e
280
282
 
281
283
  except httpx.RequestError as e:
282
284
  # Handle request errors (connection, timeout, etc.)
283
- raise ValueError(f"Request error: {str(e)}")
285
+ raise ValueError(f"Request error: {e!s}") from e
284
286
 
285
287
 
286
288
  class OpenAPIResourceTemplate(ResourceTemplate):
@@ -295,9 +297,11 @@ class OpenAPIResourceTemplate(ResourceTemplate):
295
297
  name: str,
296
298
  description: str,
297
299
  parameters: dict[str, Any],
298
- tags: set[str] = set(),
300
+ tags: set[str] | None = None,
299
301
  timeout: float | None = None,
300
302
  ):
303
+ if tags is None:
304
+ tags = set()
301
305
  super().__init__(
302
306
  uri_template=uri_template,
303
307
  name=name,
@@ -342,7 +346,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
342
346
 
343
347
  # Export public symbols
344
348
  __all__ = [
345
- "OpenAPITool",
346
349
  "OpenAPIResource",
347
350
  "OpenAPIResourceTemplate",
351
+ "OpenAPITool",
348
352
  ]
@@ -121,10 +121,10 @@ def _determine_route_type(
121
121
 
122
122
  # Export public symbols
123
123
  __all__ = [
124
+ "DEFAULT_ROUTE_MAPPINGS",
125
+ "ComponentFn",
124
126
  "MCPType",
125
127
  "RouteMap",
126
128
  "RouteMapFn",
127
- "ComponentFn",
128
- "DEFAULT_ROUTE_MAPPINGS",
129
129
  "_determine_route_type",
130
130
  ]
@@ -40,29 +40,24 @@ from .json_schema_converter import (
40
40
 
41
41
  # Export public symbols - maintaining backward compatibility
42
42
  __all__ = [
43
- # Models
44
43
  "HTTPRoute",
44
+ "HttpMethod",
45
+ "JsonSchema",
45
46
  "ParameterInfo",
47
+ "ParameterLocation",
46
48
  "RequestBodyInfo",
47
49
  "ResponseInfo",
48
- "HttpMethod",
49
- "ParameterLocation",
50
- "JsonSchema",
51
- # Parser
52
- "parse_openapi_to_http_routes",
53
- # Formatters
50
+ "_combine_schemas",
51
+ "_make_optional_parameter_nullable",
52
+ "clean_schema_for_display",
53
+ "convert_openapi_schema_to_json_schema",
54
+ "convert_schema_definitions",
55
+ "extract_output_schema_from_responses",
54
56
  "format_array_parameter",
55
57
  "format_deep_object_parameter",
56
58
  "format_description_with_responses",
57
59
  "format_json_for_description",
58
60
  "format_simple_description",
59
61
  "generate_example_from_schema",
60
- # Schemas
61
- "_combine_schemas",
62
- "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",
62
+ "parse_openapi_to_http_routes",
68
63
  ]
@@ -63,7 +63,7 @@ class RequestDirector:
63
63
 
64
64
  # Step 4: Handle request body
65
65
  if body is not None:
66
- if isinstance(body, dict) or isinstance(body, list):
66
+ if isinstance(body, dict | list):
67
67
  request_data["json"] = body
68
68
  else:
69
69
  request_data["content"] = body
@@ -164,10 +164,10 @@ def _convert_nullable_field(schema: dict[str, Any]) -> dict[str, Any]:
164
164
  if isinstance(current_type, str):
165
165
  result["type"] = [current_type, "null"]
166
166
  elif isinstance(current_type, list) and "null" not in current_type:
167
- result["type"] = current_type + ["null"]
167
+ result["type"] = [*current_type, "null"]
168
168
  elif "oneOf" in result:
169
169
  # Convert oneOf to anyOf with null
170
- result["anyOf"] = result.pop("oneOf") + [{"type": "null"}]
170
+ result["anyOf"] = [*result.pop("oneOf"), {"type": "null"}]
171
171
  elif "anyOf" in result:
172
172
  # Add null to anyOf if not present
173
173
  if not any(item.get("type") == "null" for item in result["anyOf"]):
@@ -79,10 +79,10 @@ class HTTPRoute(FastMCPBaseModel):
79
79
  # Export public symbols
80
80
  __all__ = [
81
81
  "HTTPRoute",
82
+ "HttpMethod",
83
+ "JsonSchema",
82
84
  "ParameterInfo",
85
+ "ParameterLocation",
83
86
  "RequestBodyInfo",
84
87
  "ResponseInfo",
85
- "HttpMethod",
86
- "ParameterLocation",
87
- "JsonSchema",
88
88
  ]
@@ -178,7 +178,7 @@ class OpenAPIParser(
178
178
  else:
179
179
  # Special handling for components
180
180
  if part == "components" and hasattr(target, "components"):
181
- target = getattr(target, "components")
181
+ target = target.components
182
182
  elif hasattr(target, part): # Fallback check
183
183
  target = getattr(target, part, None)
184
184
  else:
@@ -554,9 +554,7 @@ class OpenAPIParser(
554
554
  if "$ref" in obj and isinstance(obj["$ref"], str):
555
555
  ref = obj["$ref"]
556
556
  # Handle both converted and unconverted refs
557
- if ref.startswith("#/$defs/"):
558
- schema_name = ref.split("/")[-1]
559
- elif ref.startswith("#/components/schemas/"):
557
+ if ref.startswith(("#/$defs/", "#/components/schemas/")):
560
558
  schema_name = ref.split("/")[-1]
561
559
  else:
562
560
  return
@@ -815,6 +813,6 @@ class OpenAPIParser(
815
813
 
816
814
  # Export public symbols
817
815
  __all__ = [
818
- "parse_openapi_to_http_routes",
819
816
  "OpenAPIParser",
817
+ "parse_openapi_to_http_routes",
820
818
  ]
@@ -585,9 +585,9 @@ def extract_output_schema_from_responses(
585
585
 
586
586
  # Export public symbols
587
587
  __all__ = [
588
- "clean_schema_for_display",
589
588
  "_combine_schemas",
590
589
  "_combine_schemas_and_map_params",
591
- "extract_output_schema_from_responses",
592
590
  "_make_optional_parameter_nullable",
591
+ "clean_schema_for_display",
592
+ "extract_output_schema_from_responses",
593
593
  ]
fastmcp/mcp_config.py CHANGED
@@ -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
@@ -207,10 +207,7 @@ class FunctionPrompt(Prompt):
207
207
  # Auto-detect context parameter if not provided
208
208
 
209
209
  context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
210
- if context_kwarg:
211
- prune_params = [context_kwarg]
212
- else:
213
- prune_params = None
210
+ prune_params = [context_kwarg] if context_kwarg else None
214
211
 
215
212
  parameters = compress_schema(parameters, prune_params=prune_params)
216
213
 
@@ -290,10 +287,7 @@ class FunctionPrompt(Prompt):
290
287
  if (
291
288
  param.annotation == inspect.Parameter.empty
292
289
  or param.annotation is str
293
- ):
294
- converted_kwargs[param_name] = param_value
295
- # If argument is not a string, pass as-is (already properly typed)
296
- elif not isinstance(param_value, str):
290
+ ) or not isinstance(param_value, str):
297
291
  converted_kwargs[param_name] = param_value
298
292
  else:
299
293
  # Try to convert string argument using type adapter
@@ -314,7 +308,7 @@ class FunctionPrompt(Prompt):
314
308
  raise PromptError(
315
309
  f"Could not convert argument '{param_name}' with value '{param_value}' "
316
310
  f"to expected type {param.annotation}. Error: {e}"
317
- )
311
+ ) from e
318
312
  else:
319
313
  # Parameter not in function signature, pass as-is
320
314
  converted_kwargs[param_name] = param_value
@@ -376,10 +370,12 @@ class FunctionPrompt(Prompt):
376
370
  content=TextContent(type="text", text=content),
377
371
  )
378
372
  )
379
- except Exception:
380
- 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
381
377
 
382
378
  return messages
383
- except Exception:
379
+ except Exception as e:
384
380
  logger.exception(f"Error rendering prompt {self.name}")
385
- raise PromptError(f"Error rendering prompt {self.name}.")
381
+ raise PromptError(f"Error rendering prompt {self.name}.") 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
  ]
@@ -217,9 +217,7 @@ class FunctionResource(Resource):
217
217
 
218
218
  if isinstance(result, Resource):
219
219
  return await result.read()
220
- elif isinstance(result, bytes):
221
- return result
222
- elif isinstance(result, str):
220
+ elif isinstance(result, bytes | str):
223
221
  return result
224
222
  else:
225
223
  return pydantic_core.to_json(result, fallback=str).decode()