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/server/server.py CHANGED
@@ -15,7 +15,11 @@ from collections.abc import (
15
15
  Mapping,
16
16
  Sequence,
17
17
  )
18
- from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
18
+ from contextlib import (
19
+ AbstractAsyncContextManager,
20
+ AsyncExitStack,
21
+ asynccontextmanager,
22
+ )
19
23
  from dataclasses import dataclass
20
24
  from functools import partial
21
25
  from pathlib import Path
@@ -150,7 +154,7 @@ class FastMCP(Generic[LifespanResultT]):
150
154
  version: str | None = None,
151
155
  website_url: str | None = None,
152
156
  icons: list[mcp.types.Icon] | None = None,
153
- auth: AuthProvider | None | NotSetT = NotSet,
157
+ auth: AuthProvider | NotSetT | None = NotSet,
154
158
  middleware: Sequence[Middleware] | None = None,
155
159
  lifespan: LifespanCallable | None = None,
156
160
  dependencies: list[str] | None = None,
@@ -1062,10 +1066,10 @@ class FastMCP(Generic[LifespanResultT]):
1062
1066
  try:
1063
1067
  result = await self._call_tool_middleware(key, arguments)
1064
1068
  return result.to_mcp_result()
1065
- except DisabledError:
1066
- raise NotFoundError(f"Unknown tool: {key}")
1067
- except NotFoundError:
1068
- raise NotFoundError(f"Unknown tool: {key}")
1069
+ except DisabledError as e:
1070
+ raise NotFoundError(f"Unknown tool: {key}") from e
1071
+ except NotFoundError as e:
1072
+ raise NotFoundError(f"Unknown tool: {key}") from e
1069
1073
 
