openapi-to-mcp-cli 0.9.3__tar.gz → 0.11.0__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 (242) hide show
  1. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/PKG-INFO +2 -2
  2. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/generator.py +19 -1
  3. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/tool_runtime.py +5 -1
  4. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/analyzer.py +7 -0
  5. openapi_to_mcp_cli-0.11.0/openapi_to_mcp/doctor/security.py +136 -0
  6. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/swagger.py +1 -1
  7. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/README.md.j2 +31 -0
  8. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/package.json.j2 +7 -2
  9. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/auth.ts.j2 +41 -3
  10. openapi_to_mcp_cli-0.11.0/openapi_to_mcp/templates/src/runtime/basic_auth.ts.j2 +36 -0
  11. openapi_to_mcp_cli-0.11.0/openapi_to_mcp/templates/src/runtime/health.ts.j2 +106 -0
  12. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/request.ts.j2 +6 -3
  13. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/server.ts.j2 +41 -0
  14. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/transport_streamable_http.ts.j2 +37 -11
  15. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/pyproject.toml +9 -9
  16. openapi_to_mcp_cli-0.11.0/scripts/assert_container_server.py +86 -0
  17. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_suites.py +5 -0
  18. openapi_to_mcp_cli-0.11.0/scripts/audit_generated_dependencies.py +121 -0
  19. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_generated_server.sh +4 -0
  20. openapi_to_mcp_cli-0.11.0/scripts/generated_dependency_audit.py +87 -0
  21. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/mock_target_api.py +16 -10
  22. openapi_to_mcp_cli-0.11.0/tests/integration/basic_auth_support.py +117 -0
  23. openapi_to_mcp_cli-0.11.0/tests/integration/http_health_support.py +127 -0
  24. openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_contracts.py +68 -0
  25. openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_header_conflicts.py +104 -0
  26. openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_requests.py +189 -0
  27. openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_unicode.py +30 -0
  28. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_doctor.py +2 -2
  29. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_generation_outputs.py +127 -0
  30. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_runtime_validation.py +2 -0
  31. openapi_to_mcp_cli-0.11.0/tests/integration/test_generated_dev_dependencies.py +52 -0
  32. openapi_to_mcp_cli-0.11.0/tests/integration/test_http_health.py +302 -0
  33. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_swagger_policy_compatibility.py +3 -1
  34. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/auth_openapi.yaml +16 -0
  35. openapi_to_mcp_cli-0.11.0/tests/resources/basic_auth_request.mjs +30 -0
  36. openapi_to_mcp_cli-0.11.0/tests/resources/http_health_lifecycle.mjs +36 -0
  37. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_doctor_analyzer.py +2 -2
  38. openapi_to_mcp_cli-0.11.0/tests/unit/test_doctor_auth_headers.py +102 -0
  39. openapi_to_mcp_cli-0.11.0/tests/unit/test_generated_dependency_audit.py +101 -0
  40. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator_runtime_files.py +2 -0
  41. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/uv.lock +67 -66
  42. openapi_to_mcp_cli-0.9.3/openapi_to_mcp/doctor/security.py +0 -71
  43. openapi_to_mcp_cli-0.9.3/scripts/assert_container_server.py +0 -43
  44. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/.gitignore +0 -0
  45. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/LICENSE +0 -0
  46. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/README.md +0 -0
  47. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/__init__.py +0 -0
  48. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/__init__.py +0 -0
  49. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/spec_loader.py +0 -0
  50. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/__init__.py +0 -0
  51. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/http_redirects.py +0 -0
  52. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/models.py +0 -0
  53. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/response_formatting.py +0 -0
  54. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/server_tester.py +0 -0
  55. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/stdio_transport.py +0 -0
  56. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/streamable_http_transport.py +0 -0
  57. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/cli.py +0 -0
  58. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/__init__.py +0 -0
  59. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/diff.py +0 -0
  60. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/doctor.py +0 -0
  61. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generate.py +0 -0
  62. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_context.py +0 -0
  63. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_models.py +0 -0
  64. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_service.py +0 -0
  65. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/options.py +0 -0
  66. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/policy_support.py +0 -0
  67. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/run.py +0 -0
  68. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/run_support.py +0 -0
  69. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/runtime_overrides.py +0 -0
  70. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/test_server.py +0 -0
  71. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/__init__.py +0 -0
  72. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/decorators.py +0 -0
  73. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/error_policy.py +0 -0
  74. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/exceptions.py +0 -0
  75. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/logger.py +0 -0
  76. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/performance_presets.py +0 -0
  77. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/spec_compatibility.py +0 -0
  78. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/terminal.py +0 -0
  79. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/utils.py +0 -0
  80. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/__init__.py +0 -0
  81. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/analyzer.py +0 -0
  82. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/models.py +0 -0
  83. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/surface.py +0 -0
  84. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/__init__.py +0 -0
  85. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/models.py +0 -0
  86. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/swagger.py +0 -0
  87. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/__init__.py +0 -0
  88. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/mapper.py +0 -0
  89. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/output_schema.py +0 -0
  90. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_description.py +0 -0
  91. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_examples.py +0 -0
  92. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_grouping.py +0 -0
  93. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/utils.py +0 -0
  94. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/__init__.py +0 -0
  95. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/applier.py +0 -0
  96. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/loader.py +0 -0
  97. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/models.py +0 -0
  98. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/parser.py +0 -0
  99. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/settings.py +0 -0
  100. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/validators.py +0 -0
  101. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/__init__.py +0 -0
  102. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/converter.py +0 -0
  103. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/__init__.py +0 -0
  104. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/array_schema.py +0 -0
  105. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/base.py +0 -0
  106. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/common.py +0 -0
  107. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/composition.py +0 -0
  108. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/number_schema.py +0 -0
  109. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/object_schema.py +0 -0
  110. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/reference.py +0 -0
  111. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/string_schema.py +0 -0
  112. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/.env.example.j2 +0 -0
  113. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/custom/tools.ts.j2 +0 -0
  114. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/index.ts.j2 +0 -0
  115. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/audit.ts.j2 +0 -0
  116. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/cache.ts.j2 +0 -0
  117. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/circuit_breaker.ts.j2 +0 -0
  118. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/config.ts.j2 +0 -0
  119. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/errors.ts.j2 +0 -0
  120. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/executor.ts.j2 +0 -0
  121. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/executor_support.ts.j2 +0 -0
  122. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/generated.ts.j2 +0 -0
  123. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/http_transport.ts.j2 +0 -0
  124. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/limiter.ts.j2 +0 -0
  125. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/observability.ts.j2 +0 -0
  126. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/performance_preset.ts.j2 +0 -0
  127. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/query.ts.j2 +0 -0
  128. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/rate_limit.ts.j2 +0 -0
  129. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/redaction.ts.j2 +0 -0
  130. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/request_context.ts.j2 +0 -0
  131. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/resilience.ts.j2 +0 -0
  132. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/response.ts.j2 +0 -0
  133. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/retry.ts.j2 +0 -0
  134. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/serialization.ts.j2 +0 -0
  135. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/tool_access.ts.j2 +0 -0
  136. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/runtime/validation.ts.j2 +0 -0
  137. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/transport_stdio.ts.j2 +0 -0
  138. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/tsconfig.json.j2 +0 -0
  139. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server.py +0 -0
  140. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_access.py +0 -0
  141. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_audit.py +0 -0
  142. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_cached_audit.py +0 -0
  143. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_common.py +0 -0
  144. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_performance.py +0 -0
  145. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_resilience.py +0 -0
  146. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_runtime_observability.py +0 -0
  147. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/detect_release.py +0 -0
  148. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_cli_matrix.sh +0 -0
  149. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_container.sh +0 -0
  150. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_automation.py +0 -0
  151. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_contracts.py +0 -0
  152. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_metadata.py +0 -0
  153. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy.py +0 -0
  154. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_changes.py +0 -0
  155. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_config.py +0 -0
  156. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_lock.py +0 -0
  157. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/workflow.sh +0 -0
  158. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/__init__.py +0 -0
  159. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/conftest.py +0 -0
  160. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/constants.py +0 -0
  161. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/__init__.py +0 -0
  162. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/cli_generation_specs.py +0 -0
  163. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/query_serialization_spec.py +0 -0
  164. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli.py +0 -0
  165. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_audit_controls.py +0 -0
  166. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_customization.py +0 -0
  167. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_diff.py +0 -0
  168. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_diff_behavior.py +0 -0
  169. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_generate_contracts.py +0 -0
  170. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_observability.py +0 -0
  171. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_output_schema.py +0 -0
  172. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_performance_controls.py +0 -0
  173. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_config.py +0 -0
  174. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_errors.py +0 -0
  175. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_execution.py +0 -0
  176. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_resilience_controls.py +0 -0
  177. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_access.py +0 -0
  178. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_grouping.py +0 -0
  179. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_shaping.py +0 -0
  180. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_xquik_fixture.py +0 -0
  181. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_query_serialization.py +0 -0
  182. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/audit_openapi.yaml +0 -0
  183. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/release_contracts/auth.yaml +0 -0
  184. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/release_contracts/basic.yaml +0 -0
  185. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/test_openapi.json +0 -0
  186. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/test_openapi.yaml +0 -0
  187. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/__init__.py +0 -0
  188. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_assertion_script_requests.py +0 -0
  189. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_assertion_wire_metadata.py +0 -0
  190. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_cli_help.py +0 -0
  191. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_generate.py +0 -0
  192. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run.py +0 -0
  193. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_access_env.py +0 -0
  194. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_audit_env.py +0 -0
  195. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_env.py +0 -0
  196. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_errors.py +0 -0
  197. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_performance_env.py +0 -0
  198. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_resilience_env.py +0 -0
  199. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_test_server.py +0 -0
  200. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_test_server_contracts.py +0 -0
  201. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_common_logger.py +0 -0
  202. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_common_utils.py +0 -0
  203. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_decorators.py +0 -0
  204. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_diff_analyzer.py +0 -0
  205. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_diff_surface.py +0 -0
  206. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator.py +0 -0
  207. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator_transport.py +0 -0
  208. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper.py +0 -0
  209. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_output_schema.py +0 -0
  210. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_output_schema_selection.py +0 -0
  211. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_request_body_contracts.py +0 -0
  212. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_robustness_contracts.py +0 -0
  213. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_tool_naming.py +0 -0
  214. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_tool_shaping.py +0 -0
  215. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapping_utils.py +0 -0
  216. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_performance_presets.py +0 -0
  217. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_policy_applier.py +0 -0
  218. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_policy_loader.py +0 -0
  219. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_automation.py +0 -0
  220. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_container_reference.py +0 -0
  221. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_contracts.py +0 -0
  222. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_detection.py +0 -0
  223. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_metadata.py +0 -0
  224. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_policy.py +0 -0
  225. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_preflight.py +0 -0
  226. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_workflow.py +0 -0
  227. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_response_formatting.py +0 -0
  228. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_converter.py +0 -0
  229. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_base.py +0 -0
  230. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_composition.py +0 -0
  231. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_number.py +0 -0
  232. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester.py +0 -0
  233. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester_stdio.py +0 -0
  234. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester_streamable_contracts.py +0 -0
  235. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_spec_loader.py +0 -0
  236. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_streamable_http_metadata.py +0 -0
  237. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_streamable_http_redirects.py +0 -0
  238. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_swagger_compatibility.py +0 -0
  239. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_description.py +0 -0
  240. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_grouping.py +0 -0
  241. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_runtime.py +0 -0
  242. {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/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.3
3
+ Version: 0.11.0
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/
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Code Generators
19
19
  Requires-Python: >=3.14
20
20
  Requires-Dist: click==8.5.0
21
21
  Requires-Dist: jinja2==3.1.6
22
- Requires-Dist: mcp==2.2.0
22
+ Requires-Dist: mcp==2.3.0
23
23
  Requires-Dist: openapi-spec-validator==0.9.0
24
24
  Requires-Dist: pyyaml==6.0.3
25
25
  Requires-Dist: requests==2.34.2
@@ -14,6 +14,7 @@ RUNTIME_TEMPLATE_NAMES = (
14
14
  "audit",
15
15
  "request_context",
16
16
  "auth",
17
+ "basic_auth",
17
18
  "cache",
18
19
  "circuit_breaker",
19
20
  "config",
@@ -36,6 +37,7 @@ RUNTIME_TEMPLATE_NAMES = (
36
37
  "tool_access",
37
38
  "validation",
38
39
  )
40
+ HTTP_RUNTIME_TEMPLATE_NAMES = ("health",)
39
41
  CUSTOM_TEMPLATE_NAMES = ("tools",)
40
42
 
41
43
 
@@ -119,6 +121,7 @@ class Generator:
119
121
  Raises:
120
122
  GenerationError: If template rendering or file writing fails.
121
123
  """
124
+ self._remove_unused_http_runtime_files()
122
125
  generated_templates = {
123
126
  "package.json.j2": self.output_path / "package.json",
124
127
  "tsconfig.json.j2": self.output_path / "tsconfig.json",
@@ -134,11 +137,26 @@ class Generator:
134
137
  for template_name, output_file in self._custom_templates().items():
135
138
  self._render_if_missing(template_name, output_file)
136
139
 
140
+ def _remove_unused_http_runtime_files(self) -> None:
141
+ """Remove generated HTTP-only runtime files before a stdio regeneration."""
142
+ if self.context.get("transport") == "streamable-http":
143
+ return
144
+ runtime_output = self.output_path / "src" / "runtime"
145
+ try:
146
+ for name in HTTP_RUNTIME_TEMPLATE_NAMES:
147
+ (runtime_output / f"{name}.ts").unlink(missing_ok=True)
148
+ except OSError as e:
149
+ err_msg = f"Failed to remove unused HTTP runtime files: {e}"
150
+ raise GenerationError(err_msg) from e
151
+
137
152
  def _runtime_templates(self) -> dict[str, Path]:
138
153
  runtime_output = self.output_path / "src" / "runtime"
154
+ template_names = RUNTIME_TEMPLATE_NAMES
155
+ if self.context.get("transport") == "streamable-http":
156
+ template_names += HTTP_RUNTIME_TEMPLATE_NAMES
139
157
  return {
140
158
  f"src/runtime/{name}.ts.j2": runtime_output / f"{name}.ts"
141
- for name in RUNTIME_TEMPLATE_NAMES
159
+ for name in template_names
142
160
  }
143
161
 
144
162
  def _custom_templates(self) -> dict[str, Path]:
@@ -64,7 +64,7 @@ def derive_auth_env_vars(
64
64
  if not isinstance(scheme_name, str) or not isinstance(scheme_def, dict):
65
65
  continue
66
66
  normalized = "".join(
67
- c if c.isalnum() else "_" for c in scheme_name.upper()
67
+ c if c.isascii() and c.isalnum() else "_" for c in scheme_name.upper()
68
68
  ).strip("_")
69
69
  normalized = "_".join(part for part in normalized.split("_") if part)
70
70
  if not normalized:
@@ -73,6 +73,10 @@ def derive_auth_env_vars(
73
73
  http_scheme = str(scheme_def.get("scheme", "")).lower()
74
74
  if scheme_type == "apikey":
75
75
  env_vars.add(f"AUTH_{normalized}_API_KEY")
76
+ elif scheme_type == "http" and http_scheme == "basic":
77
+ env_vars.update(
78
+ (f"AUTH_{normalized}_USERNAME", f"AUTH_{normalized}_PASSWORD")
79
+ )
76
80
  elif (scheme_type == "http" and http_scheme == "bearer") or scheme_type in {
77
81
  "oauth2",
78
82
  "openidconnect",
@@ -6,6 +6,7 @@ from typing import Any
6
6
 
7
7
  from openapi_to_mcp.doctor.models import DoctorReport
8
8
  from openapi_to_mcp.doctor.security import (
9
+ check_security_header_conflicts,
9
10
  check_security_scheme,
10
11
  referenced_scheme_names,
11
12
  security_schemes,
@@ -151,6 +152,12 @@ class DoctorAnalyzer:
151
152
  schemes: dict[str, Any],
152
153
  location: str,
153
154
  ) -> None:
155
+ check_security_header_conflicts(
156
+ report,
157
+ source=source,
158
+ schemes=schemes,
159
+ location=location,
160
+ )
154
161
  for scheme_name in referenced_scheme_names(source):
155
162
  check_security_scheme(
156
163
  report,
@@ -0,0 +1,136 @@
1
+ """Security-scheme diagnostics helpers for doctor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ _SUPPORTED_SECURITY_TYPES = {"apiKey", "oauth2", "openIdConnect"}
8
+
9
+ if TYPE_CHECKING:
10
+ from openapi_to_mcp.doctor.models import DoctorReport
11
+
12
+
13
+ def security_schemes(spec: dict[str, Any]) -> dict[str, Any]:
14
+ """Return normalized OpenAPI security scheme definitions."""
15
+ components = spec.get("components", {})
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 {}
22
+
23
+
24
+ def referenced_scheme_names(source: object) -> list[str]:
25
+ """Extract security scheme names from a requirement list."""
26
+ if not isinstance(source, list):
27
+ return []
28
+ names: list[str] = []
29
+ for requirement in source:
30
+ if not isinstance(requirement, dict):
31
+ continue
32
+ names.extend(name for name in requirement if isinstance(name, str))
33
+ return names
34
+
35
+
36
+ def check_security_header_conflicts(
37
+ report: DoctorReport,
38
+ *,
39
+ source: object,
40
+ schemes: dict[str, Any],
41
+ location: str,
42
+ ) -> None:
43
+ """Warn when one security requirement targets the same header twice."""
44
+ if not isinstance(source, list) or not isinstance(schemes, dict):
45
+ return
46
+ for requirement in source:
47
+ if not isinstance(requirement, dict) or not _valid_scope_values(requirement):
48
+ continue
49
+ headers: dict[str, list[str]] = {}
50
+ for scheme_name in requirement:
51
+ if not isinstance(scheme_name, str):
52
+ continue
53
+ header = _runtime_auth_header(schemes.get(scheme_name))
54
+ if header is None:
55
+ continue
56
+ headers.setdefault(header.lower(), []).append(scheme_name)
57
+ conflicts = [names for names in headers.values() if len(names) > 1]
58
+ if not conflicts:
59
+ continue
60
+ names = ", ".join(name for conflict in conflicts for name in conflict)
61
+ report.add_warning(
62
+ "conflicting_auth_headers",
63
+ f"Security requirement combines schemes `{names}` that target the same HTTP header.",
64
+ location,
65
+ "Distinct auth values cannot share one header; use security alternatives or distinct header locations.",
66
+ )
67
+
68
+
69
+ def _valid_scope_values(requirement: dict[object, object]) -> bool:
70
+ """Return whether a security requirement has valid scope-array values."""
71
+ return all(
72
+ isinstance(scopes, list) and all(isinstance(scope, str) for scope in scopes)
73
+ for scopes in requirement.values()
74
+ )
75
+
76
+
77
+ def _runtime_auth_header(scheme: object) -> str | None:
78
+ """Return the header targeted by a supported runtime auth scheme."""
79
+ if not isinstance(scheme, dict):
80
+ return None
81
+ scheme_type = scheme.get("type")
82
+ if scheme_type == "http":
83
+ http_scheme = scheme.get("scheme")
84
+ if isinstance(http_scheme, str) and http_scheme.lower() in {"basic", "bearer"}:
85
+ return "Authorization"
86
+ elif scheme_type in {"oauth2", "openIdConnect"}:
87
+ return "Authorization"
88
+ elif scheme_type == "apiKey":
89
+ location = scheme.get("in")
90
+ name = scheme.get("name")
91
+ if (
92
+ isinstance(location, str)
93
+ and location.lower() == "header"
94
+ and isinstance(name, str)
95
+ and name.strip()
96
+ ):
97
+ return name
98
+ return None
99
+
100
+
101
+ def check_security_scheme(
102
+ report: DoctorReport,
103
+ *,
104
+ name: str,
105
+ scheme: dict[str, Any] | None,
106
+ location: str,
107
+ ) -> None:
108
+ """Record a doctor issue for undefined or unsupported security schemes."""
109
+ if not isinstance(scheme, dict):
110
+ report.add_error(
111
+ "undefined_security_scheme",
112
+ f"Security requirement references undefined scheme `{name}`.",
113
+ location,
114
+ "Define the scheme under `components.securitySchemes`.",
115
+ )
116
+ return
117
+ scheme_type = scheme.get("type")
118
+ if scheme_type in _SUPPORTED_SECURITY_TYPES:
119
+ return
120
+ if scheme_type == "http":
121
+ http_scheme = scheme.get("scheme")
122
+ if isinstance(http_scheme, str) and http_scheme.lower() in {"bearer", "basic"}:
123
+ return
124
+ report.add_error(
125
+ "unsupported_http_auth",
126
+ f"Security scheme `{name}` uses unsupported HTTP scheme `{http_scheme}`.",
127
+ location,
128
+ "Use HTTP basic/bearer, apiKey, oauth2, or openIdConnect for generated runtime auth.",
129
+ )
130
+ return
131
+ report.add_error(
132
+ "unsupported_security_scheme",
133
+ f"Security scheme `{name}` uses unsupported type `{scheme_type}`.",
134
+ location,
135
+ "Use HTTP basic/bearer, apiKey, oauth2, or openIdConnect for generated runtime auth.",
136
+ )
@@ -51,7 +51,7 @@ def _runtime_supports_scheme(scheme: object) -> bool:
51
51
  return False
52
52
  scheme_type = str(scheme.get("type", "")).lower()
53
53
  if scheme_type == "http":
54
- return str(scheme.get("scheme", "")).lower() == "bearer"
54
+ return str(scheme.get("scheme", "")).lower() in {"bearer", "basic"}
55
55
  if scheme_type == "apikey":
56
56
  location = scheme.get("in")
57
57
  name = scheme.get("name")
@@ -81,6 +81,8 @@ The `conservative` preset leaves caching off by default, but if you later enable
81
81
  * **Runtime Validation:**
82
82
  * Generated tool input validation is set to `{{ runtime_validation }}`.
83
83
  * When set to `input`, tool arguments are validated against the generated `inputSchema` before the upstream API call.
84
+ * Regardless of this setting, a tool call may contain at most 10,000 nested argument elements. Larger inputs are rejected before schema validation or an upstream call with the standard JSON-RPC `Invalid Params` error (`-32602`). This is a structural count of object properties and array entries, not a byte limit for scalar strings. For `streamable-http`, Express separately rejects request bodies over 1 MiB.
85
+ * **Migration:** This guard has no environment override. For legitimate larger structured payloads, split work across tool calls or pass a bounded reference that the upstream API can retrieve.
84
86
  * **Structured Tool Errors:**
85
87
  * Tool failures return machine-readable metadata under `_meta.error`.
86
88
  * `_meta.error.retryable` is advisory only and does not define any retry interval or backoff policy.
@@ -92,6 +94,9 @@ The `conservative` preset leaves caching off by default, but if you later enable
92
94
  * **Streamable HTTP Settings (if transport is `streamable-http`):**
93
95
  * `MCP_HTTP_HOST`, `MCP_HTTP_PORT`, `MCP_HTTP_ENDPOINT`, `MCP_ALLOWED_ORIGINS`, `MCP_ALLOWED_HOSTS`
94
96
  * Invalid `TARGET_API_BASE_URL` values also fail fast at startup. Use an absolute `http` or `https` URL.
97
+ * The fixed `/healthz` and `/readyz` paths provide local probes. `GET /healthz` returns `200` with `{"status":"ok"}`; `GET /readyz` returns `200` with `{"status":"ready"}` once the listener is ready, or `503` with `{"status":"not_ready"}` while marked not ready. Before the listener binds, probes are unreachable.
98
+ * Probes set `Cache-Control: no-store`, use the same Host and Origin allowlists, and require no MCP session, tool identity, upstream credentials, or dependency availability. `HEAD` follows GET semantics; other methods return `405`.
99
+ * `/healthz` and `/readyz` are reserved paths, including case and trailing-slash equivalents. If `MCP_HTTP_ENDPOINT` equals or matches one of them, change it to a separate path such as `/mcp` and update clients before deploying; conflicts fail at startup. Probes stop succeeding when the server shuts down; readiness does not add a draining grace period or promise upstream availability.
95
100
 
96
101
  3. **Install Dependencies:**
97
102
  ```bash
@@ -119,6 +124,10 @@ To run the server in development mode with automatic rebuilding and restarting o
119
124
  npm run dev
120
125
  ```
121
126
 
127
+ The generated npm manifest uses a scoped override to keep nodemon's glob matcher
128
+ on a patched minimatch release. Preserve that override when updating development
129
+ dependencies until nodemon's own dependency range includes the fix.
130
+
122
131
  ## Safe customization
123
132
 
124
133
  - `src/custom/tools.ts` is the preserved entry point for user-owned MCP tools.
@@ -135,6 +144,28 @@ delimiters (`#`, `&`, `=`, `+`, `[`, and `]`) remain percent encoded so a value
135
144
  cannot create another query field or fragment. Parameter names continue to use
136
145
  safe standard encoding.
137
146
 
147
+ ## Upstream HTTP Basic authentication
148
+
149
+ For an OpenAPI 3 `type: http`, `scheme: basic` security scheme, set
150
+ `AUTH_<SCHEME>_USERNAME` and `AUTH_<SCHEME>_PASSWORD` in the server environment or
151
+ `.env`. Use the exact names listed in `.env.example`; for `BasicAuth`, they are
152
+ `AUTH_BASICAUTH_USERNAME` and `AUTH_BASICAUTH_PASSWORD`. These are upstream API
153
+ credentials and do not authenticate incoming MCP clients.
154
+
155
+ The username must be nonempty, and the password must be defined (an explicitly
156
+ empty password is supported). Spaces are preserved. Usernames cannot contain
157
+ colons, and neither value can contain ASCII control characters. Invalid or
158
+ missing values fail before an upstream request with `invalid_credentials` or
159
+ `missing_credentials` errors that name the affected variables without their values.
160
+
161
+ The runtime sends UTF-8 Base64 credentials in `Authorization: Basic`, taking
162
+ precedence over `TARGET_API_AUTH_HEADER`. It does not negotiate legacy encodings.
163
+ Use HTTPS upstream because Base64 does not encrypt credentials. Default audit
164
+ logging redacts the Authorization header.
165
+ Combined security schemes cannot send different values in the same HTTP header.
166
+ Conflicts (such as Basic and Bearer both requiring Authorization) fail with
167
+ `invalid_credentials`, unless another security alternative can be satisfied.
168
+
138
169
  ## MCP Tools
139
170
 
140
171
  This server exposes the following tools based on the OpenAPI specification:
@@ -20,8 +20,8 @@
20
20
  "node": ">=22"
21
21
  },
22
22
  "dependencies": {
23
- {% if transport == 'streamable-http' %}"@modelcontextprotocol/node": "^2.0.0",
24
- {% endif %}"@modelcontextprotocol/server": "^2.0.0",
23
+ {% if transport == 'streamable-http' %}"@modelcontextprotocol/node": "^2.1.2",
24
+ {% endif %}"@modelcontextprotocol/server": "^2.4.0",
25
25
  {%- if runtime_validation == 'input' %}
26
26
  "ajv": "^8.20.0",
27
27
  {%- endif %}
@@ -36,5 +36,10 @@
36
36
  "@types/node": "^22.20.4",
37
37
  "nodemon": "^3.1.14",
38
38
  "typescript": "^7.0.2"
39
+ },
40
+ "overrides": {
41
+ "nodemon": {
42
+ "minimatch": "^11.0.8"
43
+ }
39
44
  }
40
45
  }
