fastmcp 2.11.2__py3-none-any.whl → 2.12.0rc1__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 (77) hide show
  1. fastmcp/__init__.py +5 -4
  2. fastmcp/cli/claude.py +22 -18
  3. fastmcp/cli/cli.py +472 -136
  4. fastmcp/cli/install/claude_code.py +37 -40
  5. fastmcp/cli/install/claude_desktop.py +37 -42
  6. fastmcp/cli/install/cursor.py +148 -38
  7. fastmcp/cli/install/mcp_json.py +38 -43
  8. fastmcp/cli/install/shared.py +64 -7
  9. fastmcp/cli/run.py +122 -215
  10. fastmcp/client/auth/oauth.py +69 -13
  11. fastmcp/client/client.py +46 -9
  12. fastmcp/client/logging.py +25 -1
  13. fastmcp/client/oauth_callback.py +91 -91
  14. fastmcp/client/sampling.py +12 -4
  15. fastmcp/client/transports.py +143 -67
  16. fastmcp/experimental/sampling/__init__.py +0 -0
  17. fastmcp/experimental/sampling/handlers/__init__.py +3 -0
  18. fastmcp/experimental/sampling/handlers/base.py +21 -0
  19. fastmcp/experimental/sampling/handlers/openai.py +163 -0
  20. fastmcp/experimental/server/openapi/routing.py +1 -3
  21. fastmcp/experimental/server/openapi/server.py +10 -25
  22. fastmcp/experimental/utilities/openapi/__init__.py +2 -2
  23. fastmcp/experimental/utilities/openapi/formatters.py +34 -0
  24. fastmcp/experimental/utilities/openapi/models.py +5 -2
  25. fastmcp/experimental/utilities/openapi/parser.py +252 -70
  26. fastmcp/experimental/utilities/openapi/schemas.py +135 -106
  27. fastmcp/mcp_config.py +40 -20
  28. fastmcp/prompts/prompt_manager.py +4 -2
  29. fastmcp/resources/resource_manager.py +16 -6
  30. fastmcp/server/auth/__init__.py +11 -1
  31. fastmcp/server/auth/auth.py +19 -2
  32. fastmcp/server/auth/oauth_proxy.py +1047 -0
  33. fastmcp/server/auth/providers/azure.py +270 -0
  34. fastmcp/server/auth/providers/github.py +287 -0
  35. fastmcp/server/auth/providers/google.py +305 -0
  36. fastmcp/server/auth/providers/jwt.py +27 -16
  37. fastmcp/server/auth/providers/workos.py +256 -2
  38. fastmcp/server/auth/redirect_validation.py +65 -0
  39. fastmcp/server/auth/registry.py +1 -1
  40. fastmcp/server/context.py +91 -41
  41. fastmcp/server/dependencies.py +32 -2
  42. fastmcp/server/elicitation.py +60 -1
  43. fastmcp/server/http.py +44 -37
  44. fastmcp/server/middleware/logging.py +66 -28
  45. fastmcp/server/proxy.py +2 -0
  46. fastmcp/server/sampling/handler.py +19 -0
  47. fastmcp/server/server.py +85 -20
  48. fastmcp/settings.py +18 -3
  49. fastmcp/tools/tool.py +23 -10
  50. fastmcp/tools/tool_manager.py +5 -1
  51. fastmcp/tools/tool_transform.py +75 -32
  52. fastmcp/utilities/auth.py +34 -0
  53. fastmcp/utilities/cli.py +148 -15
  54. fastmcp/utilities/components.py +21 -5
  55. fastmcp/utilities/inspect.py +166 -37
  56. fastmcp/utilities/json_schema_type.py +4 -2
  57. fastmcp/utilities/logging.py +4 -1
  58. fastmcp/utilities/mcp_config.py +47 -18
  59. fastmcp/utilities/mcp_server_config/__init__.py +25 -0
  60. fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
  61. fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
  62. fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
  63. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
  64. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
  65. fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
  66. fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
  67. fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
  68. fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
  69. fastmcp/utilities/openapi.py +4 -4
  70. fastmcp/utilities/tests.py +7 -2
  71. fastmcp/utilities/types.py +15 -2
  72. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/METADATA +3 -2
  73. fastmcp-2.12.0rc1.dist-info/RECORD +129 -0
  74. fastmcp-2.11.2.dist-info/RECORD +0 -108
  75. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/WHEEL +0 -0
  76. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/entry_points.txt +0 -0
  77. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -20,6 +20,7 @@ from .formatters import (
20
20
  format_deep_object_parameter,
21
21
  format_description_with_responses,
22
22
  format_json_for_description,
23
+ format_simple_description,
23
24
  generate_example_from_schema,
24
25
  )
