openapi-to-mcp-cli 0.9.2__tar.gz → 0.9.3__tar.gz

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 (225) hide show
  1. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/PKG-INFO +7 -3
  2. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/README.md +6 -2
  3. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/generator.py +1 -0
  4. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/generation_service.py +1 -0
  5. openapi_to_mcp_cli-0.9.3/openapi_to_mcp/common/spec_compatibility.py +152 -0
  6. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/doctor/analyzer.py +9 -6
  7. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/doctor/security.py +6 -4
  8. openapi_to_mcp_cli-0.9.3/openapi_to_mcp/doctor/swagger.py +38 -0
  9. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/mapper.py +65 -16
  10. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/output_schema.py +9 -2
  11. openapi_to_mcp_cli-0.9.3/openapi_to_mcp/policy/swagger.py +64 -0
  12. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/converter.py +4 -2
  13. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/array_schema.py +2 -3
  14. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/base.py +9 -1
  15. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/common.py +2 -0
  16. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/composition.py +2 -2
  17. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/number_schema.py +2 -3
  18. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/object_schema.py +2 -3
  19. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/reference.py +2 -0
  20. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/string_schema.py +2 -2
  21. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/README.md.j2 +9 -0
  22. openapi_to_mcp_cli-0.9.3/openapi_to_mcp/templates/src/runtime/query.ts.j2 +106 -0
  23. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/request.ts.j2 +15 -1
  24. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/serialization.ts.j2 +30 -4
  25. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/pyproject.toml +1 -1
  26. openapi_to_mcp_cli-0.9.3/scripts/assert_container_server.py +43 -0
  27. openapi_to_mcp_cli-0.9.3/scripts/e2e_container.sh +39 -0
  28. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_policy_changes.py +3 -1
  29. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/workflow.sh +2 -2
  30. openapi_to_mcp_cli-0.9.3/tests/integration/query_serialization_spec.py +81 -0
  31. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli.py +120 -0
  32. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_doctor.py +33 -0
  33. openapi_to_mcp_cli-0.9.3/tests/integration/test_cli_policy_errors.py +106 -0
  34. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_runtime_validation.py +41 -0
  35. openapi_to_mcp_cli-0.9.3/tests/integration/test_query_serialization.py +131 -0
  36. openapi_to_mcp_cli-0.9.3/tests/integration/test_swagger_policy_compatibility.py +199 -0
  37. openapi_to_mcp_cli-0.9.3/tests/unit/test_doctor_analyzer.py +259 -0
  38. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_generator_runtime_files.py +2 -0
  39. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper.py +98 -0
  40. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper_output_schema.py +32 -0
  41. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper_request_body_contracts.py +31 -0
  42. openapi_to_mcp_cli-0.9.3/tests/unit/test_mapper_tool_naming.py +63 -0
  43. openapi_to_mcp_cli-0.9.3/tests/unit/test_release_container_reference.py +30 -0
  44. openapi_to_mcp_cli-0.9.3/tests/unit/test_release_preflight.py +36 -0
  45. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_schema_converter.py +67 -0
  46. openapi_to_mcp_cli-0.9.3/tests/unit/test_swagger_compatibility.py +115 -0
  47. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/uv.lock +1 -1
  48. openapi_to_mcp_cli-0.9.2/tests/unit/test_doctor_analyzer.py +0 -112
  49. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/.gitignore +0 -0
  50. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/LICENSE +0 -0
  51. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/__init__.py +0 -0
  52. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/__init__.py +0 -0
  53. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/spec_loader.py +0 -0
  54. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/__init__.py +0 -0
  55. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/http_redirects.py +0 -0
  56. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/models.py +0 -0
  57. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/response_formatting.py +0 -0
  58. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/server_tester.py +0 -0
  59. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/stdio_transport.py +0 -0
  60. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/adapters/testing/streamable_http_transport.py +0 -0
  61. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/cli.py +0 -0
  62. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/__init__.py +0 -0
  63. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/diff.py +0 -0
  64. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/doctor.py +0 -0
  65. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/generate.py +0 -0
  66. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/generation_context.py +0 -0
  67. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/generation_models.py +0 -0
  68. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/options.py +0 -0
  69. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/policy_support.py +0 -0
  70. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/run.py +0 -0
  71. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/run_support.py +0 -0
  72. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/runtime_overrides.py +0 -0
  73. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/commands/test_server.py +0 -0
  74. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/__init__.py +0 -0
  75. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/decorators.py +0 -0
  76. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/error_policy.py +0 -0
  77. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/exceptions.py +0 -0
  78. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/logger.py +0 -0
  79. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/performance_presets.py +0 -0
  80. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/terminal.py +0 -0
  81. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/tool_runtime.py +0 -0
  82. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/common/utils.py +0 -0
  83. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/diff/__init__.py +0 -0
  84. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/diff/analyzer.py +0 -0
  85. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/diff/models.py +0 -0
  86. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/diff/surface.py +0 -0
  87. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/doctor/__init__.py +0 -0
  88. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/doctor/models.py +0 -0
  89. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/__init__.py +0 -0
  90. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/tool_description.py +0 -0
  91. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/tool_examples.py +0 -0
  92. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/tool_grouping.py +0 -0
  93. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/mapping/utils.py +0 -0
  94. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/__init__.py +0 -0
  95. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/applier.py +0 -0
  96. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/loader.py +0 -0
  97. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/models.py +0 -0
  98. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/parser.py +0 -0
  99. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/settings.py +0 -0
  100. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/policy/validators.py +0 -0
  101. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/__init__.py +0 -0
  102. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/schema/handlers/__init__.py +0 -0
  103. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/.env.example.j2 +0 -0
  104. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/package.json.j2 +0 -0
  105. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/custom/tools.ts.j2 +0 -0
  106. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/index.ts.j2 +0 -0
  107. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/audit.ts.j2 +0 -0
  108. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/auth.ts.j2 +0 -0
  109. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/cache.ts.j2 +0 -0
  110. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/circuit_breaker.ts.j2 +0 -0
  111. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/config.ts.j2 +0 -0
  112. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/errors.ts.j2 +0 -0
  113. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/executor.ts.j2 +0 -0
  114. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/executor_support.ts.j2 +0 -0
  115. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/generated.ts.j2 +0 -0
  116. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/http_transport.ts.j2 +0 -0
  117. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/limiter.ts.j2 +0 -0
  118. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/observability.ts.j2 +0 -0
  119. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/performance_preset.ts.j2 +0 -0
  120. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/rate_limit.ts.j2 +0 -0
  121. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/redaction.ts.j2 +0 -0
  122. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/request_context.ts.j2 +0 -0
  123. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/resilience.ts.j2 +0 -0
  124. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/response.ts.j2 +0 -0
  125. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/retry.ts.j2 +0 -0
  126. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/tool_access.ts.j2 +0 -0
  127. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/runtime/validation.ts.j2 +0 -0
  128. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/server.ts.j2 +0 -0
  129. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/transport_stdio.ts.j2 +0 -0
  130. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/src/transport_streamable_http.ts.j2 +0 -0
  131. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/openapi_to_mcp/templates/tsconfig.json.j2 +0 -0
  132. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server.py +0 -0
  133. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_access.py +0 -0
  134. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_audit.py +0 -0
  135. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_cached_audit.py +0 -0
  136. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_common.py +0 -0
  137. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_performance.py +0 -0
  138. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_resilience.py +0 -0
  139. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_generated_server_suites.py +0 -0
  140. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/assert_runtime_observability.py +0 -0
  141. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/detect_release.py +0 -0
  142. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/e2e_cli_matrix.sh +0 -0
  143. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/e2e_generated_server.sh +0 -0
  144. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/mock_target_api.py +0 -0
  145. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_automation.py +0 -0
  146. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_contracts.py +0 -0
  147. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_metadata.py +0 -0
  148. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_policy.py +0 -0
  149. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_policy_config.py +0 -0
  150. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/scripts/release_policy_lock.py +0 -0
  151. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/__init__.py +0 -0
  152. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/conftest.py +0 -0
  153. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/constants.py +0 -0
  154. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/__init__.py +0 -0
  155. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/cli_generation_specs.py +0 -0
  156. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_audit_controls.py +0 -0
  157. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_customization.py +0 -0
  158. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_diff.py +0 -0
  159. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_diff_behavior.py +0 -0
  160. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_generate_contracts.py +0 -0
  161. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_generation_outputs.py +0 -0
  162. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_observability.py +0 -0
  163. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_output_schema.py +0 -0
  164. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_performance_controls.py +0 -0
  165. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_policy_config.py +0 -0
  166. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_policy_execution.py +0 -0
  167. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_resilience_controls.py +0 -0
  168. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_tool_access.py +0 -0
  169. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_tool_grouping.py +0 -0
  170. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_tool_shaping.py +0 -0
  171. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/integration/test_cli_xquik_fixture.py +0 -0
  172. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/audit_openapi.yaml +0 -0
  173. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/auth_openapi.yaml +0 -0
  174. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/release_contracts/auth.yaml +0 -0
  175. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/release_contracts/basic.yaml +0 -0
  176. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/test_openapi.json +0 -0
  177. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/resources/test_openapi.yaml +0 -0
  178. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/__init__.py +0 -0
  179. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_assertion_script_requests.py +0 -0
  180. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_assertion_wire_metadata.py +0 -0
  181. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_cli_help.py +0 -0
  182. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_generate.py +0 -0
  183. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run.py +0 -0
  184. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_access_env.py +0 -0
  185. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_audit_env.py +0 -0
  186. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_env.py +0 -0
  187. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_errors.py +0 -0
  188. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_performance_env.py +0 -0
  189. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_run_resilience_env.py +0 -0
  190. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_test_server.py +0 -0
  191. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_commands_test_server_contracts.py +0 -0
  192. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_common_logger.py +0 -0
  193. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_common_utils.py +0 -0
  194. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_decorators.py +0 -0
  195. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_diff_analyzer.py +0 -0
  196. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_diff_surface.py +0 -0
  197. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_generator.py +0 -0
  198. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_generator_transport.py +0 -0
  199. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper_output_schema_selection.py +0 -0
  200. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper_robustness_contracts.py +0 -0
  201. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapper_tool_shaping.py +0 -0
  202. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_mapping_utils.py +0 -0
  203. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_performance_presets.py +0 -0
  204. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_policy_applier.py +0 -0
  205. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_policy_loader.py +0 -0
  206. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_automation.py +0 -0
  207. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_contracts.py +0 -0
  208. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_detection.py +0 -0
  209. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_metadata.py +0 -0
  210. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_policy.py +0 -0
  211. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_release_workflow.py +0 -0
  212. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_response_formatting.py +0 -0
  213. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_schema_handlers_base.py +0 -0
  214. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_schema_handlers_composition.py +0 -0
  215. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_schema_handlers_number.py +0 -0
  216. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_server_tester.py +0 -0
  217. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_server_tester_stdio.py +0 -0
  218. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_server_tester_streamable_contracts.py +0 -0
  219. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_spec_loader.py +0 -0
  220. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_streamable_http_metadata.py +0 -0
  221. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_streamable_http_redirects.py +0 -0
  222. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_tool_description.py +0 -0
  223. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_tool_grouping.py +0 -0
  224. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/unit/test_tool_runtime.py +0 -0
  225. {openapi_to_mcp_cli-0.9.2 → openapi_to_mcp_cli-0.9.3}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: openapi-to-mcp-cli
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Generate, run, and test Node.js/TypeScript MCP servers from OpenAPI specifications.
5
5
  Project-URL: Homepage, https://github.com/nihal1294/openapi-to-mcp