@@ -1,3 +1,4 @@
1
+ import { resolveBasicCredentials } from './basic_auth.js';
1
2
  import type { ToolRuntimeMetadata } from './generated.js';
2
3
 
3
4
  export interface ResolvedAuth {
@@ -6,6 +7,7 @@ export interface ResolvedAuth {
6
7
  query: Record<string, unknown>;
7
8
  cookies: Record<string, string>;
8
9
  missing: string[];
10
+ invalid?: string[];
9
11
  }
10
12
 
11
13
  function normalizeSchemeName(name: string): string {
@@ -16,6 +18,22 @@ function normalizeSchemeName(name: string): string {
16
18
  .replace(/_+/g, '_');
17
19
  }
18
20
 
21
+ function mergeAuthHeaders(
22
+ headers: Record<string, string>,
23
+ values: Array<[string, string]>
24
+ ): string[] {
25
+ const conflicts: string[] = [];
26
+ for (const [name, value] of values) {
27
+ const existing = Object.keys(headers).find((key) => key.toLowerCase() === name.toLowerCase());
28
+ if (existing !== undefined && headers[existing] !== value) {
29
+ conflicts.push(`conflicting values for auth header ${name}`);
30
+ } else {
31
+ headers[existing ?? name] = value;
32
+ }
33
+ }
34
+ return conflicts;
35
+ }
36
+
19
37
  export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
20
38
  const securityRequirements = tool.security;
21
39
  const securitySchemes = tool.securitySchemes || {};
@@ -24,6 +42,7 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
24
42
  }
25
43
 
26
44
  const missingAcrossRequirements: Set<string> = new Set();
45
+ const invalidAcrossRequirements: Set<string> = new Set();
27
46
  for (const requirement of securityRequirements) {
28
47
  if (!requirement || typeof requirement !== 'object') {
29
48
  continue;
@@ -34,6 +53,7 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
34
53
  }
35
54
 
36
55
  const headers: Record<string, string> = {};
56
+ const headerValues: Array<[string, string]> = [];
37
57
  const query: Record<string, unknown> = {};
38
58
  const cookies: Record<string, string> = {};
39
59
  let satisfied = true;
@@ -63,8 +83,20 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
63
83
  } else if (apiKeyIn === 'cookie') {
64
84
  cookies[apiKeyName] = apiKey;
65
85
  } else {
66
- headers[apiKeyName] = apiKey;
86
+ headerValues.push([apiKeyName, apiKey]);
87
+ }
88
+ continue;
89
+ }
90
+
91
+ if (schemeType === 'http' && httpScheme === 'basic') {
92
+ const credentials = resolveBasicCredentials(normalizedScheme);
93
+ if (!credentials.authorization) {
94
+ satisfied = false;
95
+ credentials.missing.forEach((name) => missingAcrossRequirements.add(name));
96
+ credentials.invalid.forEach((issue) => invalidAcrossRequirements.add(issue));
97
+ break;
67
98
  }
99
+ headerValues.push(['Authorization', credentials.authorization]);
68
100
  continue;
69
101
  }