1070
1074
  async def _call_tool_middleware(
1071
1075
  self,
@@ -1142,12 +1146,12 @@ class FastMCP(Generic[LifespanResultT]):
1142
1146
  return list[ReadResourceContents](
1143
1147
  await self._read_resource_middleware(uri)
1144
1148
  )
1145
- except DisabledError:
1149
+ except DisabledError as e:
1146
1150
  # convert to NotFoundError to avoid leaking resource presence
1147
- raise NotFoundError(f"Unknown resource: {str(uri)!r}")
1148
- except NotFoundError:
1151
+ raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
1152
+ except NotFoundError as e:
1149
1153
  # standardize NotFound message
1150
- raise NotFoundError(f"Unknown resource: {str(uri)!r}")
1154
+ raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
1151
1155
 
1152
1156
  async def _read_resource_middleware(
1153
1157
  self,
@@ -1158,10 +1162,7 @@ class FastMCP(Generic[LifespanResultT]):
1158
1162
  """
1159
1163
 
1160
1164
  # Convert string URI to AnyUrl if needed
1161
- if isinstance(uri, str):
1162
- uri_param = AnyUrl(uri)
1163
- else:
1164
- uri_param = uri
1165
+ uri_param = AnyUrl(uri) if isinstance(uri, str) else uri
1165
1166
 
1166
1167
  mw_context = MiddlewareContext(
1167
1168
  message=mcp.types.ReadResourceRequestParams(uri=uri_param),
@@ -1241,12 +1242,12 @@ class FastMCP(Generic[LifespanResultT]):
1241
1242
  async with fastmcp.server.context.Context(fastmcp=self):
1242
1243
  try:
1243
1244
  return await self._get_prompt_middleware(name, arguments)
1244
- except DisabledError:
1245
+ except DisabledError as e:
1245
1246
  # convert to NotFoundError to avoid leaking prompt presence
1246
- raise NotFoundError(f"Unknown prompt: {name}")
1247
- except NotFoundError:
1247
+ raise NotFoundError(f"Unknown prompt: {name}") from e
1248
+ except NotFoundError as e:
1248
1249
  # standardize NotFound message
1249
- raise NotFoundError(f"Unknown prompt: {name}")
1250
+ raise NotFoundError(f"Unknown prompt: {name}") from e
1250
1251
 
1251
1252
  async def _get_prompt_middleware(
1252
1253
  self, name: str, arguments: dict[str, Any] | None = None
@@ -1369,7 +1370,7 @@ class FastMCP(Generic[LifespanResultT]):
1369
1370
  description: str | None = None,
1370
1371
  icons: list[mcp.types.Icon] | None = None,
1371
1372
  tags: set[str] | None = None,
1372
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
1373
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
1373
1374
  annotations: ToolAnnotations | dict[str, Any] | None = None,
1374
1375
  exclude_args: list[str] | None = None,
1375
1376
  meta: dict[str, Any] | None = None,
@@ -1386,7 +1387,7 @@ class FastMCP(Generic[LifespanResultT]):
1386
1387
  description: str | None = None,
1387
1388
  icons: list[mcp.types.Icon] | None = None,
1388
1389
  tags: set[str] | None = None,
1389
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
1390
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
1390
1391
  annotations: ToolAnnotations | dict[str, Any] | None = None,
1391
1392
  exclude_args: list[str] | None = None,
1392
1393
  meta: dict[str, Any] | None = None,
@@ -1402,7 +1403,7 @@ class FastMCP(Generic[LifespanResultT]):
1402
1403
  description: str | None = None,
1403
1404
  icons: list[mcp.types.Icon] | None = None,
1404
1405
  tags: set[str] | None = None,
1405
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
1406
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
1406
1407
  annotations: ToolAnnotations | dict[str, Any] | None = None,
1407
1408
  exclude_args: list[str] | None = None,
1408
1409
  meta: dict[str, Any] | None = None,
@@ -2029,14 +2030,14 @@ class FastMCP(Generic[LifespanResultT]):
2029
2030
  port=port,
2030
2031
  path=server_path,
2031
2032
  )
2032
- _uvicorn_config_from_user = uvicorn_config or {}
2033
+ uvicorn_config_from_user = uvicorn_config or {}
2033
2034
 
2034
2035
  config_kwargs: dict[str, Any] = {
2035
2036
  "timeout_graceful_shutdown": 0,
2036
2037
  "lifespan": "on",
2037
2038
  "ws": "websockets-sansio",
2038
2039
  }
2039
- config_kwargs.update(_uvicorn_config_from_user)
2040
+ config_kwargs.update(uvicorn_config_from_user)
2040
2041
 
2041
2042
  if "log_config" not in config_kwargs and "log_level" not in config_kwargs:
2042
2043
  config_kwargs["log_level"] = default_log_level_to_use
@@ -2605,8 +2606,8 @@ class FastMCP(Generic[LifespanResultT]):
2605
2606
  # - Connected clients: reuse existing session for all requests
2606
2607
  # - Disconnected clients: create fresh sessions per request for isolation
2607
2608
  if client.is_connected():
2608
- _proxy_logger = get_logger(__name__)
2609
- _proxy_logger.info(
2609
+ proxy_logger = get_logger(__name__)
2610
+ proxy_logger.info(
2610
2611
  "Proxy detected connected client - reusing existing session for all requests. "
2611
2612
  "This may cause context mixing in concurrent scenarios."
2612
2613
  )
@@ -2678,10 +2679,7 @@ class FastMCP(Generic[LifespanResultT]):
2678
2679
  return False
2679
2680
 
2680
2681
  if self.include_tags is not None:
2681
- if any(itag in component.tags for itag in self.include_tags):
2682
- return True
2683
- else:
2684
- return False
2682
+ return bool(any(itag in component.tags for itag in self.include_tags))
2685
2683
 
2686
2684
  return True
2687
2685
 
fastmcp/tools/__init__.py CHANGED
@@ -2,4 +2,4 @@ from .tool import Tool, FunctionTool
2
2
  from .tool_manager import ToolManager
3
3
  from .tool_transform import forward, forward_raw
4
4
 
5
- __all__ = ["Tool", "ToolManager", "FunctionTool", "forward", "forward_raw"]
5
+ __all__ = ["FunctionTool", "Tool", "ToolManager", "forward", "forward_raw"]
fastmcp/tools/tool.py CHANGED
@@ -173,7 +173,7 @@ class Tool(FastMCPComponent):
173
173
  tags: set[str] | None = None,
174
174
  annotations: ToolAnnotations | None = None,
175
175
  exclude_args: list[str] | None = None,
176
- output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
176
+ output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
177
177
  serializer: ToolResultSerializerType | None = None,
178
178
  meta: dict[str, Any] | None = None,
179
179
  enabled: bool | None = None,
@@ -212,13 +212,13 @@ class Tool(FastMCPComponent):
212
212
  tool: Tool,
213
213
  *,
214
214
  name: str | None = None,
215
- title: str | None | NotSetT = NotSet,
216
- description: str | None | NotSetT = NotSet,
215
+ title: str | NotSetT | None = NotSet,
216
+ description: str | NotSetT | None = NotSet,
217
217
  tags: set[str] | None = None,
218
- annotations: ToolAnnotations | None | NotSetT = NotSet,
219
- output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
218
+ annotations: ToolAnnotations | NotSetT | None = NotSet,
219
+ output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
220
220
  serializer: ToolResultSerializerType | None = None,
221
- meta: dict[str, Any] | None | NotSetT = NotSet,
221
+ meta: dict[str, Any] | NotSetT | None = NotSet,
222
222
  transform_args: dict[str, ArgTransform] | None = None,
223
223
  enabled: bool | None = None,
224
224
  transform_fn: Callable[..., Any] | None = None,
@@ -255,7 +255,7 @@ class FunctionTool(Tool):
255
255
  tags: set[str] | None = None,
256
256
  annotations: ToolAnnotations | None = None,
257
257
  exclude_args: list[str] | None = None,
258
- output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
258
+ output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
259
259
  serializer: ToolResultSerializerType | None = None,
260
260
  meta: dict[str, Any] | None = None,
261
261
  enabled: bool | None = None,
@@ -446,9 +446,8 @@ class ParsedFunction:
446
446
  # we ensure that no output schema is automatically generated.
447
447
  clean_output_type = replace_type(
448
448
  output_type,
449
- {
450
- t: _UnserializableType
451
- for t in (
449
+ dict.fromkeys( # type: ignore[arg-type]
450
+ (
452
451
  Image,
453
452
  Audio,
454
453
  File,
@@ -458,8 +457,9 @@ class ParsedFunction:
458
457
  mcp.types.AudioContent,
459
458
  mcp.types.ResourceLink,
460
459
  mcp.types.EmbeddedResource,
461
- )
462
- },
460
+ ),
461
+ _UnserializableType,
462
+ ),
463
463
  )
464
464
 
465
465
  try:
@@ -365,15 +365,15 @@ class TransformedTool(Tool):
365
365
  cls,
366
366
  tool: Tool,
367
367
  name: str | None = None,
368
- title: str | None | NotSetT = NotSet,
369
- description: str | None | NotSetT = NotSet,
368
+ title: str | NotSetT | None = NotSet,
369
+ description: str | NotSetT | None = NotSet,
370
370
  tags: set[str] | None = None,
371
371
  transform_fn: Callable[..., Any] | None = None,
372
372
  transform_args: dict[str, ArgTransform] | None = None,
373
- annotations: ToolAnnotations | None | NotSetT = NotSet,
374
- output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
375
- serializer: Callable[[Any], str] | None | NotSetT = NotSet,
376
- meta: dict[str, Any] | None | NotSetT = NotSet,
373
+ annotations: ToolAnnotations | NotSetT | None = NotSet,
374
+ output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
375
+ serializer: Callable[[Any], str] | NotSetT | None = NotSet,
376
+ meta: dict[str, Any] | NotSetT | None = NotSet,
377
377
  enabled: bool | None = None,
378
378
  ) -> TransformedTool:
379
379
  """Create a transformed tool from a parent tool.
fastmcp/utilities/cli.py CHANGED
@@ -240,12 +240,11 @@ def log_server_banner(
240
240
  info_table.add_row("📦", "Transport:", display_transport)
241
241
 
242
242
  # Show connection info based on transport
243
- if transport in ("http", "streamable-http", "sse"):
244
- if host and port:
245
- server_url = f"http://{host}:{port}"
246
- if path:
247
- server_url += f"/{path.lstrip('/')}"
248
- info_table.add_row("🔗", "Server URL:", server_url)
243
+ if transport in ("http", "streamable-http", "sse") and host and port:
244
+ server_url = f"http://{host}:{port}"
245
+ if path:
246
+ server_url += f"/{path.lstrip('/')}"
247
+ info_table.add_row("🔗", "Server URL:", server_url)
249
248
 
250
249
  # Add documentation link
251
250
  info_table.add_row("", "", "")
@@ -412,7 +412,7 @@ class InspectFormat(str, Enum):
412
412
  MCP = "mcp"
413
413
 
414
414
 
415
- async def format_fastmcp_info(info: FastMCPInfo) -> bytes:
415
+ def format_fastmcp_info(info: FastMCPInfo) -> bytes:
416
416
  """Format FastMCPInfo as FastMCP-specific JSON.
417
417
 
418
418
  This includes FastMCP-specific fields like tags, enabled, annotations, etc.
@@ -501,6 +501,6 @@ async def format_info(
501
501
  # This works for both v1 and v2 servers
502
502
  if info is None:
503
503
  info = await inspect_fastmcp(mcp)
504
- return await format_fastmcp_info(info)
504
+ return format_fastmcp_info(info)
505
505
  else:
506
506
  raise ValueError(f"Unknown format: {format}")
@@ -61,7 +61,7 @@ from pydantic import (
61
61
  )
62
62
  from typing_extensions import NotRequired, TypedDict
63
63
 
64
- __all__ = ["json_schema_to_type", "JSONSchema"]
64
+ __all__ = ["JSONSchema", "json_schema_to_type"]
65
65
 
66
66
 
67
67
  FORMAT_TYPES: dict[str, Any] = {
@@ -368,7 +368,7 @@ def _schema_to_type(
368
368
  return types[0]
369
369
  else:
370
370
  if has_null:
371
- return Union[tuple(types + [type(None)])] # type: ignore # noqa: UP007
371
+ return Union[(*types, type(None))] # type: ignore
372
372
  else:
373
373
  return Union[tuple(types)] # type: ignore # noqa: UP007
374
374
 
@@ -389,7 +389,7 @@ def _schema_to_type(
389
389
  if len(types) == 1:
390
390
  return types[0] | None # type: ignore
391
391
  else:
392
- return Union[tuple(types + [type(None)])] # type: ignore # noqa: UP007
392
+ return Union[(*types, type(None))] # type: ignore
393
393
  return Union[tuple(types)] # type: ignore # noqa: UP007
394
394
 
395
395
  return _get_from_type_handler(schema, schemas)(schema)
@@ -578,7 +578,7 @@ def _create_dataclass(
578
578
  return _merge_defaults(data, original_schema)
579
579
  return data
580
580
 
581
- setattr(cls, "_apply_defaults", _apply_defaults)
581
+ cls._apply_defaults = _apply_defaults # type: ignore[attr-defined]
582
582
 
583
583
  # Store completed class
584
584
  _classes[cache_key] = cls
@@ -147,6 +147,18 @@ def temporary_log_level(
147
147
  yield
148
148
 
149
149
 
150
+ _level_to_no: dict[
151
+ Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None, int | None
152
+ ] = {
153
+ "DEBUG": logging.DEBUG,
154
+ "INFO": logging.INFO,
155
+ "WARNING": logging.WARNING,
156
+ "ERROR": logging.ERROR,
157
+ "CRITICAL": logging.CRITICAL,
158
+ None: None,
159
+ }
160
+
161
+
150
162
  class _ClampedLogFilter(logging.Filter):
151
163
  min_level: tuple[int, str] | None
152
164
  max_level: tuple[int, str] | None
@@ -161,29 +173,13 @@ class _ClampedLogFilter(logging.Filter):
161
173
  self.min_level = None
162
174
  self.max_level = None
163
175
 
164
- if min_level_no := self._level_to_no(level=min_level):
176
+ if min_level_no := _level_to_no.get(min_level):
165
177
  self.min_level = (min_level_no, str(min_level))
166
- if max_level_no := self._level_to_no(level=max_level):
178
+ if max_level_no := _level_to_no.get(max_level):
167
179
  self.max_level = (max_level_no, str(max_level))
168
180
 
169
181
  super().__init__()
170
182
 
171
- def _level_to_no(
172
- self, level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None
173
- ) -> int | None:
174
- if level == "DEBUG":
175
- return logging.DEBUG
176
- elif level == "INFO":
177
- return logging.INFO
178
- elif level == "WARNING":
179
- return logging.WARNING
180
- elif level == "ERROR":
181
- return logging.ERROR
182
- elif level == "CRITICAL":
183
- return logging.CRITICAL
184
- else:
185
- return None
186
-
187
183
  @override
188
184
  def filter(self, record: logging.LogRecord) -> bool:
189
185
  if self.max_level:
@@ -15,11 +15,11 @@ from fastmcp.utilities.mcp_server_config.v1.sources.base import Source
15
15
  from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
16
16
 
17
17
  __all__ = [
18
- "Source",
19
18
  "Deployment",
20
19
  "Environment",
21
- "UVEnvironment",
22
- "MCPServerConfig",
23
20
  "FileSystemSource",
21
+ "MCPServerConfig",
22
+ "Source",
23
+ "UVEnvironment",
24
24
  "generate_schema",
25
25
  ]
@@ -19,7 +19,6 @@ class Environment(BaseModel, ABC):
19
19
  Returns:
20
20
  Full command ready for subprocess execution
21
21
  """
22
- pass
23
22
 
24
23
  async def prepare(self, output_dir: Path | None = None) -> None:
25
24
  """Prepare the environment (optional, can be no-op).
@@ -27,4 +26,4 @@ class Environment(BaseModel, ABC):
27
26
  Args:
28
27
  output_dir: Directory for persistent environment setup
29
28
  """
30
- pass # Default no-op implementation
29
+ # Default no-op implementation
@@ -17,7 +17,6 @@ class Source(BaseModel, ABC):
17
17
  need preparation (e.g., local files), this is a no-op.
18
18
  """
19
19
  # Default implementation for sources that don't need preparation
20
- pass
21
20
 
22
21
  @abstractmethod
23
22
  async def load_server(self) -> Any:
@@ -175,16 +175,16 @@ class HTTPRoute(FastMCPBaseModel):
175
175
  # Export public symbols
176
176
  __all__ = [
177
177
  "HTTPRoute",
178
+ "HttpMethod",
179
+ "JsonSchema",
178
180
  "ParameterInfo",
181
+ "ParameterLocation",
179
182
  "RequestBodyInfo",
180
183
  "ResponseInfo",
181
- "HttpMethod",
182
- "ParameterLocation",
183
- "JsonSchema",
184
- "parse_openapi_to_http_routes",
184
+ "_handle_nullable_fields",
185
185
  "extract_output_schema_from_responses",
186
186
  "format_deep_object_parameter",
187
- "_handle_nullable_fields",
187
+ "parse_openapi_to_http_routes",
188
188
  ]
189
189
 
190
190
  # Type variables for generic parser
@@ -321,7 +321,7 @@ class OpenAPIParser(
321
321
  else:
322
322
  # Special handling for components
323
323
  if part == "components" and hasattr(target, "components"):
324
- target = getattr(target, "components")
324
+ target = target.components
325
325
  elif hasattr(target, part): # Fallback check
326
326
  target = getattr(target, part, None)
327
327
  else:
@@ -1178,10 +1178,10 @@ def _add_null_to_type(schema: dict[str, Any]) -> None:
1178
1178
  elif isinstance(current_type, list):
1179
1179
  # Add null to array if not already present
1180
1180
  if "null" not in current_type:
1181
- schema["type"] = current_type + ["null"]
1181
+ schema["type"] = [*current_type, "null"]
1182
1182
  elif "oneOf" in schema:
1183
1183
  # Convert oneOf to anyOf with null type
1184
- schema["anyOf"] = schema.pop("oneOf") + [{"type": "null"}]
1184
+ schema["anyOf"] = [*schema.pop("oneOf"), {"type": "null"}]
1185
1185
  elif "anyOf" in schema:
1186
1186
  # Add null type to anyOf if not already present
1187
1187
  if not any(item.get("type") == "null" for item in schema["anyOf"]):
@@ -1233,7 +1233,7 @@ def _handle_nullable_fields(schema: dict[str, Any] | Any) -> dict[str, Any] | An
1233
1233
 
1234
1234
  # Handle properties nullable fields
1235
1235
  if has_property_nullable_field and "properties" in result:
1236
- for prop_name, prop_schema in result["properties"].items():
1236
+ for _prop_name, prop_schema in result["properties"].items():
1237
1237
  if isinstance(prop_schema, dict) and "nullable" in prop_schema:
1238
1238
  nullable_value = prop_schema.pop("nullable")
1239
1239
  if nullable_value and (
@@ -6,7 +6,7 @@ import multiprocessing
6
6
  import socket
7
7
  import time
8
8
  from collections.abc import AsyncGenerator, Callable, Generator
9
- from contextlib import asynccontextmanager, contextmanager
9
+ from contextlib import asynccontextmanager, contextmanager, suppress
10
10
  from typing import TYPE_CHECKING, Any, Literal
11
11
  from urllib.parse import parse_qs, urlparse
12
12
 
@@ -216,10 +216,8 @@ async def run_server_async(
216
216
  finally:
217
217
  # Cleanup: cancel the task
218
218
  server_task.cancel()
219
- try:
219
+ with suppress(asyncio.CancelledError):
220
220
  await server_task
221
- except asyncio.CancelledError:
222
- pass
223
221
 
224
222
 
225
223
  @contextmanager
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.13.0rc3
3
+ Version: 2.13.0.1
4
4
  Summary: The fast, Pythonic way to build MCP servers and clients.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp