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.
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/PKG-INFO +2 -2
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/generator.py +19 -1
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/tool_runtime.py +5 -1
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/analyzer.py +7 -0
- openapi_to_mcp_cli-0.11.0/openapi_to_mcp/doctor/security.py +136 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/swagger.py +1 -1
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/README.md.j2 +31 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/package.json.j2 +7 -2
- {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
- openapi_to_mcp_cli-0.11.0/openapi_to_mcp/templates/src/runtime/basic_auth.ts.j2 +36 -0
- openapi_to_mcp_cli-0.11.0/openapi_to_mcp/templates/src/runtime/health.ts.j2 +106 -0
- {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
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/server.ts.j2 +41 -0
- {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
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/pyproject.toml +9 -9
- openapi_to_mcp_cli-0.11.0/scripts/assert_container_server.py +86 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_suites.py +5 -0
- openapi_to_mcp_cli-0.11.0/scripts/audit_generated_dependencies.py +121 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_generated_server.sh +4 -0
- openapi_to_mcp_cli-0.11.0/scripts/generated_dependency_audit.py +87 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/mock_target_api.py +16 -10
- openapi_to_mcp_cli-0.11.0/tests/integration/basic_auth_support.py +117 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/http_health_support.py +127 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_contracts.py +68 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_header_conflicts.py +104 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_requests.py +189 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_basic_auth_unicode.py +30 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_doctor.py +2 -2
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_generation_outputs.py +127 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_runtime_validation.py +2 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_generated_dev_dependencies.py +52 -0
- openapi_to_mcp_cli-0.11.0/tests/integration/test_http_health.py +302 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_swagger_policy_compatibility.py +3 -1
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/auth_openapi.yaml +16 -0
- openapi_to_mcp_cli-0.11.0/tests/resources/basic_auth_request.mjs +30 -0
- openapi_to_mcp_cli-0.11.0/tests/resources/http_health_lifecycle.mjs +36 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_doctor_analyzer.py +2 -2
- openapi_to_mcp_cli-0.11.0/tests/unit/test_doctor_auth_headers.py +102 -0
- openapi_to_mcp_cli-0.11.0/tests/unit/test_generated_dependency_audit.py +101 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator_runtime_files.py +2 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/uv.lock +67 -66
- openapi_to_mcp_cli-0.9.3/openapi_to_mcp/doctor/security.py +0 -71
- openapi_to_mcp_cli-0.9.3/scripts/assert_container_server.py +0 -43
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/.gitignore +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/LICENSE +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/README.md +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/spec_loader.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/http_redirects.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/models.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/response_formatting.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/server_tester.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/adapters/testing/stdio_transport.py +0 -0
- {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
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/cli.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/diff.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/doctor.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generate.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_context.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_models.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/generation_service.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/options.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/policy_support.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/run.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/run_support.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/runtime_overrides.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/commands/test_server.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/decorators.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/error_policy.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/exceptions.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/logger.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/performance_presets.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/spec_compatibility.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/terminal.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/utils.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/analyzer.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/models.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/diff/surface.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/models.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/doctor/swagger.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/mapper.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/output_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_description.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_examples.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/tool_grouping.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/mapping/utils.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/applier.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/loader.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/models.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/parser.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/settings.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/policy/validators.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/converter.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/array_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/base.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/common.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/composition.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/number_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/object_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/reference.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/schema/handlers/string_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/.env.example.j2 +0 -0
- {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
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/index.ts.j2 +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/tsconfig.json.j2 +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_access.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_audit.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_cached_audit.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_common.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_performance.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_generated_server_resilience.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/assert_runtime_observability.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/detect_release.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_cli_matrix.sh +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/e2e_container.sh +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_automation.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_metadata.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_changes.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_config.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/release_policy_lock.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/scripts/workflow.sh +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/conftest.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/constants.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/cli_generation_specs.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/query_serialization_spec.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_audit_controls.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_customization.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_diff.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_diff_behavior.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_generate_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_observability.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_output_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_performance_controls.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_config.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_errors.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_policy_execution.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_resilience_controls.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_access.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_grouping.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_tool_shaping.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_cli_xquik_fixture.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/integration/test_query_serialization.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/audit_openapi.yaml +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/release_contracts/auth.yaml +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/release_contracts/basic.yaml +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/test_openapi.json +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/resources/test_openapi.yaml +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/__init__.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_assertion_script_requests.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_assertion_wire_metadata.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_cli_help.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_generate.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_access_env.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_audit_env.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_env.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_errors.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_performance_env.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_run_resilience_env.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_test_server.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_commands_test_server_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_common_logger.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_common_utils.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_decorators.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_diff_analyzer.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_diff_surface.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_generator_transport.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_output_schema.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_output_schema_selection.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_request_body_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_robustness_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_tool_naming.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapper_tool_shaping.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_mapping_utils.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_performance_presets.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_policy_applier.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_policy_loader.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_automation.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_container_reference.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_detection.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_metadata.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_policy.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_preflight.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_release_workflow.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_response_formatting.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_converter.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_base.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_composition.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_schema_handlers_number.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester_stdio.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_server_tester_streamable_contracts.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_spec_loader.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_streamable_http_metadata.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_streamable_http_redirects.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_swagger_compatibility.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_description.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_grouping.py +0 -0
- {openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/tests/unit/test_tool_runtime.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
159
|
+
for name in template_names
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
def _custom_templates(self) -> dict[str, Path]:
|
{openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/common/tool_runtime.py
RENAMED
|
@@ -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()
|
|
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")
|
{openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/README.md.j2
RENAMED
|
@@ -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:
|
{openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/package.json.j2
RENAMED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
{% if transport == 'streamable-http' %}"@modelcontextprotocol/node": "^2.
|
|
24
|
-
{% endif %}"@modelcontextprotocol/server": "^2.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(':');
|
{openapi_to_mcp_cli-0.9.3 → openapi_to_mcp_cli-0.11.0}/openapi_to_mcp/templates/src/server.ts.j2
RENAMED
|
@@ -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
|