70
102
 
@@ -75,7 +107,7 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
75
107
  missingAcrossRequirements.add(`AUTH_${normalizedScheme}_TOKEN`);
76
108
  break;
77
109
  }
78
- headers.Authorization = `Bearer ${token}`;
110
+ headerValues.push(['Authorization', `Bearer ${token}`]);
79
111
  continue;
80
112
  }
81
113
 
@@ -86,7 +118,7 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
86
118
  missingAcrossRequirements.add(`AUTH_${normalizedScheme}_TOKEN`);
87
119
  break;
88
120
  }
89
- headers.Authorization = `Bearer ${token}`;
121
+ headerValues.push(['Authorization', `Bearer ${token}`]);
90
122
  continue;
91
123
  }
92
124
 
@@ -103,6 +135,11 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
103
135
  break;
104
136
  }
105
137
 
138
+ if (satisfied) {
139
+ const conflicts = mergeAuthHeaders(headers, headerValues);
140
+ conflicts.forEach((issue) => invalidAcrossRequirements.add(issue));
141
+ satisfied = conflicts.length === 0;
142
+ }
106
143
  if (satisfied) {
107
144
  return { ok: true, headers, query, cookies, missing: [] };
108
145
  }
@@ -114,5 +151,6 @@ export function resolveAuthForTool(tool: ToolRuntimeMetadata): ResolvedAuth {
114
151
  query: {},
115
152
  cookies: {},
116
153
  missing: [...missingAcrossRequirements],
154
+ invalid: [...invalidAcrossRequirements],
117
155
  };