25
26
 
@@ -28,7 +29,6 @@ from .schemas import (
28
29
  _combine_schemas,
29
30
  extract_output_schema_from_responses,
30
31
  clean_schema_for_display,
31
- _replace_ref_with_defs,
32
32
  _make_optional_parameter_nullable,
33
33
  )
34
34
 
@@ -55,12 +55,12 @@ __all__ = [
55
55
  "format_deep_object_parameter",
56
56
  "format_description_with_responses",
57
57
  "format_json_for_description",
58
+ "format_simple_description",
58
59
  "generate_example_from_schema",
59
60
  # Schemas
60
61
  "_combine_schemas",
61
62
  "extract_output_schema_from_responses",
62
63
  "clean_schema_for_display",
63
- "_replace_ref_with_defs",
64
64
  "_make_optional_parameter_nullable",
65
65
  # JSON Schema Converter
66
66
  "convert_openapi_schema_to_json_schema",
@@ -189,6 +189,39 @@ def format_json_for_description(data: Any, indent: int = 2) -> str:
189
189
  return f"```\nCould not serialize to JSON: {data}\n```"
190
190
 
191
191
 
192
+ def format_simple_description(
193
+ base_description: str,
194
+ parameters: list[ParameterInfo] | None = None,
195
+ request_body: RequestBodyInfo | None = None,
196
+ ) -> str:
197
+ """
198
+ Formats a simple description for MCP objects (tools, resources, prompts).
199
+ Excludes response details, examples, and verbose status codes.
200
+
201
+ Args:
202
+ base_description (str): The initial description to be formatted.
203
+ parameters (list[ParameterInfo] | None, optional): A list of parameter information.
204
+ request_body (RequestBodyInfo | None, optional): Information about the request body.
205
+
206
+ Returns:
207
+ str: The formatted description string with minimal details.
208
+ """
209
+ desc_parts = [base_description]
210
+
211
+ # Only add critical parameter information if they have descriptions
212
+ if parameters:
213
+ path_params = [p for p in parameters if p.location == "path" and p.description]
214
+ if path_params:
215
+ desc_parts.append("\n\n**Path Parameters:**")
216
+ for param in path_params:
217
+ desc_parts.append(f"\n- **{param.name}**: {param.description}")
218
+
219
+ # Skip query parameters, request body details, and all response information
220
+ # These are already captured in the inputSchema
221
+
222
+ return "\n".join(desc_parts)
223
+
224
+
192
225
  def format_description_with_responses(
193
226
  base_description: str,
194
227
  responses: dict[
@@ -351,5 +384,6 @@ __all__ = [
351
384
  "format_deep_object_parameter",
352
385
  "format_description_with_responses",
353
386
  "format_json_for_description",
387
+ "format_simple_description",
354
388
  "generate_example_from_schema",
355
389
  ]
@@ -58,9 +58,12 @@ class HTTPRoute(FastMCPBaseModel):
58
58
  responses: dict[str, ResponseInfo] = Field(
59
59
  default_factory=dict
60
60
  ) # Key: status code str
61
- schema_definitions: dict[str, JsonSchema] = Field(
61
+ request_schemas: dict[str, JsonSchema] = Field(
62
62
  default_factory=dict
63
- ) # Store component schemas
63
+ ) # Store schemas needed for input (parameters/request body)
64
+ response_schemas: dict[str, JsonSchema] = Field(
65
+ default_factory=dict
66
+ ) # Store schemas needed for output (responses)
64
67
  extensions: dict[str, Any] = Field(default_factory=dict)
65
68
  openapi_version: str | None = None
66
69
 
@@ -34,7 +34,10 @@ from .models import (
34
34
  RequestBodyInfo,
35
35
  ResponseInfo,
36
36
  )
37
- from .schemas import _combine_schemas_and_map_params, _replace_ref_with_defs
37
+ from .schemas import (
38
+ _combine_schemas_and_map_params,
39
+ _replace_ref_with_defs,
40
+ )
38
41
 
39
42
  logger = get_logger(__name__)
40
43
 
@@ -63,7 +66,7 @@ def parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute
63
66
  if openapi_version.startswith("3.0"):
64
67
  # Use OpenAPI 3.0 models
65
68
  openapi_30 = OpenAPI_30.model_validate(openapi_dict)
66
- logger.info(
69
+ logger.debug(
67
70
  f"Successfully parsed OpenAPI 3.0 schema version: {openapi_30.openapi}"
68
71
  )
69
72
  parser = OpenAPIParser(
@@ -81,7 +84,7 @@ def parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute
81
84
  else:
82
85
  # Default to OpenAPI 3.1 models
83
86
  openapi_31 = OpenAPI.model_validate(openapi_dict)
84
- logger.info(
87
+ logger.debug(
85
88
  f"Successfully parsed OpenAPI 3.1 schema version: {openapi_31.openapi}"
86
89
  )
87
90
  parser = OpenAPIParser(
@@ -207,7 +210,7 @@ class OpenAPIParser(
207
210
  try:
208
211
  resolved_schema = self._resolve_ref(schema_obj)
209
212
 
210
- if isinstance(resolved_schema, (self.schema_cls)):
213
+ if isinstance(resolved_schema, self.schema_cls):
211
214
  # Convert schema to dictionary
212
215
  result = resolved_schema.model_dump(
213
216
  mode="json", by_alias=True, exclude_none=True
@@ -220,7 +223,10 @@ class OpenAPIParser(
220
223
  )
221
224
  result = {}
222
225
 
223
- return _replace_ref_with_defs(result)
226
+ # Convert refs from OpenAPI format to JSON Schema format using recursive approach
227
+
228
+ result = _replace_ref_with_defs(result)
229
+ return result
224
230
  except ValueError as e:
225
231
  # Re-raise ValueError for external reference errors and other validation issues
226
232
  if "External or non-local reference not supported" in str(e):
@@ -396,80 +402,239 @@ class OpenAPIParser(
396
402
  )
397
403
  return None
398
404
 
405
+ def _is_success_status_code(self, status_code: str) -> bool:
406
+ """Check if a status code represents a successful response (2xx)."""
407
+ try:
408
+ code_int = int(status_code)
409
+ return 200 <= code_int < 300
410
+ except (ValueError, TypeError):
411
+ # Handle special cases like 'default' or other non-numeric codes
412
+ return status_code.lower() in ["default", "2xx"]
413
+
414
+ def _get_primary_success_response(
415
+ self, operation_responses: dict[str, Any]
416
+ ) -> tuple[str, Any] | None:
417
+ """Get the primary success response for an MCP tool. We only need one success response."""
418
+ if not operation_responses:
419
+ return None
420
+
421
+ # Priority order: 200, 201, 202, 204, 207, then any other 2xx
422
+ priority_codes = ["200", "201", "202", "204", "207"]
423
+
424
+ # First check priority codes
425
+ for code in priority_codes:
426
+ if code in operation_responses:
427
+ return (code, operation_responses[code])
428
+
429
+ # Then check any other 2xx codes
430
+ for status_code, resp_or_ref in operation_responses.items():
431
+ if self._is_success_status_code(status_code):
432
+ return (status_code, resp_or_ref)
433
+
434
+ # If no success codes found, return None (tool will have no output schema)
435
+ return None
436
+
399
437
  def _extract_responses(
400
438
  self, operation_responses: dict[str, Any] | None
401
439
  ) -> dict[str, ResponseInfo]:
402
- """Extract and resolve response information."""
440
+ """Extract and resolve response information. Only includes the primary success response for MCP tools."""
403
441
  extracted_responses: dict[str, ResponseInfo] = {}
404
442
 
405
443
  if not operation_responses:
406
444
  return extracted_responses
407
445
 
408
- for status_code, resp_or_ref in operation_responses.items():
409
- try:
410
- response = self._resolve_ref(resp_or_ref)
446
+ # For MCP tools, we only need the primary success response
447
+ primary_response = self._get_primary_success_response(operation_responses)
448
+ if not primary_response:
449
+ logger.debug("No success responses found, tool will have no output schema")
450
+ return extracted_responses
411
451
 
412
- if not isinstance(response, self.response_cls):
413
- logger.warning(
414
- f"Expected Response after resolving for status code {status_code}, "
415
- f"got {type(response)}. Skipping."
416
- )
417
- continue
452
+ status_code, resp_or_ref = primary_response
453
+ logger.debug(f"Using primary success response: {status_code}")
418
454
 
419
- # Create response info
420
- resp_info = ResponseInfo(description=response.description)
455
+ try:
456
+ response = self._resolve_ref(resp_or_ref)
421
457
 
422
- # Extract content schemas
423
- if hasattr(response, "content") and response.content:
424
- for media_type_str, media_type_obj in response.content.items():
425
- if (
426
- media_type_obj
427
- and hasattr(media_type_obj, "media_type_schema")
428
- and media_type_obj.media_type_schema
429
- ):
430
- try:
431
- schema_dict = self._extract_schema_as_dict(
432
- media_type_obj.media_type_schema
433
- )
434
- resp_info.content_schema[media_type_str] = schema_dict
435
- except ValueError as e:
436
- # Re-raise ValueError for external reference errors
437
- if (
438
- "External or non-local reference not supported"
439
- in str(e)
440
- ):
441
- raise
442
- logger.error(
443
- f"Failed to extract schema for media type '{media_type_str}' "
444
- f"in response {status_code}: {e}"
445
- )
446
- except Exception as e:
447
- logger.error(
448
- f"Failed to extract schema for media type '{media_type_str}' "
449
- f"in response {status_code}: {e}"
450
- )
451
-
452
- extracted_responses[str(status_code)] = resp_info
453
- except ValueError as e:
454
- # Re-raise ValueError for external reference errors
455
- if "External or non-local reference not supported" in str(e):
456
- raise
457
- ref_name = getattr(resp_or_ref, "ref", "unknown")
458
- logger.error(
459
- f"Failed to extract response for status code {status_code} "
460
- f"from reference '{ref_name}': {e}",
461
- exc_info=False,
462
- )
463
- except Exception as e:
464
- ref_name = getattr(resp_or_ref, "ref", "unknown")
465
- logger.error(
466
- f"Failed to extract response for status code {status_code} "
467
- f"from reference '{ref_name}': {e}",
468
- exc_info=False,
458
+ if not isinstance(response, self.response_cls):
459
+ logger.warning(
460
+ f"Expected Response after resolving for status code {status_code}, "
461
+ f"got {type(response)}. Returning empty responses."
469
462
  )
463
+ return extracted_responses
464
+
465
+ # Create response info
466
+ resp_info = ResponseInfo(description=response.description)
467
+
468
+ # Extract content schemas
469
+ if hasattr(response, "content") and response.content:
470
+ for media_type_str, media_type_obj in response.content.items():
471
+ if (
472
+ media_type_obj
473
+ and hasattr(media_type_obj, "media_type_schema")
474
+ and media_type_obj.media_type_schema
475
+ ):
476
+ try:
477
+ schema_dict = self._extract_schema_as_dict(
478
+ media_type_obj.media_type_schema
479
+ )
480
+ resp_info.content_schema[media_type_str] = schema_dict
481
+ except ValueError as e:
482
+ # Re-raise ValueError for external reference errors
483
+ if "External or non-local reference not supported" in str(
484
+ e
485
+ ):
486
+ raise
487
+ logger.error(
488
+ f"Failed to extract schema for media type '{media_type_str}' "
489
+ f"in response {status_code}: {e}"
490
+ )
491
+ except Exception as e:
492
+ logger.error(
493
+ f"Failed to extract schema for media type '{media_type_str}' "
494
+ f"in response {status_code}: {e}"
495
+ )
496
+
497
+ extracted_responses[str(status_code)] = resp_info
498
+ except ValueError as e:
499
+ # Re-raise ValueError for external reference errors
500
+ if "External or non-local reference not supported" in str(e):
501
+ raise
502
+ ref_name = getattr(resp_or_ref, "ref", "unknown")
503
+ logger.error(
504
+ f"Failed to extract response for status code {status_code} "
505
+ f"from reference '{ref_name}': {e}",
506
+ exc_info=False,
507
+ )
508
+ except Exception as e:
509
+ ref_name = getattr(resp_or_ref, "ref", "unknown")
510
+ logger.error(
511
+ f"Failed to extract response for status code {status_code} "
512
+ f"from reference '{ref_name}': {e}",
513
+ exc_info=False,
514
+ )
470
515
 
471
516
  return extracted_responses
472
517
 
518
+ def _extract_schema_dependencies(
519
+ self,
520
+ schema: dict,
521
+ all_schemas: dict[str, Any],
522
+ collected: set[str] | None = None,
523
+ ) -> set[str]:
524
+ """
525
+ Extract all schema names referenced by a schema (including transitive dependencies).
526
+
527
+ Args:
528
+ schema: The schema to analyze
529
+ all_schemas: All available schema definitions
530
+ collected: Set of already collected schema names (for recursion)
531
+
532
+ Returns:
533
+ Set of schema names that are referenced
534
+ """
535
+ if collected is None:
536
+ collected = set()
537
+
538
+ def find_refs(obj):
539
+ """Recursively find all $ref references."""
540
+ if isinstance(obj, dict):
541
+ if "$ref" in obj and isinstance(obj["$ref"], str):
542
+ ref = obj["$ref"]
543
+ # Handle both converted and unconverted refs
544
+ if ref.startswith("#/$defs/"):
545
+ schema_name = ref.split("/")[-1]
546
+ elif ref.startswith("#/components/schemas/"):
547
+ schema_name = ref.split("/")[-1]
548
+ else:
549
+ return
550
+
551
+ # Add this schema and recursively find its dependencies
552
+ if (
553
+ collected is not None
554
+ and schema_name not in collected
555
+ and schema_name in all_schemas
556
+ ):
557
+ collected.add(schema_name)
558
+ # Recursively find dependencies of this schema
559
+ find_refs(all_schemas[schema_name])
560
+
561
+ # Continue searching in all values
562
+ for value in obj.values():
563
+ find_refs(value)
564
+ elif isinstance(obj, list):
565
+ for item in obj:
566
+ find_refs(item)
567
+
568
+ find_refs(schema)
569
+ return collected
570
+
571
+ def _extract_input_schema_dependencies(
572
+ self,
573
+ parameters: list[ParameterInfo],
574
+ request_body: RequestBodyInfo | None,
575
+ all_schemas: dict[str, Any],
576
+ ) -> dict[str, Any]:
577
+ """
578
+ Extract only the schema definitions needed for input (parameters and request body).
579
+
580
+ Args:
581
+ parameters: Route parameters
582
+ request_body: Route request body
583
+ all_schemas: All available schema definitions
584
+
585
+ Returns:
586
+ Dictionary containing only the schemas needed for input
587
+ """
588
+ needed_schemas = set()
589
+
590
+ # Check parameters for schema references
591
+ for param in parameters:
592
+ if param.schema_:
593
+ deps = self._extract_schema_dependencies(param.schema_, all_schemas)
594
+ needed_schemas.update(deps)
595
+
596
+ # Check request body for schema references
597
+ if request_body and request_body.content_schema:
598
+ for content_schema in request_body.content_schema.values():
599
+ deps = self._extract_schema_dependencies(content_schema, all_schemas)
600
+ needed_schemas.update(deps)
601
+
602
+ # Return only the needed input schemas
603
+ return {
604
+ name: all_schemas[name] for name in needed_schemas if name in all_schemas
605
+ }
606
+
607
+ def _extract_output_schema_dependencies(
608
+ self,
609
+ responses: dict[str, ResponseInfo],
610
+ all_schemas: dict[str, Any],
611
+ ) -> dict[str, Any]:
612
+ """
613
+ Extract only the schema definitions needed for outputs (responses).
614
+
615
+ Args:
616
+ responses: Route responses
617
+ all_schemas: All available schema definitions
618
+
619
+ Returns:
620
+ Dictionary containing only the schemas needed for outputs
621
+ """
622
+ needed_schemas = set()
623
+
624
+ # Check responses for schema references
625
+ for response in responses.values():
626
+ if response.content_schema:
627
+ for content_schema in response.content_schema.values():
628
+ deps = self._extract_schema_dependencies(
629
+ content_schema, all_schemas
630
+ )
631
+ needed_schemas.update(deps)
632
+
633
+ # Return only the needed output schemas
634
+ return {
635
+ name: all_schemas[name] for name in needed_schemas if name in all_schemas
636
+ }
637
+
473
638
  def parse(self) -> list[HTTPRoute]:
474
639
  """Parse the OpenAPI schema into HTTP routes."""
475
640
  routes: list[HTTPRoute] = []
@@ -499,6 +664,13 @@ class OpenAPIParser(
499
664
  f"Failed to extract schema definition '{name}': {e}"
500
665
  )
501
666
 
667
+ # Convert schema definitions refs from OpenAPI to JSON Schema format (once)
668
+ if schema_definitions:
669
+ # Convert each schema definition recursively
670
+ for name, schema in schema_definitions.items():
671
+ if isinstance(schema, dict):
672
+ schema_definitions[name] = _replace_ref_with_defs(schema)
673
+
502
674
  # Process paths and operations
503
675
  for path_str, path_item_obj in self.openapi.paths.items():
504
676
  if not isinstance(path_item_obj, self.path_item_cls):
@@ -552,6 +724,17 @@ class OpenAPIParser(
552
724
  if k.startswith("x-")
553
725
  }
554
726
 
727
+ # Extract schemas separately for input and output
728
+ input_schemas = self._extract_input_schema_dependencies(
729
+ parameters,
730
+ request_body_info,
731
+ schema_definitions,
732
+ )
733
+ output_schemas = self._extract_output_schema_dependencies(
734
+ responses,
735
+ schema_definitions,
736
+ )
737
+
555
738
  # Create initial route without pre-calculated fields
556
739
  route = HTTPRoute(
557
740
  path=path_str,
@@ -563,7 +746,8 @@ class OpenAPIParser(
563
746
  parameters=parameters,
564
747
  request_body=request_body_info,
565
748
  responses=responses,
566
- schema_definitions=schema_definitions,
749
+ request_schemas=input_schemas,
750
+ response_schemas=output_schemas,
567
751
  extensions=extensions,
568
752
  openapi_version=self.openapi_version,
569
753
  )
@@ -571,7 +755,8 @@ class OpenAPIParser(
571
755
  # Pre-calculate schema and parameter mapping for performance
572
756
  try:
573
757
  flat_schema, param_map = _combine_schemas_and_map_params(
574
- route
758
+ route,
759
+ convert_refs=False, # Parser already converted refs
575
760
  )
576
761
  route.flat_param_schema = flat_schema
577
762
  route.parameter_map = param_map
@@ -586,9 +771,6 @@ class OpenAPIParser(
586
771
  }
587
772
  route.parameter_map = {}
588
773
  routes.append(route)
589
- logger.info(
590
- f"Successfully extracted route: {method_upper} {path_str}"
591
- )
592
774
  except ValueError as op_error:
593
775
  # Re-raise ValueError for external reference errors
594
776
  if "External or non-local reference not supported" in str(
@@ -607,7 +789,7 @@ class OpenAPIParser(
607
789
  exc_info=True,
608
790
  )
609
791
 
610
- logger.info(f"Finished parsing. Extracted {len(routes)} HTTP routes.")
792
+ logger.debug(f"Finished parsing. Extracted {len(routes)} HTTP routes.")
611
793
  return routes
612
794
 
613
795