django-spec-mcp 0.1.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.
- django_spec_mcp-0.1.0/.github/CODEOWNERS +1 -0
- django_spec_mcp-0.1.0/.github/SECURITY.md +9 -0
- django_spec_mcp-0.1.0/.github/dependabot.yml +26 -0
- django_spec_mcp-0.1.0/.github/workflows/release.yml +112 -0
- django_spec_mcp-0.1.0/.github/workflows/tests.yml +500 -0
- django_spec_mcp-0.1.0/.github/workflows/upstream-drift.yml +306 -0
- django_spec_mcp-0.1.0/.gitignore +636 -0
- django_spec_mcp-0.1.0/.gitleaksignore +2 -0
- django_spec_mcp-0.1.0/.pre-commit-config.yaml +83 -0
- django_spec_mcp-0.1.0/CHANGELOG.md +514 -0
- django_spec_mcp-0.1.0/CLAUDE.md +382 -0
- django_spec_mcp-0.1.0/LICENSE +21 -0
- django_spec_mcp-0.1.0/Makefile +94 -0
- django_spec_mcp-0.1.0/PKG-INFO +241 -0
- django_spec_mcp-0.1.0/README.md +199 -0
- django_spec_mcp-0.1.0/django_spec_mcp/__init__.py +103 -0
- django_spec_mcp-0.1.0/django_spec_mcp/_compat/__init__.py +4 -0
- django_spec_mcp-0.1.0/django_spec_mcp/_compat/acall.py +33 -0
- django_spec_mcp-0.1.0/django_spec_mcp/_compat/reject_awaitable.py +53 -0
- django_spec_mcp-0.1.0/django_spec_mcp/_compat/tracing.py +58 -0
- django_spec_mcp-0.1.0/django_spec_mcp/_compat/utils.py +22 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/__init__.py +1 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/check_kernel_spec_guarded.py +51 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/kernel_selector_to_resource.py +75 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/kernel_spec_to_tool.py +163 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/ui_to_resource.py +199 -0
- django_spec_mcp-0.1.0/django_spec_mcp/adapters/utils.py +91 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/__init__.py +15 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/audience.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/backends/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/backends/allow_any_backend.py +39 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/bearer_challenge.py +34 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/insufficient_scope_response.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/principal_for_token.py +57 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/protected_resource_metadata_view.py +63 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/__init__.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/fixed_window_rate_limit.py +68 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/sliding_window_rate_limit.py +75 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/token_bucket_rate_limit.py +85 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/types/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/types/mcp_rate_limit.py +30 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/utils.py +24 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/types/__init__.py +9 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/types/auth_backend.py +39 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/types/protected_resource_metadata.py +39 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/types/self_checking.py +42 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/types/token_info.py +38 -0
- django_spec_mcp-0.1.0/django_spec_mcp/auth/unauthenticated_response.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/conf.py +77 -0
- django_spec_mcp-0.1.0/django_spec_mcp/config/__init__.py +4 -0
- django_spec_mcp-0.1.0/django_spec_mcp/config/build_mcp_config.py +153 -0
- django_spec_mcp-0.1.0/django_spec_mcp/config/types/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/config/types/mcp_config.py +191 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/__init__.py +52 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/build_requested_schema.py +109 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/can_ask_client.py +26 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/fingerprint_request.py +35 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/read_elicit_answer.py +40 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/sign_request_state.py +28 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/__init__.py +13 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/elicit_answer.py +22 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/elicit_request.py +41 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/input_required_result.py +65 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/request_state.py +82 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/resolved_input.py +41 -0
- django_spec_mcp-0.1.0/django_spec_mcp/elicitation/verify_request_state.py +56 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/__init__.py +23 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/answer_kernel_refusal.py +26 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/async_dispatch.py +76 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/call_kernel_tool.py +118 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/dispatch.py +77 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_completion_complete.py +285 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_initialize.py +135 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_ping.py +18 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_get.py +84 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_get_async.py +64 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_list.py +54 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_list.py +62 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_read.py +101 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_read_async.py +96 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_templates_list.py +59 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_server_discover.py +61 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_cancel.py +59 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_get.py +50 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_update.py +93 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_call.py +153 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_call_async.py +153 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_list.py +70 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/input_dispatch.py +179 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/invalidation_dispatch.py +83 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/is_binding_listable.py +121 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/judge_binding.py +62 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_refusals.py +156 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_tool_entry.py +60 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_utils.py +1060 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/listable_tool_bindings.py +38 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/offered_tools.py +180 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/pagination.py +74 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/present_kernel_result.py +63 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/read_kernel_resource.py +66 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/read_kernel_resource_async.py +65 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/render_prompt_messages.py +36 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/resolve_call_input.py +32 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/resource_presentation_refusal.py +40 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/run_kernel_tool.py +79 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/run_kernel_tool_async.py +81 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/runs_on_kernel.py +22 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/task_dispatch.py +138 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/tasks_utils.py +112 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/context.py +138 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/kernel_input_round.py +24 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/kernel_refusal_scene.py +30 -0
- django_spec_mcp-0.1.0/django_spec_mcp/handlers/utils.py +687 -0
- django_spec_mcp-0.1.0/django_spec_mcp/observability/__init__.py +34 -0
- django_spec_mcp-0.1.0/django_spec_mcp/observability/get_logger.py +18 -0
- django_spec_mcp-0.1.0/django_spec_mcp/observability/session_fingerprint.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/__init__.py +9 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/build_content_blocks.py +154 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/build_resource_contents.py +34 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/encode_json.py +56 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/encode_resource_contents.py +85 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/enforce_result_bytes.py +51 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/error_tool_result.py +51 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/resolve_structured_output.py +55 -0
- django_spec_mcp-0.1.0/django_spec_mcp/output/tool_result.py +93 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/__init__.py +47 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/build_server_info.py +83 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/parse_message.py +50 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/__init__.py +45 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/client_capabilities.py +42 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/completion.py +31 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/discover_result.py +48 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/get_prompt_result.py +27 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/icon.py +63 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/implementation.py +52 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/initialize_params.py +38 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/initialize_result.py +30 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_error.py +26 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_message.py +9 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_notification.py +24 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_request.py +26 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_response.py +49 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt.py +46 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt_argument.py +29 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt_message.py +42 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource.py +48 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource_contents.py +34 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource_template.py +45 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/server_capabilities.py +56 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/task.py +72 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool.py +52 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool_content_block.py +177 -0
- django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool_result.py +49 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/__init__.py +5 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/prompt_registry.py +35 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/resource_registry.py +92 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/tool_registry.py +77 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/__init__.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/kernel_resource_binding.py +51 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/kernel_tool_binding.py +189 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/prompt_binding.py +79 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/resource_binding.py +108 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_csp.py +52 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_resource_meta.py +55 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_tool_meta.py +44 -0
- django_spec_mcp-0.1.0/django_spec_mcp/registry/types/utils.py +112 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/__init__.py +13 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/agent_conventions.py +115 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/kernel_tool_input_schema.py +98 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/kernel_tool_output_schema.py +80 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/types/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/types/agent_conventions.py +185 -0
- django_spec_mcp-0.1.0/django_spec_mcp/schema/utils.py +22 -0
- django_spec_mcp-0.1.0/django_spec_mcp/server/__init__.py +3 -0
- django_spec_mcp-0.1.0/django_spec_mcp/server/mcp_server.py +1716 -0
- django_spec_mcp-0.1.0/django_spec_mcp/server/utils.py +247 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/__init__.py +24 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/grant_subscription.py +190 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/in_memory_subscription_broker.py +83 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/publish_after_task.py +39 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/publish_invalidations.py +81 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/redis_subscription_broker.py +174 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/render_invalidations.py +81 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/__init__.py +4 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/subscription_broker.py +54 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/subscription_filter.py +71 -0
- django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/utils.py +49 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/__init__.py +36 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/build_worker_token.py +69 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/create_task.py +81 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/django_cache_task_store.py +167 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/in_memory_task_store.py +41 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/report_task_progress.py +90 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/run_task.py +102 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/transition_task.py +67 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/__init__.py +5 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_executor.py +42 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_record.py +108 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_store.py +50 -0
- django_spec_mcp-0.1.0/django_spec_mcp/tasks/utils.py +54 -0
- django_spec_mcp-0.1.0/django_spec_mcp/testing/__init__.py +16 -0
- django_spec_mcp-0.1.0/django_spec_mcp/testing/assert_tool_result_conforms.py +200 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/__init__.py +21 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/async_streamable_http_view.py +624 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/django_cache_session_store.py +176 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_session_store.py +32 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_sse_broker.py +85 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_sse_replay_buffer.py +91 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/negotiate_protocol_version.py +61 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/origin_validation.py +26 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/progress_dispatch.py +191 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/protocol_version.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/redis_sse_broker.py +183 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/redis_sse_replay_buffer.py +157 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/response_stream.py +235 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/sse_response.py +139 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/streamable_http_view.py +489 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/subscription_stream.py +171 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/types/__init__.py +5 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/types/request_metadata.py +77 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/types/session_store.py +32 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/types/sse_broker.py +53 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/types/sse_replay_buffer.py +61 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/utils.py +555 -0
- django_spec_mcp-0.1.0/django_spec_mcp/transport/validate_modern_request.py +137 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/__init__.py +89 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/cache_scope.py +22 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/client_capabilities_meta_key.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/client_info_meta_key.py +8 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/elicit_action.py +20 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_create_method.py +15 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_key.py +8 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_scalar_types.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/icon_theme.py +18 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/internal_error_message.py +16 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/json_rpc_error_code.py +46 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/json_rpc_id.py +8 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/jsonrpc_version.py +7 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/max_completion_values.py +7 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/mcp_error_header.py +18 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/modern_protocol_versions.py +14 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/notification_kind.py +39 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/progress_token_meta_key.py +12 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/protocol_version_meta_key.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/request_state_salt.py +10 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/reserved_post_fetch_keys.py +18 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/resource_encoding.py +36 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/resource_updated_method.py +6 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/result_type.py +33 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/server_info_meta_key.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/session_missing_hint.py +6 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/session_unknown_hint.py +9 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/sessionless_methods.py +13 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/subscription_id_meta_key.py +12 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/subscriptions_acknowledged_method.py +9 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/subscriptions_listen_method.py +8 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/task_methods.py +12 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/task_policy.py +30 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/task_status.py +36 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/task_status_method.py +10 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/tasks_extension_id.py +13 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/tool_content_kind.py +38 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/ui_extension_id.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/ui_meta_key.py +7 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/ui_permission.py +19 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/ui_resource_mime_type.py +8 -0
- django_spec_mcp-0.1.0/django_spec_mcp/types/ui_visibility.py +24 -0
- django_spec_mcp-0.1.0/django_spec_mcp/ui/__init__.py +11 -0
- django_spec_mcp-0.1.0/django_spec_mcp/ui/bridge.js +468 -0
- django_spec_mcp-0.1.0/django_spec_mcp/ui/build_app_document.py +124 -0
- django_spec_mcp-0.1.0/django_spec_mcp/version.py +5 -0
- django_spec_mcp-0.1.0/docs/async.md +184 -0
- django_spec_mcp-0.1.0/docs/auth.md +453 -0
- django_spec_mcp-0.1.0/docs/concepts.md +2369 -0
- django_spec_mcp-0.1.0/docs/index.md +128 -0
- django_spec_mcp-0.1.0/docs/moving-from-drf-mcp.md +355 -0
- django_spec_mcp-0.1.0/docs/observability.md +155 -0
- django_spec_mcp-0.1.0/docs/performance.md +160 -0
- django_spec_mcp-0.1.0/docs/prompts.md +126 -0
- django_spec_mcp-0.1.0/docs/quickstart.md +182 -0
- django_spec_mcp-0.1.0/docs/recipes/agent-audience.md +126 -0
- django_spec_mcp-0.1.0/docs/recipes/async-auth-backend.md +138 -0
- django_spec_mcp-0.1.0/docs/recipes/custom-permission.md +138 -0
- django_spec_mcp-0.1.0/docs/recipes/expose-a-service.md +44 -0
- django_spec_mcp-0.1.0/docs/recipes/index.md +35 -0
- django_spec_mcp-0.1.0/docs/recipes/interactive-view.md +208 -0
- django_spec_mcp-0.1.0/docs/recipes/migrating.md +280 -0
- django_spec_mcp-0.1.0/docs/recipes/pydantic-ai-client.md +239 -0
- django_spec_mcp-0.1.0/docs/recipes/rate-limiting.md +162 -0
- django_spec_mcp-0.1.0/docs/recipes/redis-sse-broker.md +80 -0
- django_spec_mcp-0.1.0/docs/recipes/register-from-spec-registry.md +245 -0
- django_spec_mcp-0.1.0/docs/recipes/selector-tool-with-filterset.md +311 -0
- django_spec_mcp-0.1.0/docs/recipes/sse-replay-buffer.md +143 -0
- django_spec_mcp-0.1.0/docs/recipes/swap-session-store.md +83 -0
- django_spec_mcp-0.1.0/docs/reference/auth.md +51 -0
- django_spec_mcp-0.1.0/docs/reference/index.md +14 -0
- django_spec_mcp-0.1.0/docs/reference/mcp-server.md +12 -0
- django_spec_mcp-0.1.0/docs/reference/output.md +30 -0
- django_spec_mcp-0.1.0/docs/reference/protocol.md +87 -0
- django_spec_mcp-0.1.0/docs/reference/registries.md +117 -0
- django_spec_mcp-0.1.0/docs/reference/settings.md +140 -0
- django_spec_mcp-0.1.0/docs/reference/testing.md +76 -0
- django_spec_mcp-0.1.0/docs/troubleshooting.md +217 -0
- django_spec_mcp-0.1.0/examples/README.md +62 -0
- django_spec_mcp-0.1.0/examples/invoicing/README.md +202 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/apps.py +8 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/auth.py +67 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/filters.py +32 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/forms.py +18 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/mcp.py +299 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/migrations/0001_initial.py +33 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/migrations/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/models.py +18 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/presenters.py +52 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/selectors.py +78 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/services.py +68 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoices/shapes.py +30 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoicing/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoicing/asgi.py +14 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoicing/settings.py +54 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoicing/urls.py +15 -0
- django_spec_mcp-0.1.0/examples/invoicing/invoicing/wsgi.py +16 -0
- django_spec_mcp-0.1.0/examples/invoicing/manage.py +22 -0
- django_spec_mcp-0.1.0/examples/job_status/README.md +106 -0
- django_spec_mcp-0.1.0/examples/job_status/job_status/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/job_status/job_status/asgi.py +8 -0
- django_spec_mcp-0.1.0/examples/job_status/job_status/settings.py +45 -0
- django_spec_mcp-0.1.0/examples/job_status/job_status/urls.py +13 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/apps.py +8 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/mcp.py +106 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/migrations/0001_initial.py +48 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/migrations/__init__.py +0 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/models.py +29 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/selectors.py +18 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/services.py +68 -0
- django_spec_mcp-0.1.0/examples/job_status/jobs/shapes.py +18 -0
- django_spec_mcp-0.1.0/examples/job_status/manage.py +18 -0
- django_spec_mcp-0.1.0/mkdocs.yml +120 -0
- django_spec_mcp-0.1.0/pyproject.toml +243 -0
- django_spec_mcp-0.1.0/scripts/benchmark.py +217 -0
- django_spec_mcp-0.1.0/scripts/check-changelog +148 -0
- django_spec_mcp-0.1.0/scripts/check_docs_snippets.py +189 -0
- django_spec_mcp-0.1.0/scripts/interop_pydantic_ai.py +215 -0
- django_spec_mcp-0.1.0/scripts/release-publish.sh +246 -0
- django_spec_mcp-0.1.0/tests/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/_compat/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/_compat/test_reject_awaitable.py +169 -0
- django_spec_mcp-0.1.0/tests/adapters/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/adapters/test_meta_passthrough.py +309 -0
- django_spec_mcp-0.1.0/tests/adapters/test_required_param_sources.py +207 -0
- django_spec_mcp-0.1.0/tests/adapters/test_reserved_input_fields.py +138 -0
- django_spec_mcp-0.1.0/tests/adapters/test_selector_to_resource.py +134 -0
- django_spec_mcp-0.1.0/tests/adapters/test_service_to_tool.py +170 -0
- django_spec_mcp-0.1.0/tests/adapters/test_tool_annotations.py +155 -0
- django_spec_mcp-0.1.0/tests/adapters/test_ui_to_resource.py +319 -0
- django_spec_mcp-0.1.0/tests/auth/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/auth/backends/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/auth/backends/test_allow_any_backend.py +39 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/conftest.py +13 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/test_fixed_window_rate_limit.py +17 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/test_rate_limit_contract.py +104 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/test_sliding_window_rate_limit.py +33 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/test_token_bucket_rate_limit.py +46 -0
- django_spec_mcp-0.1.0/tests/auth/rate_limits/utils.py +15 -0
- django_spec_mcp-0.1.0/tests/auth/test_audience.py +28 -0
- django_spec_mcp-0.1.0/tests/auth/test_bearer_challenge.py +64 -0
- django_spec_mcp-0.1.0/tests/auth/test_principal_for_token.py +75 -0
- django_spec_mcp-0.1.0/tests/auth/test_protected_resource_metadata.py +34 -0
- django_spec_mcp-0.1.0/tests/auth/test_response_builders.py +29 -0
- django_spec_mcp-0.1.0/tests/auth/test_token_info.py +23 -0
- django_spec_mcp-0.1.0/tests/auth/test_worked_backend.py +96 -0
- django_spec_mcp-0.1.0/tests/conformance/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/conformance/conftest.py +13 -0
- django_spec_mcp-0.1.0/tests/conformance/mcp.py +87 -0
- django_spec_mcp-0.1.0/tests/conformance/test_error_codes.py +126 -0
- django_spec_mcp-0.1.0/tests/conformance/test_register_tools_parity.py +113 -0
- django_spec_mcp-0.1.0/tests/conformance/test_spec_field_honoring.py +31 -0
- django_spec_mcp-0.1.0/tests/conformance/test_unknown_arguments_matrix.py +102 -0
- django_spec_mcp-0.1.0/tests/conformance/test_urls.py +30 -0
- django_spec_mcp-0.1.0/tests/conformance/urls.py +36 -0
- django_spec_mcp-0.1.0/tests/conftest.py +81 -0
- django_spec_mcp-0.1.0/tests/conftest_settings.py +67 -0
- django_spec_mcp-0.1.0/tests/elicitation/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/elicitation/conftest.py +135 -0
- django_spec_mcp-0.1.0/tests/elicitation/test_asking_for_input.py +521 -0
- django_spec_mcp-0.1.0/tests/elicitation/test_elicitation_endtoend.py +133 -0
- django_spec_mcp-0.1.0/tests/elicitation/test_request_state.py +146 -0
- django_spec_mcp-0.1.0/tests/elicitation/test_requested_schema.py +140 -0
- django_spec_mcp-0.1.0/tests/elicitation/test_wire_shapes.py +155 -0
- django_spec_mcp-0.1.0/tests/elicitation/urls.py +18 -0
- django_spec_mcp-0.1.0/tests/handlers/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/handlers/test_a_dataclass_input_is_laid_back.py +141 -0
- django_spec_mcp-0.1.0/tests/handlers/test_a_denied_caller_learns_nothing_of_the_target.py +280 -0
- django_spec_mcp-0.1.0/tests/handlers/test_a_kernel_spec_answers_before_the_charge.py +1188 -0
- django_spec_mcp-0.1.0/tests/handlers/test_a_namesake_default_is_evaluated_off_the_event_loop.py +79 -0
- django_spec_mcp-0.1.0/tests/handlers/test_a_permission_writing_its_arguments.py +209 -0
- django_spec_mcp-0.1.0/tests/handlers/test_an_answer_is_judged_on_the_arguments_it_changes.py +205 -0
- django_spec_mcp-0.1.0/tests/handlers/test_an_answer_is_judged_on_the_route_it_names.py +380 -0
- django_spec_mcp-0.1.0/tests/handlers/test_argument_validation_is_a_tool_error.py +263 -0
- django_spec_mcp-0.1.0/tests/handlers/test_async_handler_branches.py +289 -0
- django_spec_mcp-0.1.0/tests/handlers/test_call_spec_tool.py +307 -0
- django_spec_mcp-0.1.0/tests/handlers/test_capability_honesty.py +223 -0
- django_spec_mcp-0.1.0/tests/handlers/test_closed_service_schema.py +180 -0
- django_spec_mcp-0.1.0/tests/handlers/test_completion_complete.py +708 -0
- django_spec_mcp-0.1.0/tests/handlers/test_copy_request.py +459 -0
- django_spec_mcp-0.1.0/tests/handlers/test_dispatch_gains.py +142 -0
- django_spec_mcp-0.1.0/tests/handlers/test_every_route_strips_pagination_keys.py +183 -0
- django_spec_mcp-0.1.0/tests/handlers/test_handler_branches.py +404 -0
- django_spec_mcp-0.1.0/tests/handlers/test_handler_error_paths.py +123 -0
- django_spec_mcp-0.1.0/tests/handlers/test_instance_selector_tools.py +289 -0
- django_spec_mcp-0.1.0/tests/handlers/test_is_binding_listable.py +344 -0
- django_spec_mcp-0.1.0/tests/handlers/test_judge_binding.py +491 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_handbuilt_bindings.py +176 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_presenter_refusal.py +305 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_refusals.py +318 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_resource_object_check.py +113 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_selector_rounds.py +142 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_shell_knobs.py +132 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_tool_calls.py +379 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kernel_unguarded.py +190 -0
- django_spec_mcp-0.1.0/tests/handlers/test_kwargs_providers.py +218 -0
- django_spec_mcp-0.1.0/tests/handlers/test_list_output_conforms.py +183 -0
- django_spec_mcp-0.1.0/tests/handlers/test_listings_filter.py +529 -0
- django_spec_mcp-0.1.0/tests/handlers/test_many_argument_tools.py +275 -0
- django_spec_mcp-0.1.0/tests/handlers/test_missing_required_argument.py +509 -0
- django_spec_mcp-0.1.0/tests/handlers/test_offered_tools.py +488 -0
- django_spec_mcp-0.1.0/tests/handlers/test_outbound_bounds.py +603 -0
- django_spec_mcp-0.1.0/tests/handlers/test_pagination.py +93 -0
- django_spec_mcp-0.1.0/tests/handlers/test_pagination_handlers.py +115 -0
- django_spec_mcp-0.1.0/tests/handlers/test_pool_seed_precedence.py +355 -0
- django_spec_mcp-0.1.0/tests/handlers/test_prompts.py +395 -0
- django_spec_mcp-0.1.0/tests/handlers/test_rate_limits.py +126 -0
- django_spec_mcp-0.1.0/tests/handlers/test_request_user_is_the_tokens_principal.py +342 -0
- django_spec_mcp-0.1.0/tests/handlers/test_resource_permissions_see_uri_variables.py +164 -0
- django_spec_mcp-0.1.0/tests/handlers/test_resources_read_dataclass_output.py +368 -0
- django_spec_mcp-0.1.0/tests/handlers/test_retrieve_tool_missing.py +165 -0
- django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_dispatch.py +438 -0
- django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_dispatch_async.py +95 -0
- django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_ordering.py +180 -0
- django_spec_mcp-0.1.0/tests/handlers/test_server_discover.py +207 -0
- django_spec_mcp-0.1.0/tests/handlers/test_service_error_code.py +121 -0
- django_spec_mcp-0.1.0/tests/handlers/test_spec_shaping_and_context.py +163 -0
- django_spec_mcp-0.1.0/tests/handlers/test_tools_list_affordances.py +197 -0
- django_spec_mcp-0.1.0/tests/handlers/test_ui_resources.py +253 -0
- django_spec_mcp-0.1.0/tests/handlers/test_unknown_arguments_policy.py +188 -0
- django_spec_mcp-0.1.0/tests/handlers/test_view_read_async_encodes_off_the_loop.py +45 -0
- django_spec_mcp-0.1.0/tests/handlers/test_view_resources.py +113 -0
- django_spec_mcp-0.1.0/tests/kernel_client.py +55 -0
- django_spec_mcp-0.1.0/tests/output/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/output/conftest.py +9 -0
- django_spec_mcp-0.1.0/tests/output/test_build_resource_contents.py +185 -0
- django_spec_mcp-0.1.0/tests/output/test_content_blocks.py +338 -0
- django_spec_mcp-0.1.0/tests/output/test_encode_json.py +108 -0
- django_spec_mcp-0.1.0/tests/output/test_encode_resource_contents.py +305 -0
- django_spec_mcp-0.1.0/tests/output/test_enforce_result_bytes.py +41 -0
- django_spec_mcp-0.1.0/tests/output/test_error_tool_result.py +32 -0
- django_spec_mcp-0.1.0/tests/output/test_resolve_structured_output.py +91 -0
- django_spec_mcp-0.1.0/tests/output/test_tool_result.py +92 -0
- django_spec_mcp-0.1.0/tests/protocol/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/protocol/test_build_server_info.py +42 -0
- django_spec_mcp-0.1.0/tests/protocol/test_dataclasses.py +369 -0
- django_spec_mcp-0.1.0/tests/protocol/test_icon.py +55 -0
- django_spec_mcp-0.1.0/tests/protocol/test_wire_meta.py +95 -0
- django_spec_mcp-0.1.0/tests/registry/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/registry/test_prompt_registry.py +28 -0
- django_spec_mcp-0.1.0/tests/registry/test_resource_registry.py +112 -0
- django_spec_mcp-0.1.0/tests/registry/test_tool_registry.py +38 -0
- django_spec_mcp-0.1.0/tests/registry/types/test_audience_projection_override.py +87 -0
- django_spec_mcp-0.1.0/tests/schema/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/schema/test_agent_conventions.py +101 -0
- django_spec_mcp-0.1.0/tests/schema/test_agent_output_schema.py +142 -0
- django_spec_mcp-0.1.0/tests/schema/test_dropped_edge_seeds_are_not_advertised.py +95 -0
- django_spec_mcp-0.1.0/tests/schema/test_input_output_schema.py +141 -0
- django_spec_mcp-0.1.0/tests/schema/test_kernel_output_presents_nothing.py +210 -0
- django_spec_mcp-0.1.0/tests/schema/test_kernel_tool_input_schema.py +138 -0
- django_spec_mcp-0.1.0/tests/schema/test_selector_tool_requiredness.py +349 -0
- django_spec_mcp-0.1.0/tests/schema/test_selector_tool_schema.py +128 -0
- django_spec_mcp-0.1.0/tests/schema/test_service_tool_schema.py +139 -0
- django_spec_mcp-0.1.0/tests/schema/test_service_tool_spread_parameters.py +286 -0
- django_spec_mcp-0.1.0/tests/schema/test_service_tool_target_lookup.py +264 -0
- django_spec_mcp-0.1.0/tests/schema/test_utils.py +21 -0
- django_spec_mcp-0.1.0/tests/schema/types/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/schema/types/test_agent_conventions.py +213 -0
- django_spec_mcp-0.1.0/tests/server/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/server/test_a_mount_hands_the_view_every_collaborator.py +55 -0
- django_spec_mcp-0.1.0/tests/server/test_agent_contract_registration.py +123 -0
- django_spec_mcp-0.1.0/tests/server/test_call_seed_names_on_a_kernel_tool.py +136 -0
- django_spec_mcp-0.1.0/tests/server/test_mcp_server.py +186 -0
- django_spec_mcp-0.1.0/tests/server/test_mcp_server_in_process.py +280 -0
- django_spec_mcp-0.1.0/tests/server/test_mcp_server_notify.py +82 -0
- django_spec_mcp-0.1.0/tests/server/test_mcp_server_unavailable_tools.py +337 -0
- django_spec_mcp-0.1.0/tests/server/test_mount_checks_auth_backend.py +90 -0
- django_spec_mcp-0.1.0/tests/server/test_register_kernel_specs.py +511 -0
- django_spec_mcp-0.1.0/tests/server/test_registration_compiles_the_spec.py +107 -0
- django_spec_mcp-0.1.0/tests/server/test_resource_and_prompt_registration_guards.py +166 -0
- django_spec_mcp-0.1.0/tests/server/test_selector_parameter_shadowing.py +113 -0
- django_spec_mcp-0.1.0/tests/server/test_server_arguments.py +344 -0
- django_spec_mcp-0.1.0/tests/server/test_server_arguments_on_every_registrar.py +111 -0
- django_spec_mcp-0.1.0/tests/server/test_ui_tool_linkage.py +201 -0
- django_spec_mcp-0.1.0/tests/server/test_unbounded_list_warning.py +161 -0
- django_spec_mcp-0.1.0/tests/server/test_undescribed_warning.py +137 -0
- django_spec_mcp-0.1.0/tests/subscriptions/kernel_urls.py +67 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_invalidates.py +491 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_redis_subscription_broker.py +238 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_subscription_core.py +598 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_subscription_stream.py +234 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_subscriptions_endtoend.py +308 -0
- django_spec_mcp-0.1.0/tests/subscriptions/test_task_notifications.py +243 -0
- django_spec_mcp-0.1.0/tests/tasks/conftest.py +150 -0
- django_spec_mcp-0.1.0/tests/tasks/test_a_task_runs_with_the_bound_value.py +83 -0
- django_spec_mcp-0.1.0/tests/tasks/test_task_lifecycle.py +544 -0
- django_spec_mcp-0.1.0/tests/tasks/test_task_progress.py +214 -0
- django_spec_mcp-0.1.0/tests/tasks/test_task_stores.py +364 -0
- django_spec_mcp-0.1.0/tests/tasks/test_task_wire_shape.py +392 -0
- django_spec_mcp-0.1.0/tests/tasks/test_tasks_endtoend.py +288 -0
- django_spec_mcp-0.1.0/tests/tasks/test_tasks_methods.py +276 -0
- django_spec_mcp-0.1.0/tests/test_affordances_endtoend.py +106 -0
- django_spec_mcp-0.1.0/tests/test_agent_audience_endtoend.py +107 -0
- django_spec_mcp-0.1.0/tests/test_agent_conventions_endtoend.py +212 -0
- django_spec_mcp-0.1.0/tests/test_conf.py +27 -0
- django_spec_mcp-0.1.0/tests/test_consumer_ergonomics.py +171 -0
- django_spec_mcp-0.1.0/tests/test_display_metadata.py +80 -0
- django_spec_mcp-0.1.0/tests/test_dropped_surface.py +258 -0
- django_spec_mcp-0.1.0/tests/test_empty_result_endtoend.py +385 -0
- django_spec_mcp-0.1.0/tests/test_endtoend.py +237 -0
- django_spec_mcp-0.1.0/tests/test_examples_smoke.py +708 -0
- django_spec_mcp-0.1.0/tests/test_icons_endtoend.py +194 -0
- django_spec_mcp-0.1.0/tests/test_kernel_endtoend.py +83 -0
- django_spec_mcp-0.1.0/tests/test_multi_server.py +158 -0
- django_spec_mcp-0.1.0/tests/test_offered_tools_endtoend.py +131 -0
- django_spec_mcp-0.1.0/tests/test_pool_seeds_endtoend.py +371 -0
- django_spec_mcp-0.1.0/tests/test_prompts_endtoend.py +89 -0
- django_spec_mcp-0.1.0/tests/test_public_surface.py +101 -0
- django_spec_mcp-0.1.0/tests/test_re_exports.py +51 -0
- django_spec_mcp-0.1.0/tests/test_root_re_exports.py +43 -0
- django_spec_mcp-0.1.0/tests/test_scripts_smoke.py +156 -0
- django_spec_mcp-0.1.0/tests/test_stored_names.py +69 -0
- django_spec_mcp-0.1.0/tests/test_tracing.py +197 -0
- django_spec_mcp-0.1.0/tests/test_validation_value_echo.py +206 -0
- django_spec_mcp-0.1.0/tests/testapp/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/testapp/affordances.py +67 -0
- django_spec_mcp-0.1.0/tests/testapp/apps.py +9 -0
- django_spec_mcp-0.1.0/tests/testapp/async_urls.py +11 -0
- django_spec_mcp-0.1.0/tests/testapp/auth_backend.py +36 -0
- django_spec_mcp-0.1.0/tests/testapp/conventions.py +111 -0
- django_spec_mcp-0.1.0/tests/testapp/kernel_shapes.py +37 -0
- django_spec_mcp-0.1.0/tests/testapp/mcp.py +105 -0
- django_spec_mcp-0.1.0/tests/testapp/models.py +15 -0
- django_spec_mcp-0.1.0/tests/testapp/multi_urls.py +36 -0
- django_spec_mcp-0.1.0/tests/testapp/permissions.py +18 -0
- django_spec_mcp-0.1.0/tests/testapp/services.py +43 -0
- django_spec_mcp-0.1.0/tests/testapp/templates/mcp_ui/body.html +9 -0
- django_spec_mcp-0.1.0/tests/testapp/templates/mcp_ui/view.html +5 -0
- django_spec_mcp-0.1.0/tests/testapp/urlconf_for.py +35 -0
- django_spec_mcp-0.1.0/tests/testapp/urls.py +11 -0
- django_spec_mcp-0.1.0/tests/testing/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/testing/test_assert_tool_result_conforms.py +252 -0
- django_spec_mcp-0.1.0/tests/transport/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/transport/conftest.py +87 -0
- django_spec_mcp-0.1.0/tests/transport/test_a_challenge_names_an_error_only_for_a_credential_sent.py +103 -0
- django_spec_mcp-0.1.0/tests/transport/test_a_session_rejection_logs_on_its_views_logger.py +27 -0
- django_spec_mcp-0.1.0/tests/transport/test_accept_naming_only_a_stream.py +76 -0
- django_spec_mcp-0.1.0/tests/transport/test_agent_conventions_over_http.py +86 -0
- django_spec_mcp-0.1.0/tests/transport/test_an_auth_backend_that_raises.py +81 -0
- django_spec_mcp-0.1.0/tests/transport/test_async_streamable_http_view.py +405 -0
- django_spec_mcp-0.1.0/tests/transport/test_dispatch_backstop.py +689 -0
- django_spec_mcp-0.1.0/tests/transport/test_endpoint_http_semantics.py +371 -0
- django_spec_mcp-0.1.0/tests/transport/test_in_memory_sse_replay_buffer.py +87 -0
- django_spec_mcp-0.1.0/tests/transport/test_json_bodies_use_the_json_dumps_defaults.py +66 -0
- django_spec_mcp-0.1.0/tests/transport/test_modern_era.py +415 -0
- django_spec_mcp-0.1.0/tests/transport/test_origin_validation.py +30 -0
- django_spec_mcp-0.1.0/tests/transport/test_progress_stream.py +693 -0
- django_spec_mcp-0.1.0/tests/transport/test_protocol_version.py +80 -0
- django_spec_mcp-0.1.0/tests/transport/test_redis_sse_broker.py +181 -0
- django_spec_mcp-0.1.0/tests/transport/test_redis_sse_replay_buffer.py +205 -0
- django_spec_mcp-0.1.0/tests/transport/test_review_findings.py +271 -0
- django_spec_mcp-0.1.0/tests/transport/test_session_security.py +268 -0
- django_spec_mcp-0.1.0/tests/transport/test_session_stores.py +171 -0
- django_spec_mcp-0.1.0/tests/transport/test_sessionless_legacy.py +225 -0
- django_spec_mcp-0.1.0/tests/transport/test_sse_broker.py +96 -0
- django_spec_mcp-0.1.0/tests/transport/test_sse_endpoint.py +255 -0
- django_spec_mcp-0.1.0/tests/transport/test_sse_response.py +207 -0
- django_spec_mcp-0.1.0/tests/transport/test_streamable_http_view.py +296 -0
- django_spec_mcp-0.1.0/tests/transport/test_streamable_http_view_branches.py +211 -0
- django_spec_mcp-0.1.0/tests/transport/test_the_request_carries_the_tokens_principal.py +255 -0
- django_spec_mcp-0.1.0/tests/transport/test_unauthenticated_method_support.py +216 -0
- django_spec_mcp-0.1.0/tests/transport/test_utils.py +22 -0
- django_spec_mcp-0.1.0/tests/transport/types/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/transport/types/test_sse_replay_buffer.py +94 -0
- django_spec_mcp-0.1.0/tests/types/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/types/test_json_rpc_error_code.py +14 -0
- django_spec_mcp-0.1.0/tests/types/test_jsonrpc_version.py +9 -0
- django_spec_mcp-0.1.0/tests/types/test_reserved_post_fetch_keys.py +19 -0
- django_spec_mcp-0.1.0/tests/types/test_types_imports.py +45 -0
- django_spec_mcp-0.1.0/tests/ui/__init__.py +0 -0
- django_spec_mcp-0.1.0/tests/ui/bridge_harness.mjs +370 -0
- django_spec_mcp-0.1.0/tests/ui/test_bridge.py +252 -0
- django_spec_mcp-0.1.0/tests/ui/test_build_app_document.py +131 -0
- django_spec_mcp-0.1.0/tests/utils.py +226 -0
- django_spec_mcp-0.1.0/uv.lock +1816 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @Artui
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Please report security issues **privately** via GitHub's
|
|
6
|
+
[Report a vulnerability](../../security/advisories/new) button rather than opening a public issue.
|
|
7
|
+
I aim to acknowledge reports within 72 hours.
|
|
8
|
+
|
|
9
|
+
Supported: the latest released version on the default branch.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
# Dependabot already holds a new version for 3 days before proposing it; a
|
|
8
|
+
# week is the more useful threshold. A compromised release is typically
|
|
9
|
+
# yanked within days, so the gap between "published" and "found" is exactly
|
|
10
|
+
# the window an automatic upgrade walks into. This costs nothing in
|
|
11
|
+
# responsiveness that matters: cooldown covers version updates only, and
|
|
12
|
+
# security advisories are proposed immediately regardless.
|
|
13
|
+
cooldown:
|
|
14
|
+
default-days: 7
|
|
15
|
+
groups:
|
|
16
|
+
actions:
|
|
17
|
+
patterns: ["*"]
|
|
18
|
+
- package-ecosystem: "uv"
|
|
19
|
+
directory: "/"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: "weekly"
|
|
22
|
+
cooldown:
|
|
23
|
+
default-days: 7
|
|
24
|
+
groups:
|
|
25
|
+
python:
|
|
26
|
+
patterns: ["*"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Main-triggered: every merge to `main` runs `release-publish-prepare`, which
|
|
4
|
+
# short-circuits to a no-op unless the version in source has been bumped past
|
|
5
|
+
# the most recent `vX.Y.Z` tag. The previous tag-trigger flow has been removed
|
|
6
|
+
# — `make release-bump` + merge to main is now the only path.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: release-main
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
# Actions are pinned to a commit SHA rather than a tag: a tag is mutable, and an
|
|
16
|
+
# action owner (or anyone who takes over the account) can silently repoint one at
|
|
17
|
+
# new code that then runs with this workflow's token. The trailing version comment
|
|
18
|
+
# is not decoration -- Dependabot reads it to know which release the SHA is, and
|
|
19
|
+
# rewrites both together when a newer one ships.
|
|
20
|
+
jobs:
|
|
21
|
+
release:
|
|
22
|
+
name: release
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
environment:
|
|
25
|
+
name: pypi
|
|
26
|
+
url: https://pypi.org/p/django-spec-mcp
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write # PyPI OIDC trusted publishing
|
|
29
|
+
contents: write # tag push + gh release create
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 0
|
|
34
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: true
|
|
37
|
+
- name: Install
|
|
38
|
+
run: uv sync --all-groups
|
|
39
|
+
|
|
40
|
+
- name: Prepare release (version check, test, build dist)
|
|
41
|
+
id: prepare
|
|
42
|
+
run: make release-publish-prepare
|
|
43
|
+
|
|
44
|
+
- name: Publish to PyPI
|
|
45
|
+
if: steps.prepare.outputs.released == 'true'
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
47
|
+
with:
|
|
48
|
+
# Idempotent on purpose. What gates a release is the git tag, not the
|
|
49
|
+
# index (see `prepare`), so re-running this job after a failure in a
|
|
50
|
+
# later step arrives here having already uploaded these files. Without
|
|
51
|
+
# this the upload is the step that makes that re-run impossible, and
|
|
52
|
+
# every recovery becomes manual — which is how one release lost its tag,
|
|
53
|
+
# its GitHub Release and its docs deploy to a single transient push
|
|
54
|
+
# failure, with the package already published.
|
|
55
|
+
# PyPI never lets a file be replaced, so skipping one cannot overwrite
|
|
56
|
+
# a published artefact.
|
|
57
|
+
skip-existing: true
|
|
58
|
+
|
|
59
|
+
- name: Tag + GitHub Release
|
|
60
|
+
if: steps.prepare.outputs.released == 'true'
|
|
61
|
+
env:
|
|
62
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
63
|
+
run: make release-publish-finalize
|
|
64
|
+
|
|
65
|
+
# On release commits we own the single gh-pages push: embed the
|
|
66
|
+
# coverage badge JSON into the docs build so mkdocs gh-deploy carries
|
|
67
|
+
# it alongside the site, and tests.yml's coverage-badge job
|
|
68
|
+
# short-circuits to avoid a second push (and a second built-in
|
|
69
|
+
# "pages build and deployment" run).
|
|
70
|
+
- name: Render coverage badge for the docs site
|
|
71
|
+
if: steps.prepare.outputs.released == 'true' && hashFiles('mkdocs.yml') != ''
|
|
72
|
+
run: |
|
|
73
|
+
set -euo pipefail
|
|
74
|
+
# release-publish-prepare's pytest run wrote .coverage to the
|
|
75
|
+
# workspace root; convert to coverage.xml so the same parser
|
|
76
|
+
# we use in tests.yml can score it.
|
|
77
|
+
uv run coverage xml -o coverage.xml
|
|
78
|
+
python - <<'PY'
|
|
79
|
+
import json
|
|
80
|
+
import xml.etree.ElementTree as ET
|
|
81
|
+
|
|
82
|
+
tree = ET.parse("coverage.xml")
|
|
83
|
+
pct = round(float(tree.getroot().attrib["line-rate"]) * 100, 1)
|
|
84
|
+
if pct >= 95:
|
|
85
|
+
color = "brightgreen"
|
|
86
|
+
elif pct >= 85:
|
|
87
|
+
color = "green"
|
|
88
|
+
elif pct >= 70:
|
|
89
|
+
color = "yellow"
|
|
90
|
+
else:
|
|
91
|
+
color = "red"
|
|
92
|
+
payload = {
|
|
93
|
+
"schemaVersion": 1,
|
|
94
|
+
"label": "coverage",
|
|
95
|
+
"message": f"{pct}%",
|
|
96
|
+
"color": color,
|
|
97
|
+
}
|
|
98
|
+
# docs/ is mkdocs' source dir; non-.md files are copied verbatim
|
|
99
|
+
# into site/, which lands at the gh-pages root after gh-deploy.
|
|
100
|
+
with open("docs/coverage.json", "w") as fh:
|
|
101
|
+
json.dump(payload, fh)
|
|
102
|
+
fh.write("\n")
|
|
103
|
+
print(json.dumps(payload))
|
|
104
|
+
PY
|
|
105
|
+
|
|
106
|
+
- name: Deploy docs + coverage badge to gh-pages
|
|
107
|
+
if: steps.prepare.outputs.released == 'true' && hashFiles('mkdocs.yml') != ''
|
|
108
|
+
run: |
|
|
109
|
+
git config user.name 'github-actions[bot]'
|
|
110
|
+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
111
|
+
DJANGO_SETTINGS_MODULE=tests.conftest_settings \
|
|
112
|
+
uv run mkdocs gh-deploy --force --clean --config-file mkdocs.yml
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: tests-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
# Actions are pinned to a commit SHA rather than a tag: a tag is mutable, and an
|
|
13
|
+
# action owner (or anyone who takes over the account) can silently repoint one at
|
|
14
|
+
# new code that then runs with this workflow's token. The trailing version comment
|
|
15
|
+
# is not decoration -- Dependabot reads it to know which release the SHA is, and
|
|
16
|
+
# rewrites both together when a newer one ships.
|
|
17
|
+
jobs:
|
|
18
|
+
lint:
|
|
19
|
+
name: lint
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
- name: Install
|
|
27
|
+
run: uv sync --all-groups
|
|
28
|
+
- name: ruff check
|
|
29
|
+
run: uv run ruff check .
|
|
30
|
+
- name: ruff format check
|
|
31
|
+
run: uv run ruff format --check --diff .
|
|
32
|
+
- name: ty
|
|
33
|
+
run: uv run ty check django_spec_mcp
|
|
34
|
+
|
|
35
|
+
docs:
|
|
36
|
+
name: docs build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
40
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
41
|
+
with:
|
|
42
|
+
enable-cache: true
|
|
43
|
+
- name: Install with docs group
|
|
44
|
+
run: uv sync --all-groups
|
|
45
|
+
- name: mkdocs build --strict
|
|
46
|
+
# mkdocstrings imports the package, whose auth backends import
|
|
47
|
+
# ``django.contrib.auth``'s models, and those need configured settings
|
|
48
|
+
# and a ready app registry; the test settings module keeps the build
|
|
49
|
+
# hermetic.
|
|
50
|
+
run: DJANGO_SETTINGS_MODULE=tests.conftest_settings uv run mkdocs build --strict
|
|
51
|
+
- name: Check doc snippets against the installed packages
|
|
52
|
+
# mkdocs --strict validates links and structure, not claims about an
|
|
53
|
+
# API. This resolves every import and binds every call in a Python
|
|
54
|
+
# fence, which is the drift that accumulates silently.
|
|
55
|
+
run: uv run python scripts/check_docs_snippets.py
|
|
56
|
+
|
|
57
|
+
floor:
|
|
58
|
+
name: lowest declared versions
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
62
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
63
|
+
with:
|
|
64
|
+
enable-cache: true
|
|
65
|
+
# A floor is a claim about the *oldest* version that works, and a lockfile
|
|
66
|
+
# can only ever check the newest. Development and CI both resolve the
|
|
67
|
+
# newest in-range dependency, so every gate passes while a consumer whose
|
|
68
|
+
# own constraints pull an older one gets a package that does not import.
|
|
69
|
+
#
|
|
70
|
+
# That has already happened in this ecosystem: a sibling declared
|
|
71
|
+
# pydantic-ai >=2 while importing a symbol that first exists in 2.16, and
|
|
72
|
+
# the only thing that found it was a downstream consumer resolving from
|
|
73
|
+
# scratch.
|
|
74
|
+
#
|
|
75
|
+
# `lowest-direct` pins every *declared* dependency to the bottom of its
|
|
76
|
+
# window while letting transitives resolve normally, so this asserts the
|
|
77
|
+
# floors mean what they say. A per-PR gate rather than a scheduled job,
|
|
78
|
+
# because a floor stops being true the moment someone imports a newer
|
|
79
|
+
# upstream symbol -- a code change, not the passage of time.
|
|
80
|
+
#
|
|
81
|
+
# Pinned to the oldest supported Python rather than whatever interpreter
|
|
82
|
+
# the runner exposes. The floor claim is about the oldest configuration
|
|
83
|
+
# we support, so that is the one to install, and leaving the choice to
|
|
84
|
+
# the image makes the answer drift when the image does. It is not
|
|
85
|
+
# academic: django-filter 23.1, the bottom of the dev group's
|
|
86
|
+
# django-filter window, calls `pkgutil.find_loader`, which Python 3.14
|
|
87
|
+
# removed.
|
|
88
|
+
- name: Install Python 3.10
|
|
89
|
+
run: uv python install 3.10
|
|
90
|
+
# `--refresh` on both resolutions below. This job's whole claim is "what a
|
|
91
|
+
# consumer resolving from scratch would get", and the runner's uv cache is
|
|
92
|
+
# shared across runs: without it the answer comes from whatever package
|
|
93
|
+
# list that cache happens to hold, which can be hours behind the index.
|
|
94
|
+
# Not hypothetical -- a sibling's gate failed a floor raise as
|
|
95
|
+
# unsatisfiable while the index had been serving the release for some
|
|
96
|
+
# time, two re-runs reused the same cache and failed identically, and a
|
|
97
|
+
# cacheless resolve of the same requirement succeeded at once. A stale
|
|
98
|
+
# listing makes this gate report the past, in whichever direction happens
|
|
99
|
+
# to be wrong: inventing a broken floor, or hiding a real one.
|
|
100
|
+
- name: Resolve every declared dependency at its floor
|
|
101
|
+
run: uv lock --resolution lowest-direct --refresh
|
|
102
|
+
# `--frozen` here and `--no-sync` below, not a bare `uv sync` / `uv
|
|
103
|
+
# run`. uv records the resolution mode in the lockfile and silently
|
|
104
|
+
# discards one resolved in a different mode -- "Ignoring existing
|
|
105
|
+
# lockfile due to change in resolution mode: `lowest-direct` vs.
|
|
106
|
+
# `highest`". A plain `uv sync` therefore re-resolves at `highest` and
|
|
107
|
+
# undoes the step above, and the job then measures the newest versions
|
|
108
|
+
# twice while reporting that it tested the oldest.
|
|
109
|
+
- name: Install
|
|
110
|
+
run: uv sync --frozen --all-groups --all-extras --python 3.10
|
|
111
|
+
- name: Name the versions that produced
|
|
112
|
+
run: uv pip list
|
|
113
|
+
- name: Test
|
|
114
|
+
run: uv run --no-sync pytest
|
|
115
|
+
|
|
116
|
+
# The step above installs every extra, and an extra can hold a shared
|
|
117
|
+
# dependency *above* the floor being claimed -- masking the lie rather
|
|
118
|
+
# than exposing it. That is not hypothetical: reverting a sibling's
|
|
119
|
+
# pydantic-ai floor to its old value and resolving with all extras kept
|
|
120
|
+
# that dependency at 2.18 because another extra required it, and the
|
|
121
|
+
# suite passed. The same revert on a plain base install resolved 2.0 and
|
|
122
|
+
# failed at import, which is exactly how the original defect reached a
|
|
123
|
+
# consumer.
|
|
124
|
+
#
|
|
125
|
+
# So the floor claim has to be checked on the install shape that has the
|
|
126
|
+
# fewest constraints: the package alone, no extras, no dev group.
|
|
127
|
+
- name: Import the package installed alone, at its floors
|
|
128
|
+
run: |
|
|
129
|
+
uv venv --python 3.10 .venv-floor
|
|
130
|
+
uv pip install --python .venv-floor/bin/python --resolution lowest-direct --refresh -e .
|
|
131
|
+
uv pip list --python .venv-floor/bin/python
|
|
132
|
+
.venv-floor/bin/python - <<'PY'
|
|
133
|
+
import django
|
|
134
|
+
from django.conf import settings
|
|
135
|
+
|
|
136
|
+
# The auth backends import ``django.contrib.auth``'s models, so a
|
|
137
|
+
# bare import of this package cannot run unconfigured.
|
|
138
|
+
settings.configure(
|
|
139
|
+
SECRET_KEY="x",
|
|
140
|
+
INSTALLED_APPS=[
|
|
141
|
+
"django.contrib.contenttypes",
|
|
142
|
+
"django.contrib.auth",
|
|
143
|
+
],
|
|
144
|
+
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
|
|
145
|
+
SPEC_MCP={"ALLOWED_ORIGINS": ["*"], "SERVER_INFO": {}},
|
|
146
|
+
)
|
|
147
|
+
django.setup()
|
|
148
|
+
|
|
149
|
+
import importlib.util
|
|
150
|
+
|
|
151
|
+
import django_spec_mcp
|
|
152
|
+
from django_service_specs import ServiceSpec, Unrestricted
|
|
153
|
+
from django_spec_mcp import MCPServer, ToolRegistry
|
|
154
|
+
|
|
155
|
+
# Touch the public surface rather than only the package root: an
|
|
156
|
+
# upstream symbol that moved would be imported by a leaf module, and
|
|
157
|
+
# importing the root alone can miss it. Registering a tool runs the
|
|
158
|
+
# full spec reflection, so it exercises the sister package's dispatch
|
|
159
|
+
# surface rather than only the import of it.
|
|
160
|
+
server = MCPServer(name="floor")
|
|
161
|
+
server.register_tool(
|
|
162
|
+
ServiceSpec(service=lambda: None, permissions=[Unrestricted()]),
|
|
163
|
+
name="noop",
|
|
164
|
+
description="Probe tool for the base install at its floors.",
|
|
165
|
+
)
|
|
166
|
+
assert server.tools.get("noop") is not None
|
|
167
|
+
assert isinstance(server.tools, ToolRegistry)
|
|
168
|
+
|
|
169
|
+
# No DRF in the resolve: the package serves kernel specs, and a
|
|
170
|
+
# dependency that pulled DRF back in would pass every check above.
|
|
171
|
+
for name in ("rest_framework", "rest_framework_services"):
|
|
172
|
+
assert importlib.util.find_spec(name) is None, f"{name} is installed"
|
|
173
|
+
|
|
174
|
+
print("BASE IMPORT OK", django_spec_mcp.__version__, django.get_version())
|
|
175
|
+
PY
|
|
176
|
+
|
|
177
|
+
test:
|
|
178
|
+
name: ${{ matrix.python-version }} / Django ${{ matrix.django-version }}
|
|
179
|
+
runs-on: ubuntu-latest
|
|
180
|
+
strategy:
|
|
181
|
+
fail-fast: false
|
|
182
|
+
matrix:
|
|
183
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
184
|
+
django-version: ["4.2", "5.0", "5.1", "5.2", "6.0", "6.1"]
|
|
185
|
+
exclude:
|
|
186
|
+
# Django 4.2 supports Python 3.8–3.12
|
|
187
|
+
- python-version: "3.13"
|
|
188
|
+
django-version: "4.2"
|
|
189
|
+
- python-version: "3.14"
|
|
190
|
+
django-version: "4.2"
|
|
191
|
+
# Django 5.0 supports Python 3.10–3.12
|
|
192
|
+
- python-version: "3.13"
|
|
193
|
+
django-version: "5.0"
|
|
194
|
+
- python-version: "3.14"
|
|
195
|
+
django-version: "5.0"
|
|
196
|
+
# Django 5.1 supports Python 3.10–3.13
|
|
197
|
+
- python-version: "3.14"
|
|
198
|
+
django-version: "5.1"
|
|
199
|
+
# Django 6.0 requires Python 3.12+
|
|
200
|
+
- python-version: "3.10"
|
|
201
|
+
django-version: "6.0"
|
|
202
|
+
- python-version: "3.11"
|
|
203
|
+
django-version: "6.0"
|
|
204
|
+
# Django 6.1 requires Python 3.12+
|
|
205
|
+
- python-version: "3.10"
|
|
206
|
+
django-version: "6.1"
|
|
207
|
+
- python-version: "3.11"
|
|
208
|
+
django-version: "6.1"
|
|
209
|
+
|
|
210
|
+
steps:
|
|
211
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
212
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
213
|
+
with:
|
|
214
|
+
enable-cache: true
|
|
215
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
216
|
+
run: uv python install ${{ matrix.python-version }}
|
|
217
|
+
- name: Sync dev deps
|
|
218
|
+
run: uv sync --all-groups --python ${{ matrix.python-version }}
|
|
219
|
+
- name: Pin Django ${{ matrix.django-version }}
|
|
220
|
+
run: uv pip install --reinstall-package django "django~=${{ matrix.django-version }}.0"
|
|
221
|
+
- name: pytest
|
|
222
|
+
run: |
|
|
223
|
+
uv run --no-sync pytest \
|
|
224
|
+
--cov-report=xml:coverage.xml \
|
|
225
|
+
--cov-report=html:htmlcov
|
|
226
|
+
- name: Upload coverage artifact
|
|
227
|
+
if: always()
|
|
228
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
229
|
+
with:
|
|
230
|
+
name: coverage-py${{ matrix.python-version }}-django${{ matrix.django-version }}
|
|
231
|
+
path: |
|
|
232
|
+
coverage.xml
|
|
233
|
+
htmlcov/
|
|
234
|
+
if-no-files-found: ignore
|
|
235
|
+
|
|
236
|
+
smoke-no-extras:
|
|
237
|
+
name: smoke (no optional extras)
|
|
238
|
+
runs-on: ubuntu-latest
|
|
239
|
+
steps:
|
|
240
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
241
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
242
|
+
with:
|
|
243
|
+
enable-cache: true
|
|
244
|
+
- name: Install base only (no extras, no dev group)
|
|
245
|
+
run: |
|
|
246
|
+
uv venv --python 3.13 .venv-smoke
|
|
247
|
+
# ``uv venv`` doesn't include pip by default; use ``uv pip install``
|
|
248
|
+
# against the smoke env to do an editable install with no optional
|
|
249
|
+
# extras and no dev group.
|
|
250
|
+
uv pip install --python .venv-smoke/bin/python -e .
|
|
251
|
+
- name: Confirm import + server build + lazy optional imports
|
|
252
|
+
run: |
|
|
253
|
+
.venv-smoke/bin/python - <<'PY'
|
|
254
|
+
import django
|
|
255
|
+
from django.conf import settings
|
|
256
|
+
|
|
257
|
+
settings.configure(
|
|
258
|
+
SECRET_KEY="x",
|
|
259
|
+
INSTALLED_APPS=[
|
|
260
|
+
"django.contrib.contenttypes",
|
|
261
|
+
"django.contrib.auth",
|
|
262
|
+
],
|
|
263
|
+
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
|
|
264
|
+
# Only scalars. The auth backend and session store are
|
|
265
|
+
# constructor arguments, so building a bare server exercises the
|
|
266
|
+
# real defaults.
|
|
267
|
+
SPEC_MCP={
|
|
268
|
+
"ALLOWED_ORIGINS": ["*"],
|
|
269
|
+
"SERVER_INFO": {},
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
django.setup()
|
|
273
|
+
|
|
274
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
275
|
+
|
|
276
|
+
from django_spec_mcp import MCPServer
|
|
277
|
+
from django_spec_mcp.transport.django_cache_session_store import (
|
|
278
|
+
DjangoCacheSessionStore,
|
|
279
|
+
)
|
|
280
|
+
from django_service_specs import ServiceSpec, Unrestricted
|
|
281
|
+
|
|
282
|
+
server = MCPServer(name="smoke")
|
|
283
|
+
# A tool must declare its permissions. Declaring ``Unrestricted`` here
|
|
284
|
+
# keeps the smoke test about *imports without optional extras*, which
|
|
285
|
+
# is its subject, rather than about the permission default.
|
|
286
|
+
server.register_tool(
|
|
287
|
+
ServiceSpec(service=lambda: None, permissions=[Unrestricted()]),
|
|
288
|
+
name="noop",
|
|
289
|
+
)
|
|
290
|
+
assert server.tools.get("noop") is not None
|
|
291
|
+
|
|
292
|
+
# A bare server constructs its session store default without reaching
|
|
293
|
+
# for an optional extra, and has no auth backend: it builds, and
|
|
294
|
+
# refuses to be mounted until one is given.
|
|
295
|
+
assert server.auth_backend is None
|
|
296
|
+
assert isinstance(server.session_store, DjangoCacheSessionStore)
|
|
297
|
+
try:
|
|
298
|
+
server.urls
|
|
299
|
+
raise SystemExit("mounting a server with no auth backend should be refused")
|
|
300
|
+
except ImproperlyConfigured:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
# Guard the guard: if the smoke env ever gained the extra, everything
|
|
304
|
+
# below would pass for the wrong reason.
|
|
305
|
+
try:
|
|
306
|
+
import redis # noqa: F401
|
|
307
|
+
raise SystemExit("smoke environment unexpectedly has redis installed")
|
|
308
|
+
except ImportError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
# The Redis SSE broker module imports fine without ``redis`` — the
|
|
312
|
+
# ImportError only fires inside ``RedisSSEBroker.__init__``.
|
|
313
|
+
from django_spec_mcp.transport.redis_sse_broker import RedisSSEBroker
|
|
314
|
+
try:
|
|
315
|
+
RedisSSEBroker(client=object())
|
|
316
|
+
raise SystemExit("RedisSSEBroker construction should fail without redis")
|
|
317
|
+
except ImportError:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
# The OTel tracing helper falls back to a no-op span when the API
|
|
321
|
+
# isn't installed — handlers stay branch-free.
|
|
322
|
+
from django_spec_mcp._compat.tracing import span
|
|
323
|
+
with span("mcp.smoke") as s:
|
|
324
|
+
s.set_attribute("k", "v")
|
|
325
|
+
|
|
326
|
+
print("SMOKE OK")
|
|
327
|
+
PY
|
|
328
|
+
|
|
329
|
+
coverage-badge:
|
|
330
|
+
name: coverage badge
|
|
331
|
+
needs: test
|
|
332
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
333
|
+
runs-on: ubuntu-latest
|
|
334
|
+
permissions:
|
|
335
|
+
contents: write
|
|
336
|
+
steps:
|
|
337
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
338
|
+
with:
|
|
339
|
+
# Need the parent commit so the gate step below can diff
|
|
340
|
+
# version.py against ${{ github.event.before }}. The default
|
|
341
|
+
# shallow checkout (depth 1) would make `git show "$before:…"`
|
|
342
|
+
# fail with exit 128 and turn the whole job red.
|
|
343
|
+
fetch-depth: 2
|
|
344
|
+
# release.yml runs in parallel on the same push and owns gh-pages
|
|
345
|
+
# publishing on release commits (it bundles coverage.json into the
|
|
346
|
+
# mkdocs gh-deploy). If we also push here we get a second built-in
|
|
347
|
+
# "pages build and deployment" run that the first one cancels. The
|
|
348
|
+
# version-diff against ${{ github.event.before }} is the reliable
|
|
349
|
+
# signal — release.yml's tag isn't pushed until after PyPI publish,
|
|
350
|
+
# so a tag-existence check would race.
|
|
351
|
+
- name: Skip when release.yml owns this push
|
|
352
|
+
id: gate
|
|
353
|
+
run: |
|
|
354
|
+
set -euo pipefail
|
|
355
|
+
before="${{ github.event.before }}"
|
|
356
|
+
if [[ -z "$before" || "$before" =~ ^0+$ ]]; then
|
|
357
|
+
# First push to main on a brand-new repo — no prior state to diff.
|
|
358
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
359
|
+
exit 0
|
|
360
|
+
fi
|
|
361
|
+
# `before` may not be in the local object DB if the push
|
|
362
|
+
# contained more than one commit (fetch-depth: 2 only guarantees
|
|
363
|
+
# HEAD + 1 parent). Try to fetch just that commit; if it's not
|
|
364
|
+
# reachable, fall back to "don't skip" — better to double-push
|
|
365
|
+
# to gh-pages once than to turn the pipeline red.
|
|
366
|
+
if ! git cat-file -e "$before^{commit}" 2>/dev/null; then
|
|
367
|
+
git fetch --depth=1 origin "$before" 2>/dev/null || true
|
|
368
|
+
fi
|
|
369
|
+
old=""
|
|
370
|
+
if git cat-file -e "$before:django_spec_mcp/version.py" 2>/dev/null; then
|
|
371
|
+
old="$(git show "$before:django_spec_mcp/version.py" \
|
|
372
|
+
| awk -F'"' '/^__version__/ { print $2; exit }')"
|
|
373
|
+
fi
|
|
374
|
+
new="$(awk -F'"' '/^__version__/ { print $2; exit }' \
|
|
375
|
+
django_spec_mcp/version.py)"
|
|
376
|
+
if [[ -n "$old" && "$old" != "$new" ]]; then
|
|
377
|
+
echo "Version bumped $old → $new; release.yml will deploy gh-pages."
|
|
378
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
379
|
+
else
|
|
380
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
381
|
+
fi
|
|
382
|
+
- name: Download canonical coverage artifact
|
|
383
|
+
if: steps.gate.outputs.skip != 'true'
|
|
384
|
+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
385
|
+
with:
|
|
386
|
+
name: coverage-py3.13-django5.2
|
|
387
|
+
path: coverage-artifact/
|
|
388
|
+
- name: Render coverage.json
|
|
389
|
+
if: steps.gate.outputs.skip != 'true'
|
|
390
|
+
run: |
|
|
391
|
+
python - <<'PY'
|
|
392
|
+
import json
|
|
393
|
+
import xml.etree.ElementTree as ET
|
|
394
|
+
|
|
395
|
+
tree = ET.parse("coverage-artifact/coverage.xml")
|
|
396
|
+
pct = round(float(tree.getroot().attrib["line-rate"]) * 100, 1)
|
|
397
|
+
if pct >= 95:
|
|
398
|
+
color = "brightgreen"
|
|
399
|
+
elif pct >= 85:
|
|
400
|
+
color = "green"
|
|
401
|
+
elif pct >= 70:
|
|
402
|
+
color = "yellow"
|
|
403
|
+
else:
|
|
404
|
+
color = "red"
|
|
405
|
+
payload = {
|
|
406
|
+
"schemaVersion": 1,
|
|
407
|
+
"label": "coverage",
|
|
408
|
+
"message": f"{pct}%",
|
|
409
|
+
"color": color,
|
|
410
|
+
}
|
|
411
|
+
with open("coverage.json", "w") as fh:
|
|
412
|
+
json.dump(payload, fh)
|
|
413
|
+
fh.write("\n")
|
|
414
|
+
print(json.dumps(payload))
|
|
415
|
+
PY
|
|
416
|
+
- name: Publish coverage.json to gh-pages
|
|
417
|
+
if: steps.gate.outputs.skip != 'true'
|
|
418
|
+
env:
|
|
419
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
420
|
+
run: |
|
|
421
|
+
set -euo pipefail
|
|
422
|
+
git config user.name 'github-actions[bot]'
|
|
423
|
+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
424
|
+
|
|
425
|
+
tmp="$(mktemp -d)"
|
|
426
|
+
cp coverage.json "$tmp/coverage.json"
|
|
427
|
+
|
|
428
|
+
git fetch origin gh-pages --depth=1 2>/dev/null || true
|
|
429
|
+
if git rev-parse --verify origin/gh-pages >/dev/null 2>&1; then
|
|
430
|
+
git worktree add gh-pages origin/gh-pages
|
|
431
|
+
cp "$tmp/coverage.json" gh-pages/coverage.json
|
|
432
|
+
cd gh-pages
|
|
433
|
+
if [[ -n "$(git status --porcelain coverage.json)" ]]; then
|
|
434
|
+
git add coverage.json
|
|
435
|
+
git commit -m "coverage badge: update from ${GITHUB_SHA::7}"
|
|
436
|
+
git push origin HEAD:gh-pages
|
|
437
|
+
else
|
|
438
|
+
echo "coverage.json unchanged, skipping push"
|
|
439
|
+
fi
|
|
440
|
+
else
|
|
441
|
+
git worktree add --detach gh-pages
|
|
442
|
+
cd gh-pages
|
|
443
|
+
git checkout --orphan gh-pages
|
|
444
|
+
git rm -rf . >/dev/null 2>&1 || true
|
|
445
|
+
cp "$tmp/coverage.json" coverage.json
|
|
446
|
+
git add coverage.json
|
|
447
|
+
git commit -m "coverage badge: initial publish from ${GITHUB_SHA::7}"
|
|
448
|
+
git push origin gh-pages
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
secrets:
|
|
452
|
+
name: secret scan
|
|
453
|
+
runs-on: ubuntu-latest
|
|
454
|
+
steps:
|
|
455
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
456
|
+
with:
|
|
457
|
+
fetch-depth: 0
|
|
458
|
+
- name: Install gitleaks
|
|
459
|
+
run: |
|
|
460
|
+
curl -sSL https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz | tar -xz gitleaks
|
|
461
|
+
sudo mv gitleaks /usr/local/bin/
|
|
462
|
+
- name: Scan repo + history
|
|
463
|
+
run: gitleaks git --redact --no-banner .
|
|
464
|
+
|
|
465
|
+
tests-passed:
|
|
466
|
+
name: tests
|
|
467
|
+
# always(), never !cancelled(): a skipped job reports a skipped check, and
|
|
468
|
+
# branch protection counts a skipped required check as passing.
|
|
469
|
+
if: always()
|
|
470
|
+
needs: [lint, docs, floor, test, smoke-no-extras]
|
|
471
|
+
runs-on: ubuntu-latest
|
|
472
|
+
steps:
|
|
473
|
+
# Pass on an exact `success` from every job above, rather than failing on
|
|
474
|
+
# a list of bad results: such a list passes whatever it does not name, and
|
|
475
|
+
# GitHub reports a result it does not document. A job no hosted runner
|
|
476
|
+
# ever picks up ("The job was not acquired by Runner of type hosted even
|
|
477
|
+
# after multiple attempts") shows as cancelled on its own check but
|
|
478
|
+
# reaches `needs` as `abandoned`, and a gate that failed only on `failure`
|
|
479
|
+
# and `cancelled` went green over exactly that while the jobs it guards
|
|
480
|
+
# never ran. A matrix reaches `needs` as one result, and one cell that did
|
|
481
|
+
# not succeed is enough to make it something other than `success`.
|
|
482
|
+
#
|
|
483
|
+
# None of the jobs above has an `if:`, so `skipped` can only mean one of
|
|
484
|
+
# them never ran, and it fails like any other result. A job that gains a
|
|
485
|
+
# legitimate skip gets an exception naming it, never `skipped` accepted
|
|
486
|
+
# from all of them.
|
|
487
|
+
#
|
|
488
|
+
# The results come in through `env` rather than being spliced into the
|
|
489
|
+
# script, and every one is printed, so a red gate says which job and what
|
|
490
|
+
# GitHub reported for it.
|
|
491
|
+
- name: Verify every required job succeeded
|
|
492
|
+
env:
|
|
493
|
+
NEEDS: ${{ toJSON(needs) }}
|
|
494
|
+
run: |
|
|
495
|
+
echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
|
|
496
|
+
not_ok="$(echo "$NEEDS" | jq -r '[to_entries[] | select(.value.result != "success") | "\(.key)=\(.value.result)"] | join(" ")')"
|
|
497
|
+
if [ -n "$not_ok" ]; then
|
|
498
|
+
echo "::error::required jobs did not succeed: $not_ok"
|
|
499
|
+
exit 1
|
|
500
|
+
fi
|