6
6
  Project-URL: Documentation, https://nihal1294.github.io/openapi-to-mcp/
@@ -75,8 +75,8 @@ Run directly from a spec:
75
75
 
76
76
  ```bash
77
77
  openapi-to-mcp run \
78
- --openapi-json https://petstore.swagger.io/v2/swagger.json \
79
- --target-api-base-url https://petstore.swagger.io/v2
78
+ --openapi-json https://petstore3.swagger.io/api/v3/openapi.json \
79
+ --target-api-base-url https://petstore3.swagger.io/api/v3
80
80
  ```
81
81
 
82
82
  Smoke-test a running server:
@@ -116,6 +116,10 @@ Public product documentation lives on GitHub Pages:
116
116
  - CLI E2E coverage for `generate`, `run`, and `test-server`
117
117
  - version-aware PyPI and GitHub Releases automation on `master`
118
118
 
119
+ ## Swagger 2 support
120
+
121
+ Swagger 2 response-only operations and base URL fields are supported. Convert operations that use Swagger 2 body or form parameters, top-level parameter types, security definitions, or unsupported protocol overrides to OpenAPI 3 before generating. Run `openapi-to-mcp doctor` to identify the affected operation and field.
122
+
119
123
  ## Development
120
124
 
121
125
  For repository-local workflows, use the repo docs directly:
@@ -45,8 +45,8 @@ Run directly from a spec:
45
45
 
46
46
  ```bash
47
47
  openapi-to-mcp run \
48
- --openapi-json https://petstore.swagger.io/v2/swagger.json \
49
- --target-api-base-url https://petstore.swagger.io/v2
48
+ --openapi-json https://petstore3.swagger.io/api/v3/openapi.json \
49
+ --target-api-base-url https://petstore3.swagger.io/api/v3
50
50
  ```
51
51
 
52
52
  Smoke-test a running server:
@@ -86,6 +86,10 @@ Public product documentation lives on GitHub Pages:
86
86
  - CLI E2E coverage for `generate`, `run`, and `test-server`
87
87
  - version-aware PyPI and GitHub Releases automation on `master`
88
88
 
89
+ ## Swagger 2 support
90
+
91
+ Swagger 2 response-only operations and base URL fields are supported. Convert operations that use Swagger 2 body or form parameters, top-level parameter types, security definitions, or unsupported protocol overrides to OpenAPI 3 before generating. Run `openapi-to-mcp doctor` to identify the affected operation and field.
92
+
89
93
  ## Development
90
94
 
91
95
  For repository-local workflows, use the repo docs directly:
@@ -25,6 +25,7 @@ RUNTIME_TEMPLATE_NAMES = (
25
25
  "limiter",
26
26
  "observability",
27
27
  "performance_preset",
28
+ "query",
28
29
  "rate_limit",
29
30
  "resilience",
30
31
  "retry",
@@ -65,6 +65,7 @@ def _map_tools(
65
65
  strict=behavior.strict,
66
66
  on_mapping_error=behavior.on_mapping_error,
67
67
  on_schema_error=behavior.on_schema_error,
68
+ policy_config=request.policy_config,
68
69
  )
69
70
  tools = apply_policy(mapper.map_tools(), request.policy_config)
70
71
  tools = apply_tool_grouping(tools, behavior.tool_grouping)
@@ -0,0 +1,152 @@
1
+ """Compatibility checks shared by Swagger 2 diagnostics and mapping."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SwaggerFinding:
11
+ """One Swagger 2 construct that cannot be represented by the mapper."""
12
+
13
+ field: str
14
+ location: str
15
+
16
+
17
+ def swagger_operation_findings(
18
+ spec: dict[str, Any],
19
+ method: str,
20
+ path: str,
21
+ path_item: dict[str, Any],
22
+ operation: dict[str, Any],
23
+ ) -> list[SwaggerFinding]:
24
+ """Return unsupported Swagger 2 constructs used by one operation."""
25
+ if spec.get("swagger") != "2.0":
26
+ return []
27
+ base = f"paths.{path}"
28
+ findings = _parameter_findings(
29
+ spec, path_item.get("parameters"), f"{base}.parameters"
30
+ )
31
+ findings.extend(
32
+ _parameter_findings(
33
+ spec, operation.get("parameters"), f"{base}.{method.lower()}.parameters"
34
+ )
35
+ )
36
+ findings.extend(_security_findings(spec, operation, base, method))
37
+ findings.extend(_scheme_findings(spec, operation, base, method))
38
+ return list(dict.fromkeys(findings))
39
+
40
+
41
+ def format_swagger_finding(finding: SwaggerFinding) -> str:
42
+ """Format one compatibility finding as an actionable mapping error."""
43
+ return (
44
+ f"unsupported Swagger 2 construct `{finding.field}` at `{finding.location}`; "
45
+ "convert this operation to OpenAPI 3 before generation."
46
+ )
47
+
48
+
49
+ def _parameter_findings(
50
+ spec: dict[str, Any], parameters: object, location: str
51
+ ) -> list[SwaggerFinding]:
52
+ if not isinstance(parameters, list):
53
+ return []
54
+ findings: list[SwaggerFinding] = []
55
+ for index, candidate in enumerate(parameters):
56
+ parameter = _resolve_parameter(spec, candidate)
57
+ field_location = f"{location}[{index}]"
58
+ if not isinstance(parameter, dict):
59
+ if isinstance(candidate, dict) and "$ref" in candidate:
60
+ findings.append(SwaggerFinding("$ref", f"{field_location}.$ref"))
61
+ continue
62
+ parameter_in = parameter.get("in")
63
+ if parameter_in in {"body", "formData"}:
64
+ findings.append(
65
+ SwaggerFinding(f"in: {parameter_in}", f"{field_location}.in")
66
+ )
67
+ if (
68
+ parameter_in in {"path", "query", "header", "cookie"}
69
+ and "type" in parameter
70
+ ):
71
+ findings.append(SwaggerFinding("type", f"{field_location}.type"))
72
+ return findings
73
+
74
+
75
+ def _resolve_parameter(
76
+ spec: dict[str, Any], candidate: object
77
+ ) -> dict[str, Any] | None:
78
+ if not isinstance(candidate, dict):
79
+ return None
80
+ current = candidate
81
+ for _ in range(20):
82
+ ref = current.get("$ref")
83
+ if ref is None:
84
+ return current
85
+ if not isinstance(ref, str):
86
+ return None
87
+ resolved = _resolve_local_ref(spec, ref)
88
+ if not isinstance(resolved, dict):
89
+ return None
90
+ current = resolved
91
+ return None
92
+
93
+
94
+ def _resolve_local_ref(spec: dict[str, Any], ref: str) -> object:
95
+ if not ref.startswith("#/"):
96
+ return None
97
+ current: object = spec
98
+ for token in ref[2:].split("/"):
99
+ if not isinstance(current, dict):
100
+ return None
101
+ current = current.get(token.replace("~1", "/").replace("~0", "~"))
102
+ return current
103
+
104
+
105
+ def _security_findings(
106
+ spec: dict[str, Any], operation: dict[str, Any], base: str, method: str
107
+ ) -> list[SwaggerFinding]:
108
+ location = (
109
+ f"{base}.{method.lower()}.security" if "security" in operation else "security"
110
+ )
111
+ requirements = (
112
+ operation.get("security") if "security" in operation else spec.get("security")
113
+ )
114
+ if not isinstance(requirements, list) or not requirements:
115
+ return []
116
+ definitions = spec.get("securityDefinitions")
117
+ names = {
118
+ name
119
+ for item in requirements
120
+ if isinstance(item, dict)
121
+ for name in item
122
+ if isinstance(name, str)
123
+ }
124
+ if not names:
125
+ return []
126
+ field = (
127
+ "securityDefinitions"
128
+ if isinstance(definitions, dict) and names & definitions.keys()
129
+ else "security"
130
+ )
131
+ return [SwaggerFinding(field, location)]
132
+
133
+
134
+ def _scheme_findings(
135
+ spec: dict[str, Any], operation: dict[str, Any], base: str, method: str
136
+ ) -> list[SwaggerFinding]:
137
+ selected_scheme = _document_scheme(spec)
138
+ if selected_scheme not in {"http", "https"}:
139
+ return [SwaggerFinding("schemes", "schemes[0]")]
140
+ if "schemes" not in operation:
141
+ return []
142
+ operation_schemes = operation.get("schemes")
143
+ if isinstance(operation_schemes, list) and selected_scheme in operation_schemes:
144
+ return []
145
+ return [SwaggerFinding("schemes", f"{base}.{method.lower()}.schemes")]
146
+
147
+
148
+ def _document_scheme(spec: dict[str, Any]) -> str:
149
+ schemes = spec.get("schemes")
150
+ if isinstance(schemes, list) and schemes and isinstance(schemes[0], str):
151
+ return schemes[0]
152
+ return "https"
@@ -10,6 +10,7 @@ from openapi_to_mcp.doctor.security import (
10
10
  referenced_scheme_names,
11
11
  security_schemes,
12
12
  )
13
+ from openapi_to_mcp.doctor.swagger import report_swagger_compatibility
13
14
  from openapi_to_mcp.mapping.utils import generate_tool_name
14
15
 
15
16
  _HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head", "trace"}
@@ -39,6 +40,7 @@ class DoctorAnalyzer:
39
40
  self._check_missing_operation_ids(report, operations)
40
41
  self._check_generated_name_collisions(report, operations)
41
42
  self._check_security(report, operations)
43
+ report_swagger_compatibility(report, self.spec, operations)
42
44
  self._check_schema_unions(report, operations)
43
45
  return report
44
46
 
@@ -123,12 +125,13 @@ class DoctorAnalyzer:
123
125
  operations: list[tuple[str, str, dict[str, Any]]],
124
126
  ) -> None:
125
127
  schemes = security_schemes(self.spec)
126
- self._check_security_requirements(
127
- report,
128
- source=self.spec.get("security", []),
129
- schemes=schemes,
130
- location="security",
131
- )
128
+ if any("security" not in operation for _, _, operation in operations):
129
+ self._check_security_requirements(
130
+ report,
131
+ source=self.spec.get("security", []),
132
+ schemes=schemes,
133
+ location="security",
134
+ )
132
135
  for method, path, operation in operations:
133
136
  security = operation.get("security")
134
137
  if security is None:
@@ -13,10 +13,12 @@ if TYPE_CHECKING:
13
13
  def security_schemes(spec: dict[str, Any]) -> dict[str, Any]:
14
14
  """Return normalized OpenAPI security scheme definitions."""
15
15
  components = spec.get("components", {})
16
- if not isinstance(components, dict):
17
- return {}
18
- schemes = components.get("securitySchemes", {})
19
- return schemes if isinstance(schemes, dict) else {}
16
+ if isinstance(components, dict):
17
+ schemes = components.get("securitySchemes")
18
+ if isinstance(schemes, dict):
19
+ return schemes
20
+ definitions = spec.get("securityDefinitions", {})
21
+ return definitions if isinstance(definitions, dict) else {}
20
22
 
21
23
 
22
24
  def referenced_scheme_names(source: object) -> list[str]:
@@ -0,0 +1,38 @@
1
+ """Swagger 2 compatibility diagnostics for doctor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from openapi_to_mcp.common.spec_compatibility import swagger_operation_findings
8
+
9
+ if TYPE_CHECKING:
10
+ from openapi_to_mcp.doctor.models import DoctorReport
11
+
12
+
13
+ def report_swagger_compatibility(
14
+ report: DoctorReport,
15
+ spec: dict[str, Any],
16
+ operations: list[tuple[str, str, dict[str, Any]]],
17
+ ) -> None:
18
+ """Add one diagnostic for each unique unsupported Swagger 2 construct."""
19
+ paths = spec.get("paths", {})
20
+ if not isinstance(paths, dict):
21
+ return
22
+ seen = set()
23
+ for method, path, operation in operations:
24
+ path_item = paths.get(path)
25
+ if not isinstance(path_item, dict):
26
+ continue
27
+ for finding in swagger_operation_findings(
28
+ spec, method, path, path_item, operation
29
+ ):
30
+ if finding in seen:
31
+ continue
32
+ seen.add(finding)
33
+ report.add_error(
34
+ "unsupported_swagger_construct",
35
+ f"Swagger 2 `{finding.field}` cannot be faithfully generated.",
36
+ finding.location,
37
+ "Convert this operation to OpenAPI 3 before generating an MCP server.",
38
+ )
@@ -1,10 +1,17 @@
1
1
  """Maps OpenAPI operations to MCP tool definitions."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
5
7
 
6
8
  from openapi_to_mcp.common import MappingError, SchemaError
7
9
  from openapi_to_mcp.common.error_policy import ErrorMode, resolve_error_mode
10
+ from openapi_to_mcp.common.exceptions import PolicyConfigError
11
+ from openapi_to_mcp.common.spec_compatibility import (
12
+ format_swagger_finding,
13
+ swagger_operation_findings,
14
+ )
8
15
  from openapi_to_mcp.mapping.output_schema import extract_output_schema
9
16
  from openapi_to_mcp.mapping.tool_description import build_tool_description
10
17
  from openapi_to_mcp.mapping.tool_examples import (
@@ -13,12 +20,16 @@ from openapi_to_mcp.mapping.tool_examples import (
13
20
  build_input_examples,
14
21
  )
15
22
  from openapi_to_mcp.mapping.utils import generate_tool_name
23
+ from openapi_to_mcp.policy.swagger import effective_swagger_findings
16
24
  from openapi_to_mcp.schema.converter import (
17
25
  SchemaConverter,
18
26
  openapi_schema_to_json_schema,
19
27
  )
20
28
  from openapi_to_mcp.schema.handlers.reference import ReferenceHandler
21
29
 
30
+ if TYPE_CHECKING:
31
+ from openapi_to_mcp.policy.models import PolicyConfig
32
+
22
33
  logger = logging.getLogger(__name__)
23
34
 
24
35
 
@@ -32,6 +43,7 @@ class Mapper:
32
43
  strict: bool = True,
33
44
  on_mapping_error: ErrorMode | None = None,
34
45
  on_schema_error: ErrorMode | None = None,
46
+ policy_config: PolicyConfig | None = None,
35
47
  ) -> None:
36
48
  """
37
49
  Initialize the mapper with the loaded OpenAPI spec.
@@ -48,6 +60,7 @@ class Mapper:
48
60
  raise MappingError(err_msg)
49
61
  self.spec = spec
50
62
  self.strict = strict
63
+ self.policy_config = policy_config
51
64
  self.on_mapping_error = resolve_error_mode(on_mapping_error, strict=strict)
52
65
  self.on_schema_error = resolve_error_mode(on_schema_error, strict=strict)
53
66
  self.mcp_tools: list[dict[str, Any]] = []
@@ -109,16 +122,42 @@ class Mapper:
109
122
  operation,
110
123
  merged_parameters,
111
124
  )