118
156
  }
@@ -0,0 +1,36 @@
1
+ import { Buffer } from 'node:buffer';
2
+
3
+ interface BasicCredentials {
4
+ authorization?: string;
5
+ missing: string[];
6
+ invalid: string[];
7
+ }
8
+
9
+ /** Resolve upstream Basic credentials without including their values in errors. */
10
+ export function resolveBasicCredentials(normalizedScheme: string): BasicCredentials {
11
+ const usernameEnv = `AUTH_${normalizedScheme}_USERNAME`;
12
+ const passwordEnv = `AUTH_${normalizedScheme}_PASSWORD`;
13
+ const username = process.env[usernameEnv];
14
+ const password = process.env[passwordEnv];
15
+ const missing: string[] = [];
16
+ const invalid: string[] = [];
17
+ if (!username) missing.push(usernameEnv);
18
+ if (password === undefined) missing.push(passwordEnv);
19
+
20
+ // RFC 7617 forbids control characters in credentials and ':' in user IDs.
21
+ const controlCharacters = /[\x00-\x1f\x7f]/;
22
+ if (username && (username.includes(':') || controlCharacters.test(username))) {
23
+ invalid.push(`${usernameEnv} (must not contain colons or control characters)`);
24
+ }
25
+ if (password !== undefined && controlCharacters.test(password)) {
26
+ invalid.push(`${passwordEnv} (must not contain control characters)`);
27
+ }
28
+ if (missing.length > 0 || invalid.length > 0) {
29
+ return { missing, invalid };
30
+ }
31
+ return {
32
+ authorization: `Basic ${Buffer.from(`${username}:${password}`, 'utf8').toString('base64')}`,
33
+ missing,
34
+ invalid,
35
+ };
36
+ }
@@ -0,0 +1,106 @@
1
+ import express, { type Express, type Request, type Response } from 'express';
2
+
3
+ export const HEALTH_PATH = '/healthz';
4
+ export const READINESS_PATH = '/readyz';
5
+
6
+ export type Readiness = {
7
+ isReady: () => boolean;
8
+ markNotReady: () => void;
9
+ markReady: () => void;
10
+ };
11
+
12
+ type RequestGuard = (req: Request, res: Response) => boolean;
13
+
14
+ export function createReadiness(): Readiness {
15
+ let ready = false;
16
+
17
+ return {
18
+ isReady: () => ready,
19
+ markNotReady: () => {
20
+ ready = false;
21
+ },
22
+ markReady: () => {
23
+ ready = true;
24
+ },
25
+ };
26
+ }
27
+
28
+ export async function assertOperationalRouteSeparation(
29
+ endpoint: string
30
+ ): Promise<void> {
31
+ const probePaths = [HEALTH_PATH, READINESS_PATH].flatMap((path) => [path, `${path}/`]);
32
+ for (const path of probePaths) {
33
+ if (await endpointMatchesPath(endpoint, path)) {
34
+ throw new Error(
35
+ `Invalid MCP_HTTP_ENDPOINT '${endpoint}'. It conflicts with reserved operational route '${path}'.`
36
+ );
37
+ }
38
+ }
39
+ }
40
+
41
+ export function registerOperationalRoutes(
42
+ app: Express,
43
+ readiness: Readiness,
44
+ isRequestAllowed: RequestGuard
45
+ ): void {
46
+ registerOperationalRoute(app, HEALTH_PATH, isRequestAllowed, () => ({
47
+ status: 200,
48
+ body: { status: 'ok' },
49
+ }));
50
+ registerOperationalRoute(app, READINESS_PATH, isRequestAllowed, () => ({
51
+ status: readiness.isReady() ? 200 : 503,
52
+ body: { status: readiness.isReady() ? 'ready' : 'not_ready' },
53
+ }));
54
+ }
55
+
56
+ function registerOperationalRoute(
57
+ app: Express,
58
+ path: string,
59
+ isRequestAllowed: RequestGuard,
60
+ response: () => { status: number; body: { status: string } }
61
+ ): void {
62
+ app.all(path, (req, res) => {
63
+ res.set('Cache-Control', 'no-store');
64
+ if (!isRequestAllowed(req, res)) {
65
+ return;
66
+ }
67
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
68
+ res.set('Allow', 'GET, HEAD').status(405).json({ error: 'Method not allowed' });
69
+ return;
70
+ }
71
+ const { status, body } = response();
72
+ res.status(status).json(body);
73
+ });
74
+ }
75
+
76
+ async function endpointMatchesPath(endpoint: string, path: string): Promise<boolean> {
77
+ const matcher = express();
78
+ let matched = false;
79
+ matcher.all(endpoint, (_req, _res, next) => {
80
+ matched = true;
81
+ next();
82
+ });
83
+
84
+ const request = {
85
+ baseUrl: '',
86
+ headers: {},
87
+ method: 'GET',
88
+ originalUrl: path,
89
+ url: path,
90
+ } as Request;
91
+ const response = {
92
+ setHeader: () => undefined,
93
+ } as unknown as Response;
94
+
95
+ await new Promise<void>((resolve, reject) => {
96
+ matcher(request, response, (error?: unknown) => {
97
+ if (error) {
98
+ reject(error);
99
+ return;
100
+ }
101
+ resolve();
102
+ });
103
+ });
104
+
105
+ return matched;
106
+ }
@@ -142,11 +142,14 @@ function applyAuthContext(
142
142
  ): string[] {
143
143
  const resolvedAuth = resolveAuthForTool(tool);
144
144
  if (!resolvedAuth.ok) {
145
+ const invalid = resolvedAuth.invalid || [];
145
146
  throw createToolExecutionError(
146
- 'missing_credentials',
147
+ invalid.length > 0 ? 'invalid_credentials' : 'missing_credentials',
147
148
  'auth',
148
149
  false,
149
- `Missing credentials for required security scheme(s): ${resolvedAuth.missing.join(', ')}`
150
+ invalid.length > 0
151
+ ? `Invalid credentials for required security scheme(s): ${invalid.join(', ')}${resolvedAuth.missing.length > 0 ? `; missing: ${resolvedAuth.missing.join(', ')}` : ''}`
152
+ : `Missing credentials for required security scheme(s): ${resolvedAuth.missing.join(', ')}`
150
153
  );
151
154
  }
152
155
  Object.assign(headers, resolvedAuth.headers);
@@ -157,7 +160,7 @@ function applyAuthContext(
157
160
  }
158
161
 
159
162
  function applyDefaultAuthHeader(headers: Record<string, string>): void {
160
- if (!TARGET_API_AUTH_HEADER || headers.Authorization) {
163
+ if (!TARGET_API_AUTH_HEADER || Object.keys(headers).some((name) => name.toLowerCase() === 'authorization')) {
161
164
  return;
162
165
  }
163
166
  const separatorIndex = TARGET_API_AUTH_HEADER.indexOf(':');
@@ -21,6 +21,46 @@ import {
21
21
  filterToolsForCaller,
22
22
  } from './runtime/tool_access.js';
23
23
 
24
+ const MAX_TOOL_ARGUMENT_ELEMENTS = 10_000;
25
+
26
+ function assertToolArgumentBounds(args: Record<string, unknown>): void {
27
+ const pending: unknown[] = [args];
28
+ let elementCount = 0;
29
+
30
+ while (pending.length > 0) {
31
+ const current = pending.pop();
32
+ if (Array.isArray(current)) {
33
+ for (const value of current) {
34
+ elementCount += 1;
35
+ if (elementCount > MAX_TOOL_ARGUMENT_ELEMENTS) {
36
+ throw new ProtocolError(
37
+ ProtocolErrorCode.InvalidParams,
38
+ `Tool arguments exceed the ${MAX_TOOL_ARGUMENT_ELEMENTS} element limit.`
39
+ );
40
+ }
41
+ pending.push(value);
42
+ }
43
+ continue;
44
+ }
45
+ if (current && typeof current === 'object') {
46
+ const record = current as Record<string, unknown>;
47
+ for (const key in record) {
48
+ if (!Object.hasOwn(record, key)) {
49
+ continue;
50
+ }
51
+ elementCount += 1;
52
+ if (elementCount > MAX_TOOL_ARGUMENT_ELEMENTS) {
53
+ throw new ProtocolError(
54
+ ProtocolErrorCode.InvalidParams,
55
+ `Tool arguments exceed the ${MAX_TOOL_ARGUMENT_ELEMENTS} element limit.`
56
+ );
57
+ }
58
+ pending.push(record[key]);
59
+ }
60
+ }
61
+ }
62
+ }
63
+
24
64
  function buildCustomToolMap(): Map<string, CustomToolDefinition> {
25
65
  const customToolMap = new Map<string, CustomToolDefinition>();
26
66
  for (const customTool of getCustomTools()) {
@@ -116,6 +156,7 @@ export class OpenApiMcpServer {
116
156
  `Tool '${toolName}' not found.`
117
157
  );
118
158
  }
159
+ assertToolArgumentBounds(args);
119
160
  try {
120
161
  assertToolAllowed(toolName);
121
162
  const result = customTool