mcp-openstack-ops 3.6.1__tar.gz → 3.6.2__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.
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/AGENTS.md +6 -4
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/PKG-INFO +11 -79
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/README.md +10 -78
- mcp_openstack_ops-3.6.1/docs/multi-user-phase1.md → mcp_openstack_ops-3.6.2/docs/multi-user-guide.md +27 -133
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/prompt_template.md +1 -1
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_server_events.py +5 -1
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/user_connection.py +49 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/PKG-INFO +11 -79
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/SOURCES.txt +1 -8
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/scm_file_list.json +1 -8
- mcp_openstack_ops-3.6.2/src/mcp_openstack_ops.egg-info/scm_version.json +8 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_phase1_stabilization.py +12 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_user_connection.py +111 -0
- mcp_openstack_ops-3.6.1/docs/expanded-validation-fixes-2026-11-02.json +0 -18906
- mcp_openstack_ops-3.6.1/docs/expanded-validation-fixes-2026-11-02.md +0 -205
- mcp_openstack_ops-3.6.1/docs/integration-validation.md +0 -64
- mcp_openstack_ops-3.6.1/docs/main-live-retest-2026-10-30.json +0 -2342
- mcp_openstack_ops-3.6.1/docs/main-live-retest-2026-10-30.md +0 -268
- mcp_openstack_ops-3.6.1/docs/main-live-validation-2026-10-30.json +0 -2745
- mcp_openstack_ops-3.6.1/docs/main-live-validation-2026-10-30.md +0 -315
- mcp_openstack_ops-3.6.1/src/mcp_openstack_ops.egg-info/scm_version.json +0 -8
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.env.example +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.env.test +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.github/FUNDING.yml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.github/copilot-instructions.md +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.github/dependabot.yml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.github/workflows/pypi-publish.yml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.github/workflows/tests.yml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.gitignore +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.gitleaks.toml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.pre-commit-config.yaml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/.python-version +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/Dockerfile.MCP-Server +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/Dockerfile.MCPO-Proxy +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/LICENSE +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/MANIFEST.in +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/build-mcp-server-docker-image.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/build-mcpo-server-docker-image.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/diagnose_openstack.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/docker-compose.yml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/img/MCP-Workflow-of-Quickstart-Tutorial.png +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/img/screenshot-claude-desktop.png +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/img/screenshot-example-cluster-status.png +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/img/screenshot-openstack-dashboard.png +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/main.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/mcp-config.json.http +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/mcp-config.json.multi-project +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/mcp-config.json.stdio +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/pyproject.toml +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/scripts/check_release.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/scripts/mcp-server-docker-cmd.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/scripts/publish_release.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/scripts/run-mcp-inspector-local.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/scripts/run-mcp-inspector-pypi.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/setup.cfg +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/__init__.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/__main__.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/connection.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/functions.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/mcp_main.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/__init__.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/alarms.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/compute.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/core.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/identity.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/image.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/__init__.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/amphorae.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/core.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/health_monitors.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/l7_policies.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/listeners.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/management.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/load_balancer/pools.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/monitoring.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/network.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/orchestration.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/services/storage.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/__init__.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_availability_zones.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_domains.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_floating_ip_pools.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_floating_ips.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_heat_stack_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_heat_stacks.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_hypervisor_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_identity_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_image_detail_list.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_image_members.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_instance.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_instance_by_name.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_instance_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_instances_by_status.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_keypair_list.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_amphorae.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_availability_zones.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_flavors.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_health_monitors.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_l7_policies.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_l7_rules.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_list.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_listeners.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_pool_members.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_pools.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_providers.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_load_balancer_quotas.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_network_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_network_qos_policies.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_project_details.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_quota.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_resource_monitoring.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_role_assignments.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_routers.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_security_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_server_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_server_volumes.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_service_status.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_usage_statistics.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_user_list.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_backups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_list.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_qos.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_snapshots.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_transfers.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_volume_types.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/search_instances.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_alarms.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_compute_agents.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_domains.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_flavor.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_floating_ip.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_floating_ip_port_forwarding.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_heat_stack.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_identity_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_image.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_image_members.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_image_metadata.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_image_visibility.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_instance.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_keypair.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_amphora.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_availability_zone.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_flavor.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_health_monitor.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_l7_policy.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_l7_rule.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_listener.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_pool.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_pool_member.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_load_balancer_quota.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_metrics.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_network_agents.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_network_ports.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_network_qos_policies.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_networks.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_project.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_quota.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_roles.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_routers.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_security_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_backup.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_dump.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_fixed_ip.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_floating_ip.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_group.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_migration.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_network.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_properties.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_security_group.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_server_volume.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_service_logs.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_services.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_snapshot.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_subnets.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_users.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_volume.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_volume_backups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_volume_groups.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_volume_qos.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/set_volume_types.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/dependency_links.txt +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/entry_points.txt +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/requires.txt +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops.egg-info/top_level.txt +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/test_compatibility.sh +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/test_project_isolation.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_connection.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_expanded_validation_fixes.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_floating_ip.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_instance_image.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_live_regressions.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_operational_tools.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_publish_release.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_release.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/tests/test_tool_registration.py +0 -0
- {mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/uv.lock +0 -0
|
@@ -33,10 +33,12 @@ consistent with the resulting implementation and actual verification.
|
|
|
33
33
|
Label targeted retests as partial; do not extrapolate them to the full toolset.
|
|
34
34
|
Document polling limits and distinguish accepted requests from confirmed final
|
|
35
35
|
resource states. Explain changes in test conditions that affect comparisons.
|
|
36
|
-
- Keep
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
- Keep `docs/` for reusable user guidance only. Do not create or retain
|
|
37
|
+
issue-specific reports, dated test results, or evidence JSON in `docs/`, or
|
|
38
|
+
embed those records in usage guides. Report case-specific verification results,
|
|
39
|
+
limits, failures, and cleanup in chat unless the user explicitly requests a
|
|
40
|
+
separate artifact. Never expose credentials, private endpoints, or sensitive
|
|
41
|
+
resource data. Do not add report links to README.md.
|
|
40
42
|
- Before completion, check summary arithmetic, percentages, tool/action names,
|
|
41
43
|
Status/Notes, source identity, and evidence links for agreement. If README.ko.md
|
|
42
44
|
exists, maintain equivalent information in both README files.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-openstack-ops
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.2
|
|
4
4
|
Summary: OpenStack operations automation MCP server
|
|
5
5
|
Author-email: JungJungIn <call518@gmail.com>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
|
42
42
|
## Features
|
|
43
43
|
|
|
44
44
|
Experimental per-user remote connections are available for testing with the
|
|
45
|
-
opt-in `--multi-user` mode. See the [
|
|
45
|
+
opt-in `--multi-user` mode. See the [multi-user setup and usage guide](docs/multi-user-guide.md)
|
|
46
46
|
for authentication, client limitations, and the test checklist. Administrator
|
|
47
47
|
cross-project queries are outside this phase.
|
|
48
48
|
|
|
@@ -183,80 +183,12 @@ New consolidated `get_instance` tool replaces multiple separate tools:
|
|
|
183
183
|
|
|
184
184
|
## 📊 OpenStack CLI vs MCP Tools Mapping
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
fingerprints; Claude replay remains unverified.
|
|
193
|
-
|
|
194
|
-
| Verification group | Item | Result |
|
|
195
|
-
| --- | --- | --- |
|
|
196
|
-
| Live tool checks | Unique tools retested in limited scenarios | **14/14 (100%)**, 0 failed/unverified within this scope |
|
|
197
|
-
| Live tool checks | Registered inventory not repeated in this final run | **91 of 105** tools; historical evidence below is not a current full sweep |
|
|
198
|
-
| Named live harness cases | Fixture, isolation, lifecycle and grouped load checks | **101/102**, one Cinder storage-permission fixture failure, 0 skipped |
|
|
199
|
-
| Local unit tests | Locked environment, `python -m pytest -q tests` | **199/199 (100%)**, 0 failed/skipped; 21 added regressions |
|
|
200
|
-
| Bounded load requests | Shared-server, two-project bursts | **14/14**, actual overlap up to 8 |
|
|
201
|
-
| Bounded load requests | Same shared server, 120 seconds, four workers | **209/209**, median 2.03 s, p95 2.70 s, max 3.24 s |
|
|
202
|
-
| Backup recovery | After operator corrected one NFS directory permission | **6/6 fresh backups available**, both reader MCP state checks passed |
|
|
203
|
-
| Claude client | Minimal actual model API preflight | Blocked before tools: credit/balance error; no final-source Claude replay |
|
|
204
|
-
|
|
205
|
-
Network creation now uses the backend's default MTU when omitted; automatic
|
|
206
|
-
1450 and explicit 1400 were verified. Both `set_volume` and `set_server_volume`
|
|
207
|
-
attach/detach reached final states, with cross-project requests rejected. Both
|
|
208
|
-
search wrappers now honor offset/case sensitivity and correct `has_more`.
|
|
209
|
-
Default write restrictions remain in place. These successes do not certify all
|
|
210
|
-
actions of those tools or all 105 registered tools.
|
|
211
|
-
|
|
212
|
-
The Cinder fixture failure occurred through its API, independently of MCP. The
|
|
213
|
-
operator corrected the failing directory's group-write permission; the original
|
|
214
|
-
failure remains recorded alongside the successful targeted recovery. Actual
|
|
215
|
-
Claude same-URL duplicate registration was not established as a product defect;
|
|
216
|
-
single-entry/distinct-URL attempts also failed before tools. Restoring model API
|
|
217
|
-
access is required for that controlled comparison.
|
|
218
|
-
|
|
219
|
-
Earlier same-day results are consolidated in the final report's
|
|
220
|
-
[sanitized evidence](docs/expanded-validation-fixes-2026-11-02.json), under
|
|
221
|
-
`historical_evidence.earlier_runs`. The previous 33-tool classification and
|
|
222
|
-
41 actual Claude template attempts belong to the pre-fix source, not this
|
|
223
|
-
final source. The complete 105-tool snapshot below is also historical.
|
|
224
|
-
|
|
225
|
-
**Validation snapshot — 2026-10-30** (main working tree based on `dd98993`,
|
|
226
|
-
with the uncommitted fixes described in the report):
|
|
227
|
-
|
|
228
|
-
<table border="1" rules="cols">
|
|
229
|
-
<thead>
|
|
230
|
-
<tr><th>Verification type</th><th>Item</th><th>Result</th></tr>
|
|
231
|
-
</thead>
|
|
232
|
-
<tbody>
|
|
233
|
-
<tr><td rowspan="5">Live tool checks</td><td>Registered tools invoked and classified</td><td><strong>105</strong></td></tr>
|
|
234
|
-
<tr><td>Passed within the exercised live scenarios</td><td><strong>74</strong></td></tr>
|
|
235
|
-
<tr><td>Implemented, live unverified: service/API unavailable</td><td><strong>28</strong></td></tr>
|
|
236
|
-
<tr><td>Intentional capability limitations</td><td><strong>3</strong></td></tr>
|
|
237
|
-
<tr><td>Reproduced code defects / state-check timeouts in the retest</td><td><strong>0 / 0</strong></td></tr>
|
|
238
|
-
<tr><td>Local unit tests</td><td>Test cases in <code>tests/test_*.py</code></td><td><strong>160/160 (100%)</strong></td></tr>
|
|
239
|
-
</tbody>
|
|
240
|
-
</table>
|
|
241
|
-
|
|
242
|
-
State polling allowed 180 seconds; soft reboot completed in 131.66 seconds
|
|
243
|
-
(previously it exceeded the 90-second check). No reboot implementation change
|
|
244
|
-
was needed.
|
|
245
|
-
|
|
246
|
-
The 10 previously reproduced tool defects were corrected. The 28 unavailable
|
|
247
|
-
paths are **not live-validated successes**: 24 Octavia tools, two Neutron QoS
|
|
248
|
-
tools, one Aodh tool, and one Neutron port-forwarding tool. Each affected table
|
|
249
|
-
row is marked **⚠️ Live unverified**. An absent tool/action is marked **❌ Not
|
|
250
|
-
implemented** rather than presented as an available feature.
|
|
251
|
-
|
|
252
|
-
✅ means an implemented API path; it does not certify every action or backend.
|
|
253
|
-
The live-pass count covers only the scenarios in the
|
|
254
|
-
[latest retest report](docs/main-live-retest-2026-10-30.md), with
|
|
255
|
-
[sanitized evidence](docs/main-live-retest-2026-10-30.json). API policy, asynchronous
|
|
256
|
-
completion, production HTTPS/client compatibility, load, and guest I/O limits
|
|
257
|
-
are stated there. All test resources were cleaned up; the final audit found zero
|
|
258
|
-
remaining test resources across 24 categories. See also
|
|
259
|
-
[API prerequisites and result semantics](#api-prerequisites-and-result-semantics).
|
|
186
|
+
✅ denotes an implemented API path, not certification of every action or
|
|
187
|
+
OpenStack deployment. **⚠️ Live unverified** identifies paths without successful
|
|
188
|
+
live validation; **❌ Not implemented** identifies unavailable capabilities.
|
|
189
|
+
Service availability, permissions, and asynchronous completion affect results.
|
|
190
|
+
See [API prerequisites and result semantics](#api-prerequisites-and-result-semantics)
|
|
191
|
+
and the [multi-user setup guide](docs/multi-user-guide.md).
|
|
260
192
|
|
|
261
193
|
**Detailed Mapping by Category**
|
|
262
194
|
|
|
@@ -302,7 +234,7 @@ remaining test resources across 24 categories. See also
|
|
|
302
234
|
| `openstack server set` | `set_server_properties` (action="set") | ✅ | Server property setting |
|
|
303
235
|
| `openstack server unset` | `set_server_properties` (action="unset") | ✅ | Server property unsetting |
|
|
304
236
|
| `openstack server dump create` | `set_server_dump` | ❌ Unsupported | Returns an explicit unsupported result; no dump is created |
|
|
305
|
-
| `openstack server event list` | `get_server_events` | ✅ |
|
|
237
|
+
| `openstack server event list` | `get_server_events` | ✅ | Pass the server name or UUID via `instance_name`, not `server_id`; event visibility follows Nova policy |
|
|
306
238
|
| `openstack server group list` | `get_server_groups` | ✅ | Server group listing |
|
|
307
239
|
| `openstack server group create/delete` | `set_server_group` | ✅ | Server group management |
|
|
308
240
|
| `openstack flavor list` | `set_flavor` (action="list") | ✅ | Requires modification tools to be enabled for registration |
|
|
@@ -542,7 +474,7 @@ remaining test resources across 24 categories. See also
|
|
|
542
474
|
|
|
543
475
|
### API prerequisites and result semantics
|
|
544
476
|
|
|
545
|
-
The feature table marks implemented API paths, not verification against every OpenStack deployment.
|
|
477
|
+
The feature table marks implemented API paths, not verification against every OpenStack deployment. Service and policy requirements must be checked in the target deployment. A missing service, extension, required microversion, or permission is an operation failure, not an empty successful result.
|
|
546
478
|
|
|
547
479
|
- **Modification tools:** the MCP server registers `set_*` management tools only when `ALLOW_MODIFY_OPERATIONS=true`. Existing action guards also reject writes when disabled. Dedicated read-only `get_*` tools remain available. API authorization is enforced by the target cloud; agent, user, domain, type and QoS administration may require elevated OpenStack roles.
|
|
548
480
|
- **Alarms:** `set_alarms` calls the Keystone catalog's `alarming` endpoint for the configured interface/region. It supports list/create/show/delete of project alarms and creates `gnocchi_resources_threshold` alarms. Aodh, Gnocchi, the specified resource and a collected metric are required. The default metric is `cpu_util`, which is not collected by every deployment. `metric`, `resource_type`, `aggregation_method`, `granularity`, `evaluation_periods`, and `alarm_actions` can be supplied. An empty action list creates an alarm without notifications. There is no simulation fallback.
|
|
@@ -744,7 +676,7 @@ unset OPENSTACK_MCP_TOKEN
|
|
|
744
676
|
```
|
|
745
677
|
|
|
746
678
|
This Bearer configuration uses the server's configured OpenStack account. For
|
|
747
|
-
experimental per-user authentication, see the [
|
|
679
|
+
experimental per-user authentication, see the [multi-user guide](docs/multi-user-guide.md).
|
|
748
680
|
See also the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp).
|
|
749
681
|
|
|
750
682
|
## Server Configuration
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
26
|
Experimental per-user remote connections are available for testing with the
|
|
27
|
-
opt-in `--multi-user` mode. See the [
|
|
27
|
+
opt-in `--multi-user` mode. See the [multi-user setup and usage guide](docs/multi-user-guide.md)
|
|
28
28
|
for authentication, client limitations, and the test checklist. Administrator
|
|
29
29
|
cross-project queries are outside this phase.
|
|
30
30
|
|
|
@@ -165,80 +165,12 @@ New consolidated `get_instance` tool replaces multiple separate tools:
|
|
|
165
165
|
|
|
166
166
|
## 📊 OpenStack CLI vs MCP Tools Mapping
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
fingerprints; Claude replay remains unverified.
|
|
175
|
-
|
|
176
|
-
| Verification group | Item | Result |
|
|
177
|
-
| --- | --- | --- |
|
|
178
|
-
| Live tool checks | Unique tools retested in limited scenarios | **14/14 (100%)**, 0 failed/unverified within this scope |
|
|
179
|
-
| Live tool checks | Registered inventory not repeated in this final run | **91 of 105** tools; historical evidence below is not a current full sweep |
|
|
180
|
-
| Named live harness cases | Fixture, isolation, lifecycle and grouped load checks | **101/102**, one Cinder storage-permission fixture failure, 0 skipped |
|
|
181
|
-
| Local unit tests | Locked environment, `python -m pytest -q tests` | **199/199 (100%)**, 0 failed/skipped; 21 added regressions |
|
|
182
|
-
| Bounded load requests | Shared-server, two-project bursts | **14/14**, actual overlap up to 8 |
|
|
183
|
-
| Bounded load requests | Same shared server, 120 seconds, four workers | **209/209**, median 2.03 s, p95 2.70 s, max 3.24 s |
|
|
184
|
-
| Backup recovery | After operator corrected one NFS directory permission | **6/6 fresh backups available**, both reader MCP state checks passed |
|
|
185
|
-
| Claude client | Minimal actual model API preflight | Blocked before tools: credit/balance error; no final-source Claude replay |
|
|
186
|
-
|
|
187
|
-
Network creation now uses the backend's default MTU when omitted; automatic
|
|
188
|
-
1450 and explicit 1400 were verified. Both `set_volume` and `set_server_volume`
|
|
189
|
-
attach/detach reached final states, with cross-project requests rejected. Both
|
|
190
|
-
search wrappers now honor offset/case sensitivity and correct `has_more`.
|
|
191
|
-
Default write restrictions remain in place. These successes do not certify all
|
|
192
|
-
actions of those tools or all 105 registered tools.
|
|
193
|
-
|
|
194
|
-
The Cinder fixture failure occurred through its API, independently of MCP. The
|
|
195
|
-
operator corrected the failing directory's group-write permission; the original
|
|
196
|
-
failure remains recorded alongside the successful targeted recovery. Actual
|
|
197
|
-
Claude same-URL duplicate registration was not established as a product defect;
|
|
198
|
-
single-entry/distinct-URL attempts also failed before tools. Restoring model API
|
|
199
|
-
access is required for that controlled comparison.
|
|
200
|
-
|
|
201
|
-
Earlier same-day results are consolidated in the final report's
|
|
202
|
-
[sanitized evidence](docs/expanded-validation-fixes-2026-11-02.json), under
|
|
203
|
-
`historical_evidence.earlier_runs`. The previous 33-tool classification and
|
|
204
|
-
41 actual Claude template attempts belong to the pre-fix source, not this
|
|
205
|
-
final source. The complete 105-tool snapshot below is also historical.
|
|
206
|
-
|
|
207
|
-
**Validation snapshot — 2026-10-30** (main working tree based on `dd98993`,
|
|
208
|
-
with the uncommitted fixes described in the report):
|
|
209
|
-
|
|
210
|
-
<table border="1" rules="cols">
|
|
211
|
-
<thead>
|
|
212
|
-
<tr><th>Verification type</th><th>Item</th><th>Result</th></tr>
|
|
213
|
-
</thead>
|
|
214
|
-
<tbody>
|
|
215
|
-
<tr><td rowspan="5">Live tool checks</td><td>Registered tools invoked and classified</td><td><strong>105</strong></td></tr>
|
|
216
|
-
<tr><td>Passed within the exercised live scenarios</td><td><strong>74</strong></td></tr>
|
|
217
|
-
<tr><td>Implemented, live unverified: service/API unavailable</td><td><strong>28</strong></td></tr>
|
|
218
|
-
<tr><td>Intentional capability limitations</td><td><strong>3</strong></td></tr>
|
|
219
|
-
<tr><td>Reproduced code defects / state-check timeouts in the retest</td><td><strong>0 / 0</strong></td></tr>
|
|
220
|
-
<tr><td>Local unit tests</td><td>Test cases in <code>tests/test_*.py</code></td><td><strong>160/160 (100%)</strong></td></tr>
|
|
221
|
-
</tbody>
|
|
222
|
-
</table>
|
|
223
|
-
|
|
224
|
-
State polling allowed 180 seconds; soft reboot completed in 131.66 seconds
|
|
225
|
-
(previously it exceeded the 90-second check). No reboot implementation change
|
|
226
|
-
was needed.
|
|
227
|
-
|
|
228
|
-
The 10 previously reproduced tool defects were corrected. The 28 unavailable
|
|
229
|
-
paths are **not live-validated successes**: 24 Octavia tools, two Neutron QoS
|
|
230
|
-
tools, one Aodh tool, and one Neutron port-forwarding tool. Each affected table
|
|
231
|
-
row is marked **⚠️ Live unverified**. An absent tool/action is marked **❌ Not
|
|
232
|
-
implemented** rather than presented as an available feature.
|
|
233
|
-
|
|
234
|
-
✅ means an implemented API path; it does not certify every action or backend.
|
|
235
|
-
The live-pass count covers only the scenarios in the
|
|
236
|
-
[latest retest report](docs/main-live-retest-2026-10-30.md), with
|
|
237
|
-
[sanitized evidence](docs/main-live-retest-2026-10-30.json). API policy, asynchronous
|
|
238
|
-
completion, production HTTPS/client compatibility, load, and guest I/O limits
|
|
239
|
-
are stated there. All test resources were cleaned up; the final audit found zero
|
|
240
|
-
remaining test resources across 24 categories. See also
|
|
241
|
-
[API prerequisites and result semantics](#api-prerequisites-and-result-semantics).
|
|
168
|
+
✅ denotes an implemented API path, not certification of every action or
|
|
169
|
+
OpenStack deployment. **⚠️ Live unverified** identifies paths without successful
|
|
170
|
+
live validation; **❌ Not implemented** identifies unavailable capabilities.
|
|
171
|
+
Service availability, permissions, and asynchronous completion affect results.
|
|
172
|
+
See [API prerequisites and result semantics](#api-prerequisites-and-result-semantics)
|
|
173
|
+
and the [multi-user setup guide](docs/multi-user-guide.md).
|
|
242
174
|
|
|
243
175
|
**Detailed Mapping by Category**
|
|
244
176
|
|
|
@@ -284,7 +216,7 @@ remaining test resources across 24 categories. See also
|
|
|
284
216
|
| `openstack server set` | `set_server_properties` (action="set") | ✅ | Server property setting |
|
|
285
217
|
| `openstack server unset` | `set_server_properties` (action="unset") | ✅ | Server property unsetting |
|
|
286
218
|
| `openstack server dump create` | `set_server_dump` | ❌ Unsupported | Returns an explicit unsupported result; no dump is created |
|
|
287
|
-
| `openstack server event list` | `get_server_events` | ✅ |
|
|
219
|
+
| `openstack server event list` | `get_server_events` | ✅ | Pass the server name or UUID via `instance_name`, not `server_id`; event visibility follows Nova policy |
|
|
288
220
|
| `openstack server group list` | `get_server_groups` | ✅ | Server group listing |
|
|
289
221
|
| `openstack server group create/delete` | `set_server_group` | ✅ | Server group management |
|
|
290
222
|
| `openstack flavor list` | `set_flavor` (action="list") | ✅ | Requires modification tools to be enabled for registration |
|
|
@@ -524,7 +456,7 @@ remaining test resources across 24 categories. See also
|
|
|
524
456
|
|
|
525
457
|
### API prerequisites and result semantics
|
|
526
458
|
|
|
527
|
-
The feature table marks implemented API paths, not verification against every OpenStack deployment.
|
|
459
|
+
The feature table marks implemented API paths, not verification against every OpenStack deployment. Service and policy requirements must be checked in the target deployment. A missing service, extension, required microversion, or permission is an operation failure, not an empty successful result.
|
|
528
460
|
|
|
529
461
|
- **Modification tools:** the MCP server registers `set_*` management tools only when `ALLOW_MODIFY_OPERATIONS=true`. Existing action guards also reject writes when disabled. Dedicated read-only `get_*` tools remain available. API authorization is enforced by the target cloud; agent, user, domain, type and QoS administration may require elevated OpenStack roles.
|
|
530
462
|
- **Alarms:** `set_alarms` calls the Keystone catalog's `alarming` endpoint for the configured interface/region. It supports list/create/show/delete of project alarms and creates `gnocchi_resources_threshold` alarms. Aodh, Gnocchi, the specified resource and a collected metric are required. The default metric is `cpu_util`, which is not collected by every deployment. `metric`, `resource_type`, `aggregation_method`, `granularity`, `evaluation_periods`, and `alarm_actions` can be supplied. An empty action list creates an alarm without notifications. There is no simulation fallback.
|
|
@@ -726,7 +658,7 @@ unset OPENSTACK_MCP_TOKEN
|
|
|
726
658
|
```
|
|
727
659
|
|
|
728
660
|
This Bearer configuration uses the server's configured OpenStack account. For
|
|
729
|
-
experimental per-user authentication, see the [
|
|
661
|
+
experimental per-user authentication, see the [multi-user guide](docs/multi-user-guide.md).
|
|
730
662
|
See also the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp).
|
|
731
663
|
|
|
732
664
|
## Server Configuration
|
mcp_openstack_ops-3.6.1/docs/multi-user-phase1.md → mcp_openstack_ops-3.6.2/docs/multi-user-guide.md
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Multi-user HTTP
|
|
1
|
+
# Multi-user HTTP setup and usage guide
|
|
2
2
|
|
|
3
|
-
The server provides an opt-in, experimental
|
|
3
|
+
The server provides an opt-in, experimental multi-user HTTP mode. It keeps
|
|
4
4
|
existing project ownership checks and the default write restriction. The normal
|
|
5
5
|
single-account HTTP and stdio modes remain available without this option.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Authentication
|
|
8
8
|
|
|
9
9
|
Clients send their own Keystone Application Credential ID and secret in two
|
|
10
10
|
dedicated HTTP headers on **every request**. The server authenticates against
|
|
@@ -13,10 +13,9 @@ project-scoped connection. Authentication failure never falls back to the server
|
|
|
13
13
|
environment account. `Authorization` is not forwarded to OpenStack; this is custom
|
|
14
14
|
header authentication, not an OAuth server or Keystone token passthrough.
|
|
15
15
|
|
|
16
|
-
Application Credentials
|
|
16
|
+
Application Credentials avoid LDAP passwords
|
|
17
17
|
and already specify a project and delegated roles. Raw Keystone tokens, browser
|
|
18
|
-
login, project switching, and administrator cross-project access are not
|
|
19
|
-
in phase 1. Use another Application Credential/client entry for another project.
|
|
18
|
+
login, project switching, and administrator cross-project access are not supported. Use another Application Credential/client entry for another project.
|
|
20
19
|
|
|
21
20
|
The HTTP transport is stateless with JSON responses. Every HTTP request creates a
|
|
22
21
|
fresh Keystone authentication and SDK connection; there is no authentication or
|
|
@@ -70,6 +69,26 @@ credential `.env` or `clouds.yaml`. `OS_USERNAME`, `OS_PASSWORD`, and
|
|
|
70
69
|
and `REMOTE_SECRET_KEY`: the shared Bearer mode cannot be combined with this mode.
|
|
71
70
|
DEBUG logging is rejected to avoid authentication wire dumps.
|
|
72
71
|
|
|
72
|
+
Unsupported OAuth metadata routes (`/.well-known/oauth-protected-resource`,
|
|
73
|
+
`/.well-known/oauth-authorization-server`, `/.well-known/openid-configuration`,
|
|
74
|
+
including their path suffixes) and `/register` return 404 without Keystone
|
|
75
|
+
authentication. This mode does not provide OAuth discovery or registration;
|
|
76
|
+
`/mcp` still requires both application-credential headers.
|
|
77
|
+
|
|
78
|
+
HTTP 400 responses from the MCP transport produce an INFO diagnostic with a
|
|
79
|
+
server-generated `request_id` and a fixed `reason` classification:
|
|
80
|
+
`invalid_json`, `invalid_jsonrpc`, `unsupported_protocol`, `missing_session_id`,
|
|
81
|
+
or `unclassified`. Request bodies, header values, and raw error messages are not
|
|
82
|
+
included in this diagnostic. Unknown, oversized, or non-JSON error responses
|
|
83
|
+
may remain unclassified. A 400 alone does not establish an authentication failure
|
|
84
|
+
or explain the reported first-connection behavior; correlate its request ID
|
|
85
|
+
with the client response before drawing a conclusion. A client may probe with a
|
|
86
|
+
newer protocol version, receive `unsupported_protocol`, and then negotiate a
|
|
87
|
+
supported version successfully. Check whether initialization and subsequent
|
|
88
|
+
requests succeed before treating that rejection as a connection failure. Do not
|
|
89
|
+
bypass protocol validation merely to suppress the initial 400 response.
|
|
90
|
+
|
|
91
|
+
|
|
73
92
|
Expose `/mcp` through an HTTPS reverse proxy. Do not expose the plaintext backend
|
|
74
93
|
port to clients. Configure the proxy to redact both credential headers and
|
|
75
94
|
`Authorization`, avoid request/header dumps, and enforce rate and concurrency
|
|
@@ -134,7 +153,7 @@ Sources checked while designing this branch:
|
|
|
134
153
|
- [Claude connector request-header limitations](https://claude.com/docs/connectors/custom/add-unlisted#authenticate-with-request-headers)
|
|
135
154
|
- [Keystone Application Credentials](https://docs.openstack.org/keystone/latest/user/application_credentials.html)
|
|
136
155
|
|
|
137
|
-
##
|
|
156
|
+
## Deployment validation checklist
|
|
138
157
|
|
|
139
158
|
Use disposable test projects and independently created credentials. Record client
|
|
140
159
|
versions, service versions, and results without posting secrets or resource data.
|
|
@@ -146,7 +165,7 @@ versions, service versions, and results without posting secrets or resource data
|
|
|
146
165
|
project's private resources. Public/shared resources remain governed by the
|
|
147
166
|
existing checks and service policy. Project isolation is not ownership per user.
|
|
148
167
|
3. Look up a known private resource ID from the other project: access must fail.
|
|
149
|
-
Even an admin credential remains
|
|
168
|
+
Even an admin credential remains subject to the same project ownership checks.
|
|
150
169
|
4. Connect the same user with two separate project credentials/client entries;
|
|
151
170
|
verify each entry sees only its intended project.
|
|
152
171
|
5. Omit, invalidate, or duplicate either credential header. No tool must execute,
|
|
@@ -173,128 +192,3 @@ Automated tests cover authentication rejection/revalidation, concurrent contexts
|
|
|
173
192
|
including synchronous tools, cleanup, default write gating, project checks, and
|
|
174
193
|
legacy cache behavior. They do not establish actual Keystone revocation timing,
|
|
175
194
|
service policy enforcement, or Claude client compatibility.
|
|
176
|
-
|
|
177
|
-
## Local cloud validation (2026-10-29)
|
|
178
|
-
|
|
179
|
-
Real-cloud testing used two disposable projects, two non-admin users, and one
|
|
180
|
-
private network per project. An additional reader-only Application Credential
|
|
181
|
-
verified delegated role enforcement. Initial empty-list validation passed 36
|
|
182
|
-
checks. Follow-up validation added one Cirros VM (`m1.tiny`: 1 vCPU, 512 MB RAM,
|
|
183
|
-
1 GB disk) and one empty 1 GB volume per project. All 66 recorded follow-up checks
|
|
184
|
-
passed; the latest unit-test run passed 46 tests.
|
|
185
|
-
|
|
186
|
-
The test used real Keystone, Nova, Neutron, and Cinder APIs through temporary
|
|
187
|
-
loopback TLS proxies with certificate verification. The proxy-to-cluster leg was
|
|
188
|
-
HTTP because the test cluster has no TLS on its API ports. MCP calls went through
|
|
189
|
-
the actual FastMCP ASGI HTTP application using an HTTP test client; this was not a
|
|
190
|
-
Claude Code or Desktop session and does not validate a production TLS deployment.
|
|
191
|
-
|
|
192
|
-
Validated behavior:
|
|
193
|
-
|
|
194
|
-
- Concurrent users retained their own project identity and private network view.
|
|
195
|
-
- Lookup of the other project's private network ID returned 403/404.
|
|
196
|
-
- Registered instance, network, and volume read tools returned only the expected
|
|
197
|
-
project resources. Own-instance detail queries succeeded; SDK detail queries
|
|
198
|
-
for the other project's VM and volume IDs returned 403/404.
|
|
199
|
-
- Write tools were absent by default; a test-only SDK write probe with reader
|
|
200
|
-
credentials was rejected with HTTP 403 by Neutron.
|
|
201
|
-
- Missing, invalid, and deleted credentials were rejected with HTTP 401.
|
|
202
|
-
- After credential deletion, the very next request was rejected. This observation
|
|
203
|
-
is not a deployment-independent revocation SLA.
|
|
204
|
-
- Captured application logs identified users and tools without any known test
|
|
205
|
-
passwords, Application Credential IDs/secrets, or issued tokens appearing.
|
|
206
|
-
- All test Application Credentials were deleted and both test users disabled.
|
|
207
|
-
The two isolated projects and private networks were retained for follow-up.
|
|
208
|
-
- Both VMs reached `ACTIVE`, and both volumes reached `available`. All four were
|
|
209
|
-
deleted after testing; Nova instance/core/RAM and Cinder volume/GB quota usage
|
|
210
|
-
and reservations returned to zero in both projects.
|
|
211
|
-
|
|
212
|
-
Testing exposed and fixed Cinder's SDK service-name/version/project-URL setup and
|
|
213
|
-
classification of Keystone's deleted-credential `NotFound` response as 401 rather
|
|
214
|
-
than 503. The populated-resource follow-up required no additional application
|
|
215
|
-
changes. Claude Desktop/IDE, sustained load, guest networking/SSH, volume
|
|
216
|
-
attachment/I/O, and a complete production HTTPS path remain unverified.
|
|
217
|
-
|
|
218
|
-
## Actual Claude Code validation (2026-10-29)
|
|
219
|
-
|
|
220
|
-
Claude Code 2.1.312 was used as the real MCP client, not an ASGI test client.
|
|
221
|
-
Two isolated Claude configuration directories registered the server with
|
|
222
|
-
`claude mcp add-json --scope user`. The server ran on a real HTTPS TCP listener
|
|
223
|
-
with certificate verification; the temporary cloud-side TLS proxies described
|
|
224
|
-
above remained necessary. No tools or OpenStack responses were mocked.
|
|
225
|
-
|
|
226
|
-
Both clients executed the request "Show me the overall cluster status" through
|
|
227
|
-
the actual configured Claude API. Initialization reported the user-scoped MCP
|
|
228
|
-
server as `connected`; CLI stream events and server audit logs confirmed actual
|
|
229
|
-
calls to registered service-status, monitoring, instance, network, and volume
|
|
230
|
-
tools. A second request for each user's named private network confirmed that
|
|
231
|
-
the actual client returned its own network ID rather than the other project's.
|
|
232
|
-
All 28 connection/isolation/cleanup checks passed. Temporary registrations were
|
|
233
|
-
removed, credentials deleted, and test users disabled afterward. Original Claude
|
|
234
|
-
configuration was left unchanged; test credentials were not found in client
|
|
235
|
-
configuration files or captured server logs.
|
|
236
|
-
|
|
237
|
-
**Connection validation is not a complete status-report accuracy pass.** The
|
|
238
|
-
real prompt exposed existing reporting defects outside the connection layer:
|
|
239
|
-
|
|
240
|
-
- `functions.get_service_status` omits some services after permission failures
|
|
241
|
-
and uses a fixed "skipped" Heat entry without actually checking Heat.
|
|
242
|
-
- Compute/volume availability zones read a nonexistent `available` attribute
|
|
243
|
-
rather than the SDK's `state['available']`, incorrectly defaulting to false.
|
|
244
|
-
- Resource-monitoring capacity zeros cannot establish physical capacity when
|
|
245
|
-
hypervisor access is denied. A project-scoped user cannot assess global health.
|
|
246
|
-
|
|
247
|
-
These findings were identified during validation and have not been repaired in
|
|
248
|
-
this test step. Do not treat the model's successful completion or its availability
|
|
249
|
-
summary as proof that the whole cluster is healthy or unavailable.
|
|
250
|
-
|
|
251
|
-
## Ordinary-user Claude Code queries (2026-10-29)
|
|
252
|
-
|
|
253
|
-
Administrator-only API denials are expected for ordinary users and are not a
|
|
254
|
-
phase-1 authentication/connection failure. Each of the two real, registered
|
|
255
|
-
Claude Code clients therefore executed these ordinary project requests:
|
|
256
|
-
|
|
257
|
-
| Request | Actual tool | Observed result for both users |
|
|
258
|
-
| --- | --- | --- |
|
|
259
|
-
| List all instances in the project | `get_instance` | Empty after VM cleanup |
|
|
260
|
-
| Show all networks and their subnets | `get_network_details` | Own private network and shared external network; no other test project's private network |
|
|
261
|
-
| List all volumes and their status | `get_volume_list` | Empty after volume cleanup |
|
|
262
|
-
|
|
263
|
-
All six actual Claude requests completed, and all 52 recorded connection,
|
|
264
|
-
tool-execution, isolation, credential/logging, and cleanup checks passed. Own
|
|
265
|
-
fixture networks have no subnets because none were created. Missing subnet
|
|
266
|
-
details for a shared network do not establish that the network has no subnets.
|
|
267
|
-
The earlier populated VM/volume test remains the evidence for nonempty resource
|
|
268
|
-
isolation. Test registrations and credentials were removed and users disabled.
|
|
269
|
-
|
|
270
|
-
These results validate the phase-1 project-user workflow separately from global
|
|
271
|
-
administrator reporting. They do not certify every legacy reporting field: an
|
|
272
|
-
optional `get_floating_ip_pools` call exposed an existing placeholder calculation
|
|
273
|
-
of 100 IPs per allocation pool. That is not a measured pool capacity and was not
|
|
274
|
-
counted as a verified result. The status/AZ reporting findings above also remain.
|
|
275
|
-
|
|
276
|
-
## Default WSL Claude configuration validation (2026-10-29)
|
|
277
|
-
|
|
278
|
-
A further test used the actual default WSL Claude user configuration at
|
|
279
|
-
`/root/.claude.json`, without a `CLAUDE_CONFIG_DIR` override or `--mcp-config`.
|
|
280
|
-
`claude mcp add-json --scope user` registered `openstack73-a` and `openstack73-b`
|
|
281
|
-
sequentially. Plain `claude mcp list` confirmed `Connected`; an independent plain
|
|
282
|
-
CLI invocation also confirmed the active registration. Each registration was
|
|
283
|
-
kept visible briefly for inspection before executing the three project queries.
|
|
284
|
-
|
|
285
|
-
All six actual Claude queries and all 57 recorded checks passed, including
|
|
286
|
-
correct server selection, real tool results, private-network isolation, and
|
|
287
|
-
cleanup. These registrations used the real server on WSL-only HTTP loopback;
|
|
288
|
-
the cloud-side TLS proxies remained as above. A same-user, permission-restricted
|
|
289
|
-
Unix-socket helper supplied credential headers from memory, so no credential
|
|
290
|
-
values were stored in the normal user registration or command arguments.
|
|
291
|
-
|
|
292
|
-
The initial attempt with both entries simultaneously present did not validate
|
|
293
|
-
both clients: one remained `pending`. The successful default-configuration test
|
|
294
|
-
therefore establishes sequential registrations, not simultaneous same-profile
|
|
295
|
-
multi-entry support. Earlier simultaneous-client tests used separate profiles.
|
|
296
|
-
|
|
297
|
-
Only the test registrations were removed afterward. Existing MCP registrations
|
|
298
|
-
were preserved, test credentials deleted, and test users disabled. The temporary
|
|
299
|
-
helper/server stopped after the test, so saved commands are an execution record,
|
|
300
|
-
not sufficient to reconnect without restarting the test infrastructure.
|
{mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/prompt_template.md
RENAMED
|
@@ -913,4 +913,4 @@ This approach provides **comprehensive 360-degree cluster visibility** with infr
|
|
|
913
913
|
- `get_heat_stack_details`: `section` is resources/events/template/outputs.
|
|
914
914
|
- `set_service_logs(action="show")` and `set_metrics(action="show")` are unavailable without additional backend implementation. Never claim to have retrieved logs or telemetry samples from metadata-only results.
|
|
915
915
|
|
|
916
|
-
|
|
916
|
+
Confirm service availability and permissions in the target deployment before attempting Aodh/Neutron QoS operations or network-agent state changes. Check returned errors and partial flags; never describe an accepted asynchronous request as a completed operation. Usage date bounds are UTC midnight, start inclusive/end exclusive. Missing physical capacity is null, not zero. Physical capacity and project allocations have different scopes and must not be divided into a utilization rate.
|
{mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/tools/get_server_events.py
RENAMED
|
@@ -14,7 +14,11 @@ async def get_server_events(
|
|
|
14
14
|
limit: int = 50
|
|
15
15
|
) -> str:
|
|
16
16
|
"""
|
|
17
|
-
Get recent events for a
|
|
17
|
+
Get recent events for a server by name or UUID.
|
|
18
|
+
|
|
19
|
+
Pass either value using instance_name, for example
|
|
20
|
+
get_server_events(instance_name="<server UUID>"). There is no server_id
|
|
21
|
+
parameter; do not use server_id or instance_id as argument names.
|
|
18
22
|
|
|
19
23
|
Args:
|
|
20
24
|
instance_name: Name or ID of the server instance
|
{mcp_openstack_ops-3.6.1 → mcp_openstack_ops-3.6.2}/src/mcp_openstack_ops/user_connection.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Request-local Application Credential authentication for remote clients."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
from contextvars import ContextVar
|
|
@@ -214,6 +215,26 @@ def authenticate(credential_id: str, secret: str) -> RequestIdentity:
|
|
|
214
215
|
raise
|
|
215
216
|
|
|
216
217
|
|
|
218
|
+
def _bad_request_reason(body: bytes) -> str:
|
|
219
|
+
"""Classify SDK rejections without exposing response text or request values."""
|
|
220
|
+
try:
|
|
221
|
+
payload = json.loads(body)
|
|
222
|
+
except (ValueError, RecursionError):
|
|
223
|
+
return "unclassified"
|
|
224
|
+
error = payload.get("error") if isinstance(payload, dict) else None
|
|
225
|
+
message = error.get("message") if isinstance(error, dict) else None
|
|
226
|
+
if isinstance(message, str):
|
|
227
|
+
for prefix, reason in (
|
|
228
|
+
("Parse error:", "invalid_json"),
|
|
229
|
+
("Validation error:", "invalid_jsonrpc"),
|
|
230
|
+
("Bad Request: Unsupported protocol version:", "unsupported_protocol"),
|
|
231
|
+
("Bad Request: Missing session ID", "missing_session_id"),
|
|
232
|
+
):
|
|
233
|
+
if message.startswith(prefix):
|
|
234
|
+
return reason
|
|
235
|
+
return "unclassified"
|
|
236
|
+
|
|
237
|
+
|
|
217
238
|
class UserConnectionMiddleware:
|
|
218
239
|
"""Authenticate every HTTP request and close its connection after delivery.
|
|
219
240
|
|
|
@@ -228,6 +249,22 @@ class UserConnectionMiddleware:
|
|
|
228
249
|
if scope["type"] != "http":
|
|
229
250
|
await self.app(scope, receive, send)
|
|
230
251
|
return
|
|
252
|
+
# This mode uses application credentials, not OAuth. Never forward these
|
|
253
|
+
# unsupported discovery/registration routes to an authenticated handler.
|
|
254
|
+
path = scope.get("path", "")
|
|
255
|
+
discovery_paths = (
|
|
256
|
+
"/.well-known/oauth-protected-resource",
|
|
257
|
+
"/.well-known/oauth-authorization-server",
|
|
258
|
+
"/.well-known/openid-configuration",
|
|
259
|
+
)
|
|
260
|
+
if path in ("/register", "/register/") or any(
|
|
261
|
+
path == base or path.startswith(base + "/")
|
|
262
|
+
for base in discovery_paths
|
|
263
|
+
):
|
|
264
|
+
await JSONResponse({"error": "Not found"}, status_code=404)(
|
|
265
|
+
scope, receive, send
|
|
266
|
+
)
|
|
267
|
+
return
|
|
231
268
|
headers = Headers(scope=scope)
|
|
232
269
|
ids = headers.getlist("x-openstack-application-credential-id")
|
|
233
270
|
secrets = headers.getlist("x-openstack-application-credential-secret")
|
|
@@ -283,11 +320,23 @@ class UserConnectionMiddleware:
|
|
|
283
320
|
(perf_counter() - started) * 1000,
|
|
284
321
|
)
|
|
285
322
|
status = 500
|
|
323
|
+
rejection_body = bytearray()
|
|
286
324
|
|
|
287
325
|
async def audit_send(message: Message) -> None:
|
|
288
326
|
nonlocal status
|
|
289
327
|
if message["type"] == "http.response.start":
|
|
290
328
|
status = message["status"]
|
|
329
|
+
elif status == 400 and message["type"] == "http.response.body":
|
|
330
|
+
# Bound diagnostic memory and never consume or log request bodies.
|
|
331
|
+
rejection_body.extend(
|
|
332
|
+
message.get("body", b"")[:8192 - len(rejection_body)]
|
|
333
|
+
)
|
|
334
|
+
if not message.get("more_body", False):
|
|
335
|
+
logger.info(
|
|
336
|
+
"OpenStack transport rejected request_id=%s "
|
|
337
|
+
"http_status=400 reason=%s",
|
|
338
|
+
request_id, _bad_request_reason(bytes(rejection_body)),
|
|
339
|
+
)
|
|
291
340
|
await send(message)
|
|
292
341
|
|
|
293
342
|
try:
|