125
+ self._raise_if_unsupported_swagger(
126
+ path_item, operation, tool_definition
127
+ )
128
+ self._register_tool_name(
129
+ operation.get("operationId")
130
+ or generate_tool_name(method, path),
131
+ tool_definition["name"],
132
+ )
112
133
  self.mcp_tools.append(tool_definition)
113
134
  except SchemaError as exc:
114
135
  self._handle_schema_error(method, path, exc)
115
- except MappingError as exc:
116
- self._handle_mapping_error(method, path, exc)
136
+ except PolicyConfigError:
137
+ raise
117
138
  except Exception as exc: # noqa: BLE001
118
139
  self._handle_mapping_error(method, path, exc)
119
140
 
120
141
  return self.mcp_tools
121
142
 
143
+ def _raise_if_unsupported_swagger(
144
+ self,
145
+ path_item: dict[str, Any],
146
+ operation: dict[str, Any],
147
+ tool: dict[str, Any],
148
+ ) -> None:
149
+ """Raise when this operation needs unsupported Swagger 2 behavior."""
150
+ findings = swagger_operation_findings(
151
+ self.spec,
152
+ tool["_original_method"],
153
+ tool["_original_path"],
154
+ path_item,
155
+ operation,
156
+ )
157
+ findings = effective_swagger_findings(findings, tool, self.policy_config)
158
+ if findings:
159
+ raise MappingError(format_swagger_finding(findings[0]))
160
+
122
161
  def get_report(self) -> dict[str, Any]:
123
162
  """Get mapper warnings and skipped operations for generation report."""
124
163
  return {
@@ -285,13 +324,9 @@ class Mapper:
285
324
 
286
325
  return (name, location)
287
326
 
288
- def _ensure_unique_tool_name(self, candidate_name: str) -> str:
289
- """Ensure mapped tool names are unique."""
327
+ def _resolve_unique_tool_name(self, candidate_name: str) -> str:
328
+ """Return a unique tool name without reserving it."""
290
329
  if candidate_name not in self._used_tool_names:
291
- self._used_tool_names.add(candidate_name)
292
- self._tool_name_counts[candidate_name] = max(
293
- self._tool_name_counts.get(candidate_name, 0), 1
294
- )
295
330
  return candidate_name
296
331
 
297
332
  if self.on_mapping_error == "fail":
@@ -306,12 +341,26 @@ class Mapper:
306
341
  if deduped_name not in self._used_tool_names:
307
342
  break
308
343
 
309
- self._tool_name_counts[candidate_name] = count
310
- self._used_tool_names.add(deduped_name)
344
+ return deduped_name
345
+
346
+ def _register_tool_name(
347
+ self,
348
+ candidate_name: str,
349
+ tool_name: str,
350
+ ) -> None:
351
+ """Reserve a tool name after the operation has been accepted."""
352
+ self._used_tool_names.add(tool_name)
353
+ if tool_name == candidate_name:
354
+ self._tool_name_counts[candidate_name] = max(
355
+ self._tool_name_counts.get(candidate_name, 0), 1
356
+ )
357
+ return
358
+
359
+ suffix = tool_name.removeprefix(f"{candidate_name}_")
360
+ self._tool_name_counts[candidate_name] = int(suffix)
311
361
  self._diagnostics.append(
312
- f"Duplicate tool name '{candidate_name}' deduped to '{deduped_name}'."
362
+ f"Duplicate tool name '{candidate_name}' deduped to '{tool_name}'."
313
363
  )
314
- return deduped_name
315
364
 
316
365
  def _process_parameters(
317
366
  self, parameters: list[dict[str, Any]], input_schema: dict[str, Any]
@@ -399,7 +448,7 @@ class Mapper:
399
448
  return None
400
449
 
401
450
  primary_content_type: str | None = None
402
- body_schema_openapi: dict[str, Any] | None = None
451
+ body_schema_openapi: dict[str, Any] | bool | None = None
403
452
  primary_media: dict[str, Any] | None = None
404
453
 
405
454
  if "application/json" in content and isinstance(
@@ -419,7 +468,7 @@ class Mapper:
419
468
  primary_content_type,
420
469
  )
421
470
 
422
- if primary_content_type and isinstance(body_schema_openapi, dict):
471
+ if primary_content_type and isinstance(body_schema_openapi, (bool, dict)):
423
472
  body_schema_json = openapi_schema_to_json_schema(
424
473
  body_schema_openapi, self.spec, raise_on_error=True
425
474
  )
@@ -469,7 +518,7 @@ class Mapper:
469
518
  candidate_name = operation.get("operationId") or generate_tool_name(
470
519
  method, path
471
520
  )
472
- tool_name = self._ensure_unique_tool_name(candidate_name)
521
+ tool_name = self._resolve_unique_tool_name(candidate_name)
473
522
  description = build_tool_description(method, path, operation)
474
523
 
475
524
  input_schema: dict[str, Any] = {
@@ -117,6 +117,13 @@ def _supports_structured_output(schema: dict[str, Any]) -> bool:
117
117
  schema_type = schema.get("type")
118
118
  if schema_type == "object":
119
119
  return True
120
- if schema_type in {"array", "string", "number", "integer", "boolean"}:
120
+ if isinstance(schema_type, list):
121
+ return bool(schema_type) and all(item == "object" for item in schema_type)
122
+ if isinstance(schema_type, str):
121
123
  return False
122
- return any(key in schema for key in ("properties", "allOf"))
124
+ if "properties" in schema:
125
+ return True
126
+ all_of = schema.get("allOf")
127
+ return isinstance(all_of, list) and any(
128
+ isinstance(item, dict) and _supports_structured_output(item) for item in all_of
129
+ )
@@ -0,0 +1,64 @@
1
+ """Evaluate Swagger compatibility against policy-adjusted tool metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from openapi_to_mcp.policy.applier import apply_policy
8
+
9
+ if TYPE_CHECKING:
10
+ from openapi_to_mcp.common.spec_compatibility import SwaggerFinding
11
+ from openapi_to_mcp.policy.models import PolicyConfig
12
+
13
+
14
+ def effective_swagger_findings(
15
+ findings: list[SwaggerFinding],
16
+ tool: dict[str, Any],
17
+ policy: PolicyConfig | None,
18
+ ) -> list[SwaggerFinding]:
19
+ """Keep findings that remain relevant after tool selection and auth overrides."""
20
+ if not findings or policy is None:
21
+ return findings
22
+ selected = apply_policy([tool], policy)
23
+ if not selected:
24
+ return []
25
+ if _has_supported_security_metadata(selected[0]):
26
+ return [
27
+ finding
28
+ for finding in findings
29
+ if finding.field not in {"security", "securityDefinitions"}
30
+ ]
31
+ return findings
32
+
33
+
34
+ def _has_supported_security_metadata(tool: dict[str, Any]) -> bool:
35
+ requirements = tool.get("_original_security")
36
+ if requirements is None or requirements == []:
37
+ return True
38
+ schemes = tool.get("_original_security_schemes")
39
+ if not isinstance(requirements, list) or not isinstance(schemes, dict):
40
+ return False
41
+ return all(
42
+ isinstance(requirement, dict)
43
+ and all(_runtime_supports_scheme(schemes.get(name)) for name in requirement)
44
+ for requirement in requirements
45
+ )
46
+
47
+
48
+ def _runtime_supports_scheme(scheme: object) -> bool:
49
+ """Match the scheme types handled by the generated auth resolver."""
50
+ if not isinstance(scheme, dict):
51
+ return False
52
+ scheme_type = str(scheme.get("type", "")).lower()
53
+ if scheme_type == "http":
54
+ return str(scheme.get("scheme", "")).lower() == "bearer"
55
+ if scheme_type == "apikey":
56
+ location = scheme.get("in")
57
+ name = scheme.get("name")
58
+ return (
59
+ isinstance(location, str)
60
+ and location.lower() in {"header", "query", "cookie"}
61
+ and isinstance(name, str)
62
+ and bool(name.strip())
63
+ )
64
+ return scheme_type in {"oauth2", "openidconnect"}
@@ -87,7 +87,7 @@ class SchemaConverter:
87
87
 
88
88
  def convert(
89
89
  self,
90
- openapi_schema: dict[str, Any] | None,
90
+ openapi_schema: object,
91
91
  *,
92
92
  include_internal_markers: bool = False,
93
93
  ) -> dict[str, Any]:
@@ -101,6 +101,8 @@ class SchemaConverter:
101
101
  Returns:
102
102
  The converted JSON Schema.
103
103
  """
104
+ if isinstance(openapi_schema, bool):
105
+ return {} if openapi_schema else {"not": {}}
104
106
  if not isinstance(openapi_schema, dict):
105
107
  logger.debug(
106
108
  "Invalid schema input provided to converter (expected dict). Returning empty schema."
@@ -177,7 +179,7 @@ class SchemaConverter:
177
179
 
178
180
 
179
181
  def openapi_schema_to_json_schema(
180
- openapi_schema: dict[str, Any] | None,
182
+ openapi_schema: object,
181
183
  full_spec: dict[str, Any],
182
184
  *,
183
185
  raise_on_error: bool = False,
@@ -3,7 +3,7 @@
3
3
  import logging
4
4
  from typing import Any
5
5
 
6
- from openapi_to_mcp.schema.handlers.base import SchemaHandler
6
+ from openapi_to_mcp.schema.handlers.base import SchemaHandler, schema_has_type
7
7
 
8
8
  logger = logging.getLogger(__name__)
9
9
 
@@ -13,10 +13,9 @@ class ArraySchemaHandler(SchemaHandler):
13
13
 
14
14
  def can_handle(self, schema: dict[str, Any]) -> bool:
15
15
  """Check if this handler can process the given schema."""
16
- schema_type = schema.get("type")
17
16
  # Handle explicit type=array or schemas with 'items'
18
17
  return isinstance(schema, dict) and (
19
- schema_type == "array" or "items" in schema
18
+ schema_has_type(schema, "array") or "items" in schema
20
19
  )
21
20
 
22
21
  def handle(
@@ -8,6 +8,14 @@ logger = logging.getLogger(__name__)
8
8
  INTERNAL_CYCLIC_REFERENCE_MARKER = "_is_cyclic_reference"
9
9
 
10
10
 
11
+ def schema_has_type(schema: dict[str, Any], expected_type: str) -> bool:
12
+ """Return whether a schema permits the expected JSON Schema type."""
13
+ schema_type = schema.get("type")
14
+ return schema_type == expected_type or (
15
+ isinstance(schema_type, list) and expected_type in schema_type
16
+ )
17
+
18
+
11
19
  class SchemaConverterProtocol(Protocol):
12
20
  """Protocol defining the interface for SchemaConverter.
13
21
 
@@ -17,7 +25,7 @@ class SchemaConverterProtocol(Protocol):
17
25
 
18
26
  def convert(
19
27
  self,
20
- openapi_schema: dict[str, Any],
28
+ openapi_schema: object,
21
29
  *,
22
30
  include_internal_markers: bool = False,
23
31
  ) -> dict[str, Any]:
@@ -30,6 +30,8 @@ class CommonSchemaHandler(SchemaHandler):
30
30
  "description",
31
31
  "title",
32
32
  "default",
33
+ "const",
34
+ "enum",
33
35
  "example",
34
36
  "readOnly",
35
37
  "writeOnly",
@@ -29,12 +29,12 @@ class CompositionHandler(SchemaHandler):
29
29
  for comp_key in ["allOf", "oneOf", "anyOf", "not"]:
30
30
  comp_value = openapi_schema.get(comp_key)
31
31
 
32
- if comp_key == "not" and isinstance(comp_value, dict):
32
+ if comp_key == "not" and isinstance(comp_value, (bool, dict)):
33
33
  # 'not' applies to a single schema
34
34
  json_schema[comp_key] = self.converter.convert(comp_value)
35
35
  elif comp_key != "not" and isinstance(comp_value, list):
36
36
  # Others apply to an array of schemas
37
- valid_items = [s for s in comp_value if isinstance(s, dict)]
37
+ valid_items = [s for s in comp_value if isinstance(s, (bool, dict))]
38
38
  if valid_items:
39
39
  json_schema[comp_key] = [
40
40
  self.converter.convert(s) for s in valid_items