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.
Files changed (604) hide show
  1. django_spec_mcp-0.1.0/.github/CODEOWNERS +1 -0
  2. django_spec_mcp-0.1.0/.github/SECURITY.md +9 -0
  3. django_spec_mcp-0.1.0/.github/dependabot.yml +26 -0
  4. django_spec_mcp-0.1.0/.github/workflows/release.yml +112 -0
  5. django_spec_mcp-0.1.0/.github/workflows/tests.yml +500 -0
  6. django_spec_mcp-0.1.0/.github/workflows/upstream-drift.yml +306 -0
  7. django_spec_mcp-0.1.0/.gitignore +636 -0
  8. django_spec_mcp-0.1.0/.gitleaksignore +2 -0
  9. django_spec_mcp-0.1.0/.pre-commit-config.yaml +83 -0
  10. django_spec_mcp-0.1.0/CHANGELOG.md +514 -0
  11. django_spec_mcp-0.1.0/CLAUDE.md +382 -0
  12. django_spec_mcp-0.1.0/LICENSE +21 -0
  13. django_spec_mcp-0.1.0/Makefile +94 -0
  14. django_spec_mcp-0.1.0/PKG-INFO +241 -0
  15. django_spec_mcp-0.1.0/README.md +199 -0
  16. django_spec_mcp-0.1.0/django_spec_mcp/__init__.py +103 -0
  17. django_spec_mcp-0.1.0/django_spec_mcp/_compat/__init__.py +4 -0
  18. django_spec_mcp-0.1.0/django_spec_mcp/_compat/acall.py +33 -0
  19. django_spec_mcp-0.1.0/django_spec_mcp/_compat/reject_awaitable.py +53 -0
  20. django_spec_mcp-0.1.0/django_spec_mcp/_compat/tracing.py +58 -0
  21. django_spec_mcp-0.1.0/django_spec_mcp/_compat/utils.py +22 -0
  22. django_spec_mcp-0.1.0/django_spec_mcp/adapters/__init__.py +1 -0
  23. django_spec_mcp-0.1.0/django_spec_mcp/adapters/check_kernel_spec_guarded.py +51 -0
  24. django_spec_mcp-0.1.0/django_spec_mcp/adapters/kernel_selector_to_resource.py +75 -0
  25. django_spec_mcp-0.1.0/django_spec_mcp/adapters/kernel_spec_to_tool.py +163 -0
  26. django_spec_mcp-0.1.0/django_spec_mcp/adapters/ui_to_resource.py +199 -0
  27. django_spec_mcp-0.1.0/django_spec_mcp/adapters/utils.py +91 -0
  28. django_spec_mcp-0.1.0/django_spec_mcp/auth/__init__.py +15 -0
  29. django_spec_mcp-0.1.0/django_spec_mcp/auth/audience.py +20 -0
  30. django_spec_mcp-0.1.0/django_spec_mcp/auth/backends/__init__.py +3 -0
  31. django_spec_mcp-0.1.0/django_spec_mcp/auth/backends/allow_any_backend.py +39 -0
  32. django_spec_mcp-0.1.0/django_spec_mcp/auth/bearer_challenge.py +34 -0
  33. django_spec_mcp-0.1.0/django_spec_mcp/auth/insufficient_scope_response.py +20 -0
  34. django_spec_mcp-0.1.0/django_spec_mcp/auth/principal_for_token.py +57 -0
  35. django_spec_mcp-0.1.0/django_spec_mcp/auth/protected_resource_metadata_view.py +63 -0
  36. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/__init__.py +11 -0
  37. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/fixed_window_rate_limit.py +68 -0
  38. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/sliding_window_rate_limit.py +75 -0
  39. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/token_bucket_rate_limit.py +85 -0
  40. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/types/__init__.py +3 -0
  41. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/types/mcp_rate_limit.py +30 -0
  42. django_spec_mcp-0.1.0/django_spec_mcp/auth/rate_limits/utils.py +24 -0
  43. django_spec_mcp-0.1.0/django_spec_mcp/auth/types/__init__.py +9 -0
  44. django_spec_mcp-0.1.0/django_spec_mcp/auth/types/auth_backend.py +39 -0
  45. django_spec_mcp-0.1.0/django_spec_mcp/auth/types/protected_resource_metadata.py +39 -0
  46. django_spec_mcp-0.1.0/django_spec_mcp/auth/types/self_checking.py +42 -0
  47. django_spec_mcp-0.1.0/django_spec_mcp/auth/types/token_info.py +38 -0
  48. django_spec_mcp-0.1.0/django_spec_mcp/auth/unauthenticated_response.py +20 -0
  49. django_spec_mcp-0.1.0/django_spec_mcp/conf.py +77 -0
  50. django_spec_mcp-0.1.0/django_spec_mcp/config/__init__.py +4 -0
  51. django_spec_mcp-0.1.0/django_spec_mcp/config/build_mcp_config.py +153 -0
  52. django_spec_mcp-0.1.0/django_spec_mcp/config/types/__init__.py +3 -0
  53. django_spec_mcp-0.1.0/django_spec_mcp/config/types/mcp_config.py +191 -0
  54. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/__init__.py +52 -0
  55. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/build_requested_schema.py +109 -0
  56. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/can_ask_client.py +26 -0
  57. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/fingerprint_request.py +35 -0
  58. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/read_elicit_answer.py +40 -0
  59. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/sign_request_state.py +28 -0
  60. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/__init__.py +13 -0
  61. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/elicit_answer.py +22 -0
  62. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/elicit_request.py +41 -0
  63. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/input_required_result.py +65 -0
  64. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/request_state.py +82 -0
  65. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/types/resolved_input.py +41 -0
  66. django_spec_mcp-0.1.0/django_spec_mcp/elicitation/verify_request_state.py +56 -0
  67. django_spec_mcp-0.1.0/django_spec_mcp/handlers/__init__.py +23 -0
  68. django_spec_mcp-0.1.0/django_spec_mcp/handlers/answer_kernel_refusal.py +26 -0
  69. django_spec_mcp-0.1.0/django_spec_mcp/handlers/async_dispatch.py +76 -0
  70. django_spec_mcp-0.1.0/django_spec_mcp/handlers/call_kernel_tool.py +118 -0
  71. django_spec_mcp-0.1.0/django_spec_mcp/handlers/dispatch.py +77 -0
  72. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_completion_complete.py +285 -0
  73. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_initialize.py +135 -0
  74. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_ping.py +18 -0
  75. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_get.py +84 -0
  76. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_get_async.py +64 -0
  77. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_prompts_list.py +54 -0
  78. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_list.py +62 -0
  79. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_read.py +101 -0
  80. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_read_async.py +96 -0
  81. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_resources_templates_list.py +59 -0
  82. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_server_discover.py +61 -0
  83. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_cancel.py +59 -0
  84. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_get.py +50 -0
  85. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tasks_update.py +93 -0
  86. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_call.py +153 -0
  87. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_call_async.py +153 -0
  88. django_spec_mcp-0.1.0/django_spec_mcp/handlers/handle_tools_list.py +70 -0
  89. django_spec_mcp-0.1.0/django_spec_mcp/handlers/input_dispatch.py +179 -0
  90. django_spec_mcp-0.1.0/django_spec_mcp/handlers/invalidation_dispatch.py +83 -0
  91. django_spec_mcp-0.1.0/django_spec_mcp/handlers/is_binding_listable.py +121 -0
  92. django_spec_mcp-0.1.0/django_spec_mcp/handlers/judge_binding.py +62 -0
  93. django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_refusals.py +156 -0
  94. django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_tool_entry.py +60 -0
  95. django_spec_mcp-0.1.0/django_spec_mcp/handlers/kernel_utils.py +1060 -0
  96. django_spec_mcp-0.1.0/django_spec_mcp/handlers/listable_tool_bindings.py +38 -0
  97. django_spec_mcp-0.1.0/django_spec_mcp/handlers/offered_tools.py +180 -0
  98. django_spec_mcp-0.1.0/django_spec_mcp/handlers/pagination.py +74 -0
  99. django_spec_mcp-0.1.0/django_spec_mcp/handlers/present_kernel_result.py +63 -0
  100. django_spec_mcp-0.1.0/django_spec_mcp/handlers/read_kernel_resource.py +66 -0
  101. django_spec_mcp-0.1.0/django_spec_mcp/handlers/read_kernel_resource_async.py +65 -0
  102. django_spec_mcp-0.1.0/django_spec_mcp/handlers/render_prompt_messages.py +36 -0
  103. django_spec_mcp-0.1.0/django_spec_mcp/handlers/resolve_call_input.py +32 -0
  104. django_spec_mcp-0.1.0/django_spec_mcp/handlers/resource_presentation_refusal.py +40 -0
  105. django_spec_mcp-0.1.0/django_spec_mcp/handlers/run_kernel_tool.py +79 -0
  106. django_spec_mcp-0.1.0/django_spec_mcp/handlers/run_kernel_tool_async.py +81 -0
  107. django_spec_mcp-0.1.0/django_spec_mcp/handlers/runs_on_kernel.py +22 -0
  108. django_spec_mcp-0.1.0/django_spec_mcp/handlers/task_dispatch.py +138 -0
  109. django_spec_mcp-0.1.0/django_spec_mcp/handlers/tasks_utils.py +112 -0
  110. django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/__init__.py +3 -0
  111. django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/context.py +138 -0
  112. django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/kernel_input_round.py +24 -0
  113. django_spec_mcp-0.1.0/django_spec_mcp/handlers/types/kernel_refusal_scene.py +30 -0
  114. django_spec_mcp-0.1.0/django_spec_mcp/handlers/utils.py +687 -0
  115. django_spec_mcp-0.1.0/django_spec_mcp/observability/__init__.py +34 -0
  116. django_spec_mcp-0.1.0/django_spec_mcp/observability/get_logger.py +18 -0
  117. django_spec_mcp-0.1.0/django_spec_mcp/observability/session_fingerprint.py +20 -0
  118. django_spec_mcp-0.1.0/django_spec_mcp/output/__init__.py +9 -0
  119. django_spec_mcp-0.1.0/django_spec_mcp/output/build_content_blocks.py +154 -0
  120. django_spec_mcp-0.1.0/django_spec_mcp/output/build_resource_contents.py +34 -0
  121. django_spec_mcp-0.1.0/django_spec_mcp/output/encode_json.py +56 -0
  122. django_spec_mcp-0.1.0/django_spec_mcp/output/encode_resource_contents.py +85 -0
  123. django_spec_mcp-0.1.0/django_spec_mcp/output/enforce_result_bytes.py +51 -0
  124. django_spec_mcp-0.1.0/django_spec_mcp/output/error_tool_result.py +51 -0
  125. django_spec_mcp-0.1.0/django_spec_mcp/output/resolve_structured_output.py +55 -0
  126. django_spec_mcp-0.1.0/django_spec_mcp/output/tool_result.py +93 -0
  127. django_spec_mcp-0.1.0/django_spec_mcp/protocol/__init__.py +47 -0
  128. django_spec_mcp-0.1.0/django_spec_mcp/protocol/build_server_info.py +83 -0
  129. django_spec_mcp-0.1.0/django_spec_mcp/protocol/parse_message.py +50 -0
  130. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/__init__.py +45 -0
  131. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/client_capabilities.py +42 -0
  132. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/completion.py +31 -0
  133. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/discover_result.py +48 -0
  134. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/get_prompt_result.py +27 -0
  135. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/icon.py +63 -0
  136. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/implementation.py +52 -0
  137. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/initialize_params.py +38 -0
  138. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/initialize_result.py +30 -0
  139. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_error.py +26 -0
  140. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_message.py +9 -0
  141. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_notification.py +24 -0
  142. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_request.py +26 -0
  143. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/json_rpc_response.py +49 -0
  144. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt.py +46 -0
  145. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt_argument.py +29 -0
  146. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/prompt_message.py +42 -0
  147. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource.py +48 -0
  148. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource_contents.py +34 -0
  149. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/resource_template.py +45 -0
  150. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/server_capabilities.py +56 -0
  151. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/task.py +72 -0
  152. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool.py +52 -0
  153. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool_content_block.py +177 -0
  154. django_spec_mcp-0.1.0/django_spec_mcp/protocol/types/tool_result.py +49 -0
  155. django_spec_mcp-0.1.0/django_spec_mcp/registry/__init__.py +5 -0
  156. django_spec_mcp-0.1.0/django_spec_mcp/registry/prompt_registry.py +35 -0
  157. django_spec_mcp-0.1.0/django_spec_mcp/registry/resource_registry.py +92 -0
  158. django_spec_mcp-0.1.0/django_spec_mcp/registry/tool_registry.py +77 -0
  159. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/__init__.py +11 -0
  160. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/kernel_resource_binding.py +51 -0
  161. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/kernel_tool_binding.py +189 -0
  162. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/prompt_binding.py +79 -0
  163. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/resource_binding.py +108 -0
  164. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_csp.py +52 -0
  165. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_resource_meta.py +55 -0
  166. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/ui_tool_meta.py +44 -0
  167. django_spec_mcp-0.1.0/django_spec_mcp/registry/types/utils.py +112 -0
  168. django_spec_mcp-0.1.0/django_spec_mcp/schema/__init__.py +13 -0
  169. django_spec_mcp-0.1.0/django_spec_mcp/schema/agent_conventions.py +115 -0
  170. django_spec_mcp-0.1.0/django_spec_mcp/schema/kernel_tool_input_schema.py +98 -0
  171. django_spec_mcp-0.1.0/django_spec_mcp/schema/kernel_tool_output_schema.py +80 -0
  172. django_spec_mcp-0.1.0/django_spec_mcp/schema/types/__init__.py +3 -0
  173. django_spec_mcp-0.1.0/django_spec_mcp/schema/types/agent_conventions.py +185 -0
  174. django_spec_mcp-0.1.0/django_spec_mcp/schema/utils.py +22 -0
  175. django_spec_mcp-0.1.0/django_spec_mcp/server/__init__.py +3 -0
  176. django_spec_mcp-0.1.0/django_spec_mcp/server/mcp_server.py +1716 -0
  177. django_spec_mcp-0.1.0/django_spec_mcp/server/utils.py +247 -0
  178. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/__init__.py +24 -0
  179. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/grant_subscription.py +190 -0
  180. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/in_memory_subscription_broker.py +83 -0
  181. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/publish_after_task.py +39 -0
  182. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/publish_invalidations.py +81 -0
  183. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/redis_subscription_broker.py +174 -0
  184. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/render_invalidations.py +81 -0
  185. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/__init__.py +4 -0
  186. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/subscription_broker.py +54 -0
  187. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/types/subscription_filter.py +71 -0
  188. django_spec_mcp-0.1.0/django_spec_mcp/subscriptions/utils.py +49 -0
  189. django_spec_mcp-0.1.0/django_spec_mcp/tasks/__init__.py +36 -0
  190. django_spec_mcp-0.1.0/django_spec_mcp/tasks/build_worker_token.py +69 -0
  191. django_spec_mcp-0.1.0/django_spec_mcp/tasks/create_task.py +81 -0
  192. django_spec_mcp-0.1.0/django_spec_mcp/tasks/django_cache_task_store.py +167 -0
  193. django_spec_mcp-0.1.0/django_spec_mcp/tasks/in_memory_task_store.py +41 -0
  194. django_spec_mcp-0.1.0/django_spec_mcp/tasks/report_task_progress.py +90 -0
  195. django_spec_mcp-0.1.0/django_spec_mcp/tasks/run_task.py +102 -0
  196. django_spec_mcp-0.1.0/django_spec_mcp/tasks/transition_task.py +67 -0
  197. django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/__init__.py +5 -0
  198. django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_executor.py +42 -0
  199. django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_record.py +108 -0
  200. django_spec_mcp-0.1.0/django_spec_mcp/tasks/types/task_store.py +50 -0
  201. django_spec_mcp-0.1.0/django_spec_mcp/tasks/utils.py +54 -0
  202. django_spec_mcp-0.1.0/django_spec_mcp/testing/__init__.py +16 -0
  203. django_spec_mcp-0.1.0/django_spec_mcp/testing/assert_tool_result_conforms.py +200 -0
  204. django_spec_mcp-0.1.0/django_spec_mcp/transport/__init__.py +21 -0
  205. django_spec_mcp-0.1.0/django_spec_mcp/transport/async_streamable_http_view.py +624 -0
  206. django_spec_mcp-0.1.0/django_spec_mcp/transport/django_cache_session_store.py +176 -0
  207. django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_session_store.py +32 -0
  208. django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_sse_broker.py +85 -0
  209. django_spec_mcp-0.1.0/django_spec_mcp/transport/in_memory_sse_replay_buffer.py +91 -0
  210. django_spec_mcp-0.1.0/django_spec_mcp/transport/negotiate_protocol_version.py +61 -0
  211. django_spec_mcp-0.1.0/django_spec_mcp/transport/origin_validation.py +26 -0
  212. django_spec_mcp-0.1.0/django_spec_mcp/transport/progress_dispatch.py +191 -0
  213. django_spec_mcp-0.1.0/django_spec_mcp/transport/protocol_version.py +20 -0
  214. django_spec_mcp-0.1.0/django_spec_mcp/transport/redis_sse_broker.py +183 -0
  215. django_spec_mcp-0.1.0/django_spec_mcp/transport/redis_sse_replay_buffer.py +157 -0
  216. django_spec_mcp-0.1.0/django_spec_mcp/transport/response_stream.py +235 -0
  217. django_spec_mcp-0.1.0/django_spec_mcp/transport/sse_response.py +139 -0
  218. django_spec_mcp-0.1.0/django_spec_mcp/transport/streamable_http_view.py +489 -0
  219. django_spec_mcp-0.1.0/django_spec_mcp/transport/subscription_stream.py +171 -0
  220. django_spec_mcp-0.1.0/django_spec_mcp/transport/types/__init__.py +5 -0
  221. django_spec_mcp-0.1.0/django_spec_mcp/transport/types/request_metadata.py +77 -0
  222. django_spec_mcp-0.1.0/django_spec_mcp/transport/types/session_store.py +32 -0
  223. django_spec_mcp-0.1.0/django_spec_mcp/transport/types/sse_broker.py +53 -0
  224. django_spec_mcp-0.1.0/django_spec_mcp/transport/types/sse_replay_buffer.py +61 -0
  225. django_spec_mcp-0.1.0/django_spec_mcp/transport/utils.py +555 -0
  226. django_spec_mcp-0.1.0/django_spec_mcp/transport/validate_modern_request.py +137 -0
  227. django_spec_mcp-0.1.0/django_spec_mcp/types/__init__.py +89 -0
  228. django_spec_mcp-0.1.0/django_spec_mcp/types/cache_scope.py +22 -0
  229. django_spec_mcp-0.1.0/django_spec_mcp/types/client_capabilities_meta_key.py +11 -0
  230. django_spec_mcp-0.1.0/django_spec_mcp/types/client_info_meta_key.py +8 -0
  231. django_spec_mcp-0.1.0/django_spec_mcp/types/elicit_action.py +20 -0
  232. django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_create_method.py +15 -0
  233. django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_key.py +8 -0
  234. django_spec_mcp-0.1.0/django_spec_mcp/types/elicitation_scalar_types.py +11 -0
  235. django_spec_mcp-0.1.0/django_spec_mcp/types/icon_theme.py +18 -0
  236. django_spec_mcp-0.1.0/django_spec_mcp/types/internal_error_message.py +16 -0
  237. django_spec_mcp-0.1.0/django_spec_mcp/types/json_rpc_error_code.py +46 -0
  238. django_spec_mcp-0.1.0/django_spec_mcp/types/json_rpc_id.py +8 -0
  239. django_spec_mcp-0.1.0/django_spec_mcp/types/jsonrpc_version.py +7 -0
  240. django_spec_mcp-0.1.0/django_spec_mcp/types/max_completion_values.py +7 -0
  241. django_spec_mcp-0.1.0/django_spec_mcp/types/mcp_error_header.py +18 -0
  242. django_spec_mcp-0.1.0/django_spec_mcp/types/modern_protocol_versions.py +14 -0
  243. django_spec_mcp-0.1.0/django_spec_mcp/types/notification_kind.py +39 -0
  244. django_spec_mcp-0.1.0/django_spec_mcp/types/progress_token_meta_key.py +12 -0
  245. django_spec_mcp-0.1.0/django_spec_mcp/types/protocol_version_meta_key.py +11 -0
  246. django_spec_mcp-0.1.0/django_spec_mcp/types/request_state_salt.py +10 -0
  247. django_spec_mcp-0.1.0/django_spec_mcp/types/reserved_post_fetch_keys.py +18 -0
  248. django_spec_mcp-0.1.0/django_spec_mcp/types/resource_encoding.py +36 -0
  249. django_spec_mcp-0.1.0/django_spec_mcp/types/resource_updated_method.py +6 -0
  250. django_spec_mcp-0.1.0/django_spec_mcp/types/result_type.py +33 -0
  251. django_spec_mcp-0.1.0/django_spec_mcp/types/server_info_meta_key.py +11 -0
  252. django_spec_mcp-0.1.0/django_spec_mcp/types/session_missing_hint.py +6 -0
  253. django_spec_mcp-0.1.0/django_spec_mcp/types/session_unknown_hint.py +9 -0
  254. django_spec_mcp-0.1.0/django_spec_mcp/types/sessionless_methods.py +13 -0
  255. django_spec_mcp-0.1.0/django_spec_mcp/types/subscription_id_meta_key.py +12 -0
  256. django_spec_mcp-0.1.0/django_spec_mcp/types/subscriptions_acknowledged_method.py +9 -0
  257. django_spec_mcp-0.1.0/django_spec_mcp/types/subscriptions_listen_method.py +8 -0
  258. django_spec_mcp-0.1.0/django_spec_mcp/types/task_methods.py +12 -0
  259. django_spec_mcp-0.1.0/django_spec_mcp/types/task_policy.py +30 -0
  260. django_spec_mcp-0.1.0/django_spec_mcp/types/task_status.py +36 -0
  261. django_spec_mcp-0.1.0/django_spec_mcp/types/task_status_method.py +10 -0
  262. django_spec_mcp-0.1.0/django_spec_mcp/types/tasks_extension_id.py +13 -0
  263. django_spec_mcp-0.1.0/django_spec_mcp/types/tool_content_kind.py +38 -0
  264. django_spec_mcp-0.1.0/django_spec_mcp/types/ui_extension_id.py +11 -0
  265. django_spec_mcp-0.1.0/django_spec_mcp/types/ui_meta_key.py +7 -0
  266. django_spec_mcp-0.1.0/django_spec_mcp/types/ui_permission.py +19 -0
  267. django_spec_mcp-0.1.0/django_spec_mcp/types/ui_resource_mime_type.py +8 -0
  268. django_spec_mcp-0.1.0/django_spec_mcp/types/ui_visibility.py +24 -0
  269. django_spec_mcp-0.1.0/django_spec_mcp/ui/__init__.py +11 -0
  270. django_spec_mcp-0.1.0/django_spec_mcp/ui/bridge.js +468 -0
  271. django_spec_mcp-0.1.0/django_spec_mcp/ui/build_app_document.py +124 -0
  272. django_spec_mcp-0.1.0/django_spec_mcp/version.py +5 -0
  273. django_spec_mcp-0.1.0/docs/async.md +184 -0
  274. django_spec_mcp-0.1.0/docs/auth.md +453 -0
  275. django_spec_mcp-0.1.0/docs/concepts.md +2369 -0
  276. django_spec_mcp-0.1.0/docs/index.md +128 -0
  277. django_spec_mcp-0.1.0/docs/moving-from-drf-mcp.md +355 -0
  278. django_spec_mcp-0.1.0/docs/observability.md +155 -0
  279. django_spec_mcp-0.1.0/docs/performance.md +160 -0
  280. django_spec_mcp-0.1.0/docs/prompts.md +126 -0
  281. django_spec_mcp-0.1.0/docs/quickstart.md +182 -0
  282. django_spec_mcp-0.1.0/docs/recipes/agent-audience.md +126 -0
  283. django_spec_mcp-0.1.0/docs/recipes/async-auth-backend.md +138 -0
  284. django_spec_mcp-0.1.0/docs/recipes/custom-permission.md +138 -0
  285. django_spec_mcp-0.1.0/docs/recipes/expose-a-service.md +44 -0
  286. django_spec_mcp-0.1.0/docs/recipes/index.md +35 -0
  287. django_spec_mcp-0.1.0/docs/recipes/interactive-view.md +208 -0
  288. django_spec_mcp-0.1.0/docs/recipes/migrating.md +280 -0
  289. django_spec_mcp-0.1.0/docs/recipes/pydantic-ai-client.md +239 -0
  290. django_spec_mcp-0.1.0/docs/recipes/rate-limiting.md +162 -0
  291. django_spec_mcp-0.1.0/docs/recipes/redis-sse-broker.md +80 -0
  292. django_spec_mcp-0.1.0/docs/recipes/register-from-spec-registry.md +245 -0
  293. django_spec_mcp-0.1.0/docs/recipes/selector-tool-with-filterset.md +311 -0
  294. django_spec_mcp-0.1.0/docs/recipes/sse-replay-buffer.md +143 -0
  295. django_spec_mcp-0.1.0/docs/recipes/swap-session-store.md +83 -0
  296. django_spec_mcp-0.1.0/docs/reference/auth.md +51 -0
  297. django_spec_mcp-0.1.0/docs/reference/index.md +14 -0
  298. django_spec_mcp-0.1.0/docs/reference/mcp-server.md +12 -0
  299. django_spec_mcp-0.1.0/docs/reference/output.md +30 -0
  300. django_spec_mcp-0.1.0/docs/reference/protocol.md +87 -0
  301. django_spec_mcp-0.1.0/docs/reference/registries.md +117 -0
  302. django_spec_mcp-0.1.0/docs/reference/settings.md +140 -0
  303. django_spec_mcp-0.1.0/docs/reference/testing.md +76 -0
  304. django_spec_mcp-0.1.0/docs/troubleshooting.md +217 -0
  305. django_spec_mcp-0.1.0/examples/README.md +62 -0
  306. django_spec_mcp-0.1.0/examples/invoicing/README.md +202 -0
  307. django_spec_mcp-0.1.0/examples/invoicing/invoices/__init__.py +0 -0
  308. django_spec_mcp-0.1.0/examples/invoicing/invoices/apps.py +8 -0
  309. django_spec_mcp-0.1.0/examples/invoicing/invoices/auth.py +67 -0
  310. django_spec_mcp-0.1.0/examples/invoicing/invoices/filters.py +32 -0
  311. django_spec_mcp-0.1.0/examples/invoicing/invoices/forms.py +18 -0
  312. django_spec_mcp-0.1.0/examples/invoicing/invoices/mcp.py +299 -0
  313. django_spec_mcp-0.1.0/examples/invoicing/invoices/migrations/0001_initial.py +33 -0
  314. django_spec_mcp-0.1.0/examples/invoicing/invoices/migrations/__init__.py +0 -0
  315. django_spec_mcp-0.1.0/examples/invoicing/invoices/models.py +18 -0
  316. django_spec_mcp-0.1.0/examples/invoicing/invoices/presenters.py +52 -0
  317. django_spec_mcp-0.1.0/examples/invoicing/invoices/selectors.py +78 -0
  318. django_spec_mcp-0.1.0/examples/invoicing/invoices/services.py +68 -0
  319. django_spec_mcp-0.1.0/examples/invoicing/invoices/shapes.py +30 -0
  320. django_spec_mcp-0.1.0/examples/invoicing/invoicing/__init__.py +0 -0
  321. django_spec_mcp-0.1.0/examples/invoicing/invoicing/asgi.py +14 -0
  322. django_spec_mcp-0.1.0/examples/invoicing/invoicing/settings.py +54 -0
  323. django_spec_mcp-0.1.0/examples/invoicing/invoicing/urls.py +15 -0
  324. django_spec_mcp-0.1.0/examples/invoicing/invoicing/wsgi.py +16 -0
  325. django_spec_mcp-0.1.0/examples/invoicing/manage.py +22 -0
  326. django_spec_mcp-0.1.0/examples/job_status/README.md +106 -0
  327. django_spec_mcp-0.1.0/examples/job_status/job_status/__init__.py +0 -0
  328. django_spec_mcp-0.1.0/examples/job_status/job_status/asgi.py +8 -0
  329. django_spec_mcp-0.1.0/examples/job_status/job_status/settings.py +45 -0
  330. django_spec_mcp-0.1.0/examples/job_status/job_status/urls.py +13 -0
  331. django_spec_mcp-0.1.0/examples/job_status/jobs/__init__.py +0 -0
  332. django_spec_mcp-0.1.0/examples/job_status/jobs/apps.py +8 -0
  333. django_spec_mcp-0.1.0/examples/job_status/jobs/mcp.py +106 -0
  334. django_spec_mcp-0.1.0/examples/job_status/jobs/migrations/0001_initial.py +48 -0
  335. django_spec_mcp-0.1.0/examples/job_status/jobs/migrations/__init__.py +0 -0
  336. django_spec_mcp-0.1.0/examples/job_status/jobs/models.py +29 -0
  337. django_spec_mcp-0.1.0/examples/job_status/jobs/selectors.py +18 -0
  338. django_spec_mcp-0.1.0/examples/job_status/jobs/services.py +68 -0
  339. django_spec_mcp-0.1.0/examples/job_status/jobs/shapes.py +18 -0
  340. django_spec_mcp-0.1.0/examples/job_status/manage.py +18 -0
  341. django_spec_mcp-0.1.0/mkdocs.yml +120 -0
  342. django_spec_mcp-0.1.0/pyproject.toml +243 -0
  343. django_spec_mcp-0.1.0/scripts/benchmark.py +217 -0
  344. django_spec_mcp-0.1.0/scripts/check-changelog +148 -0
  345. django_spec_mcp-0.1.0/scripts/check_docs_snippets.py +189 -0
  346. django_spec_mcp-0.1.0/scripts/interop_pydantic_ai.py +215 -0
  347. django_spec_mcp-0.1.0/scripts/release-publish.sh +246 -0
  348. django_spec_mcp-0.1.0/tests/__init__.py +0 -0
  349. django_spec_mcp-0.1.0/tests/_compat/__init__.py +0 -0
  350. django_spec_mcp-0.1.0/tests/_compat/test_reject_awaitable.py +169 -0
  351. django_spec_mcp-0.1.0/tests/adapters/__init__.py +0 -0
  352. django_spec_mcp-0.1.0/tests/adapters/test_meta_passthrough.py +309 -0
  353. django_spec_mcp-0.1.0/tests/adapters/test_required_param_sources.py +207 -0
  354. django_spec_mcp-0.1.0/tests/adapters/test_reserved_input_fields.py +138 -0
  355. django_spec_mcp-0.1.0/tests/adapters/test_selector_to_resource.py +134 -0
  356. django_spec_mcp-0.1.0/tests/adapters/test_service_to_tool.py +170 -0
  357. django_spec_mcp-0.1.0/tests/adapters/test_tool_annotations.py +155 -0
  358. django_spec_mcp-0.1.0/tests/adapters/test_ui_to_resource.py +319 -0
  359. django_spec_mcp-0.1.0/tests/auth/__init__.py +0 -0
  360. django_spec_mcp-0.1.0/tests/auth/backends/__init__.py +0 -0
  361. django_spec_mcp-0.1.0/tests/auth/backends/test_allow_any_backend.py +39 -0
  362. django_spec_mcp-0.1.0/tests/auth/rate_limits/__init__.py +0 -0
  363. django_spec_mcp-0.1.0/tests/auth/rate_limits/conftest.py +13 -0
  364. django_spec_mcp-0.1.0/tests/auth/rate_limits/test_fixed_window_rate_limit.py +17 -0
  365. django_spec_mcp-0.1.0/tests/auth/rate_limits/test_rate_limit_contract.py +104 -0
  366. django_spec_mcp-0.1.0/tests/auth/rate_limits/test_sliding_window_rate_limit.py +33 -0
  367. django_spec_mcp-0.1.0/tests/auth/rate_limits/test_token_bucket_rate_limit.py +46 -0
  368. django_spec_mcp-0.1.0/tests/auth/rate_limits/utils.py +15 -0
  369. django_spec_mcp-0.1.0/tests/auth/test_audience.py +28 -0
  370. django_spec_mcp-0.1.0/tests/auth/test_bearer_challenge.py +64 -0
  371. django_spec_mcp-0.1.0/tests/auth/test_principal_for_token.py +75 -0
  372. django_spec_mcp-0.1.0/tests/auth/test_protected_resource_metadata.py +34 -0
  373. django_spec_mcp-0.1.0/tests/auth/test_response_builders.py +29 -0
  374. django_spec_mcp-0.1.0/tests/auth/test_token_info.py +23 -0
  375. django_spec_mcp-0.1.0/tests/auth/test_worked_backend.py +96 -0
  376. django_spec_mcp-0.1.0/tests/conformance/__init__.py +0 -0
  377. django_spec_mcp-0.1.0/tests/conformance/conftest.py +13 -0
  378. django_spec_mcp-0.1.0/tests/conformance/mcp.py +87 -0
  379. django_spec_mcp-0.1.0/tests/conformance/test_error_codes.py +126 -0
  380. django_spec_mcp-0.1.0/tests/conformance/test_register_tools_parity.py +113 -0
  381. django_spec_mcp-0.1.0/tests/conformance/test_spec_field_honoring.py +31 -0
  382. django_spec_mcp-0.1.0/tests/conformance/test_unknown_arguments_matrix.py +102 -0
  383. django_spec_mcp-0.1.0/tests/conformance/test_urls.py +30 -0
  384. django_spec_mcp-0.1.0/tests/conformance/urls.py +36 -0
  385. django_spec_mcp-0.1.0/tests/conftest.py +81 -0
  386. django_spec_mcp-0.1.0/tests/conftest_settings.py +67 -0
  387. django_spec_mcp-0.1.0/tests/elicitation/__init__.py +0 -0
  388. django_spec_mcp-0.1.0/tests/elicitation/conftest.py +135 -0
  389. django_spec_mcp-0.1.0/tests/elicitation/test_asking_for_input.py +521 -0
  390. django_spec_mcp-0.1.0/tests/elicitation/test_elicitation_endtoend.py +133 -0
  391. django_spec_mcp-0.1.0/tests/elicitation/test_request_state.py +146 -0
  392. django_spec_mcp-0.1.0/tests/elicitation/test_requested_schema.py +140 -0
  393. django_spec_mcp-0.1.0/tests/elicitation/test_wire_shapes.py +155 -0
  394. django_spec_mcp-0.1.0/tests/elicitation/urls.py +18 -0
  395. django_spec_mcp-0.1.0/tests/handlers/__init__.py +0 -0
  396. django_spec_mcp-0.1.0/tests/handlers/test_a_dataclass_input_is_laid_back.py +141 -0
  397. django_spec_mcp-0.1.0/tests/handlers/test_a_denied_caller_learns_nothing_of_the_target.py +280 -0
  398. django_spec_mcp-0.1.0/tests/handlers/test_a_kernel_spec_answers_before_the_charge.py +1188 -0
  399. django_spec_mcp-0.1.0/tests/handlers/test_a_namesake_default_is_evaluated_off_the_event_loop.py +79 -0
  400. django_spec_mcp-0.1.0/tests/handlers/test_a_permission_writing_its_arguments.py +209 -0
  401. django_spec_mcp-0.1.0/tests/handlers/test_an_answer_is_judged_on_the_arguments_it_changes.py +205 -0
  402. django_spec_mcp-0.1.0/tests/handlers/test_an_answer_is_judged_on_the_route_it_names.py +380 -0
  403. django_spec_mcp-0.1.0/tests/handlers/test_argument_validation_is_a_tool_error.py +263 -0
  404. django_spec_mcp-0.1.0/tests/handlers/test_async_handler_branches.py +289 -0
  405. django_spec_mcp-0.1.0/tests/handlers/test_call_spec_tool.py +307 -0
  406. django_spec_mcp-0.1.0/tests/handlers/test_capability_honesty.py +223 -0
  407. django_spec_mcp-0.1.0/tests/handlers/test_closed_service_schema.py +180 -0
  408. django_spec_mcp-0.1.0/tests/handlers/test_completion_complete.py +708 -0
  409. django_spec_mcp-0.1.0/tests/handlers/test_copy_request.py +459 -0
  410. django_spec_mcp-0.1.0/tests/handlers/test_dispatch_gains.py +142 -0
  411. django_spec_mcp-0.1.0/tests/handlers/test_every_route_strips_pagination_keys.py +183 -0
  412. django_spec_mcp-0.1.0/tests/handlers/test_handler_branches.py +404 -0
  413. django_spec_mcp-0.1.0/tests/handlers/test_handler_error_paths.py +123 -0
  414. django_spec_mcp-0.1.0/tests/handlers/test_instance_selector_tools.py +289 -0
  415. django_spec_mcp-0.1.0/tests/handlers/test_is_binding_listable.py +344 -0
  416. django_spec_mcp-0.1.0/tests/handlers/test_judge_binding.py +491 -0
  417. django_spec_mcp-0.1.0/tests/handlers/test_kernel_handbuilt_bindings.py +176 -0
  418. django_spec_mcp-0.1.0/tests/handlers/test_kernel_presenter_refusal.py +305 -0
  419. django_spec_mcp-0.1.0/tests/handlers/test_kernel_refusals.py +318 -0
  420. django_spec_mcp-0.1.0/tests/handlers/test_kernel_resource_object_check.py +113 -0
  421. django_spec_mcp-0.1.0/tests/handlers/test_kernel_selector_rounds.py +142 -0
  422. django_spec_mcp-0.1.0/tests/handlers/test_kernel_shell_knobs.py +132 -0
  423. django_spec_mcp-0.1.0/tests/handlers/test_kernel_tool_calls.py +379 -0
  424. django_spec_mcp-0.1.0/tests/handlers/test_kernel_unguarded.py +190 -0
  425. django_spec_mcp-0.1.0/tests/handlers/test_kwargs_providers.py +218 -0
  426. django_spec_mcp-0.1.0/tests/handlers/test_list_output_conforms.py +183 -0
  427. django_spec_mcp-0.1.0/tests/handlers/test_listings_filter.py +529 -0
  428. django_spec_mcp-0.1.0/tests/handlers/test_many_argument_tools.py +275 -0
  429. django_spec_mcp-0.1.0/tests/handlers/test_missing_required_argument.py +509 -0
  430. django_spec_mcp-0.1.0/tests/handlers/test_offered_tools.py +488 -0
  431. django_spec_mcp-0.1.0/tests/handlers/test_outbound_bounds.py +603 -0
  432. django_spec_mcp-0.1.0/tests/handlers/test_pagination.py +93 -0
  433. django_spec_mcp-0.1.0/tests/handlers/test_pagination_handlers.py +115 -0
  434. django_spec_mcp-0.1.0/tests/handlers/test_pool_seed_precedence.py +355 -0
  435. django_spec_mcp-0.1.0/tests/handlers/test_prompts.py +395 -0
  436. django_spec_mcp-0.1.0/tests/handlers/test_rate_limits.py +126 -0
  437. django_spec_mcp-0.1.0/tests/handlers/test_request_user_is_the_tokens_principal.py +342 -0
  438. django_spec_mcp-0.1.0/tests/handlers/test_resource_permissions_see_uri_variables.py +164 -0
  439. django_spec_mcp-0.1.0/tests/handlers/test_resources_read_dataclass_output.py +368 -0
  440. django_spec_mcp-0.1.0/tests/handlers/test_retrieve_tool_missing.py +165 -0
  441. django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_dispatch.py +438 -0
  442. django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_dispatch_async.py +95 -0
  443. django_spec_mcp-0.1.0/tests/handlers/test_selector_tool_ordering.py +180 -0
  444. django_spec_mcp-0.1.0/tests/handlers/test_server_discover.py +207 -0
  445. django_spec_mcp-0.1.0/tests/handlers/test_service_error_code.py +121 -0
  446. django_spec_mcp-0.1.0/tests/handlers/test_spec_shaping_and_context.py +163 -0
  447. django_spec_mcp-0.1.0/tests/handlers/test_tools_list_affordances.py +197 -0
  448. django_spec_mcp-0.1.0/tests/handlers/test_ui_resources.py +253 -0
  449. django_spec_mcp-0.1.0/tests/handlers/test_unknown_arguments_policy.py +188 -0
  450. django_spec_mcp-0.1.0/tests/handlers/test_view_read_async_encodes_off_the_loop.py +45 -0
  451. django_spec_mcp-0.1.0/tests/handlers/test_view_resources.py +113 -0
  452. django_spec_mcp-0.1.0/tests/kernel_client.py +55 -0
  453. django_spec_mcp-0.1.0/tests/output/__init__.py +0 -0
  454. django_spec_mcp-0.1.0/tests/output/conftest.py +9 -0
  455. django_spec_mcp-0.1.0/tests/output/test_build_resource_contents.py +185 -0
  456. django_spec_mcp-0.1.0/tests/output/test_content_blocks.py +338 -0
  457. django_spec_mcp-0.1.0/tests/output/test_encode_json.py +108 -0
  458. django_spec_mcp-0.1.0/tests/output/test_encode_resource_contents.py +305 -0
  459. django_spec_mcp-0.1.0/tests/output/test_enforce_result_bytes.py +41 -0
  460. django_spec_mcp-0.1.0/tests/output/test_error_tool_result.py +32 -0
  461. django_spec_mcp-0.1.0/tests/output/test_resolve_structured_output.py +91 -0
  462. django_spec_mcp-0.1.0/tests/output/test_tool_result.py +92 -0
  463. django_spec_mcp-0.1.0/tests/protocol/__init__.py +0 -0
  464. django_spec_mcp-0.1.0/tests/protocol/test_build_server_info.py +42 -0
  465. django_spec_mcp-0.1.0/tests/protocol/test_dataclasses.py +369 -0
  466. django_spec_mcp-0.1.0/tests/protocol/test_icon.py +55 -0
  467. django_spec_mcp-0.1.0/tests/protocol/test_wire_meta.py +95 -0
  468. django_spec_mcp-0.1.0/tests/registry/__init__.py +0 -0
  469. django_spec_mcp-0.1.0/tests/registry/test_prompt_registry.py +28 -0
  470. django_spec_mcp-0.1.0/tests/registry/test_resource_registry.py +112 -0
  471. django_spec_mcp-0.1.0/tests/registry/test_tool_registry.py +38 -0
  472. django_spec_mcp-0.1.0/tests/registry/types/test_audience_projection_override.py +87 -0
  473. django_spec_mcp-0.1.0/tests/schema/__init__.py +0 -0
  474. django_spec_mcp-0.1.0/tests/schema/test_agent_conventions.py +101 -0
  475. django_spec_mcp-0.1.0/tests/schema/test_agent_output_schema.py +142 -0
  476. django_spec_mcp-0.1.0/tests/schema/test_dropped_edge_seeds_are_not_advertised.py +95 -0
  477. django_spec_mcp-0.1.0/tests/schema/test_input_output_schema.py +141 -0
  478. django_spec_mcp-0.1.0/tests/schema/test_kernel_output_presents_nothing.py +210 -0
  479. django_spec_mcp-0.1.0/tests/schema/test_kernel_tool_input_schema.py +138 -0
  480. django_spec_mcp-0.1.0/tests/schema/test_selector_tool_requiredness.py +349 -0
  481. django_spec_mcp-0.1.0/tests/schema/test_selector_tool_schema.py +128 -0
  482. django_spec_mcp-0.1.0/tests/schema/test_service_tool_schema.py +139 -0
  483. django_spec_mcp-0.1.0/tests/schema/test_service_tool_spread_parameters.py +286 -0
  484. django_spec_mcp-0.1.0/tests/schema/test_service_tool_target_lookup.py +264 -0
  485. django_spec_mcp-0.1.0/tests/schema/test_utils.py +21 -0
  486. django_spec_mcp-0.1.0/tests/schema/types/__init__.py +0 -0
  487. django_spec_mcp-0.1.0/tests/schema/types/test_agent_conventions.py +213 -0
  488. django_spec_mcp-0.1.0/tests/server/__init__.py +0 -0
  489. django_spec_mcp-0.1.0/tests/server/test_a_mount_hands_the_view_every_collaborator.py +55 -0
  490. django_spec_mcp-0.1.0/tests/server/test_agent_contract_registration.py +123 -0
  491. django_spec_mcp-0.1.0/tests/server/test_call_seed_names_on_a_kernel_tool.py +136 -0
  492. django_spec_mcp-0.1.0/tests/server/test_mcp_server.py +186 -0
  493. django_spec_mcp-0.1.0/tests/server/test_mcp_server_in_process.py +280 -0
  494. django_spec_mcp-0.1.0/tests/server/test_mcp_server_notify.py +82 -0
  495. django_spec_mcp-0.1.0/tests/server/test_mcp_server_unavailable_tools.py +337 -0
  496. django_spec_mcp-0.1.0/tests/server/test_mount_checks_auth_backend.py +90 -0
  497. django_spec_mcp-0.1.0/tests/server/test_register_kernel_specs.py +511 -0
  498. django_spec_mcp-0.1.0/tests/server/test_registration_compiles_the_spec.py +107 -0
  499. django_spec_mcp-0.1.0/tests/server/test_resource_and_prompt_registration_guards.py +166 -0
  500. django_spec_mcp-0.1.0/tests/server/test_selector_parameter_shadowing.py +113 -0
  501. django_spec_mcp-0.1.0/tests/server/test_server_arguments.py +344 -0
  502. django_spec_mcp-0.1.0/tests/server/test_server_arguments_on_every_registrar.py +111 -0
  503. django_spec_mcp-0.1.0/tests/server/test_ui_tool_linkage.py +201 -0
  504. django_spec_mcp-0.1.0/tests/server/test_unbounded_list_warning.py +161 -0
  505. django_spec_mcp-0.1.0/tests/server/test_undescribed_warning.py +137 -0
  506. django_spec_mcp-0.1.0/tests/subscriptions/kernel_urls.py +67 -0
  507. django_spec_mcp-0.1.0/tests/subscriptions/test_invalidates.py +491 -0
  508. django_spec_mcp-0.1.0/tests/subscriptions/test_redis_subscription_broker.py +238 -0
  509. django_spec_mcp-0.1.0/tests/subscriptions/test_subscription_core.py +598 -0
  510. django_spec_mcp-0.1.0/tests/subscriptions/test_subscription_stream.py +234 -0
  511. django_spec_mcp-0.1.0/tests/subscriptions/test_subscriptions_endtoend.py +308 -0
  512. django_spec_mcp-0.1.0/tests/subscriptions/test_task_notifications.py +243 -0
  513. django_spec_mcp-0.1.0/tests/tasks/conftest.py +150 -0
  514. django_spec_mcp-0.1.0/tests/tasks/test_a_task_runs_with_the_bound_value.py +83 -0
  515. django_spec_mcp-0.1.0/tests/tasks/test_task_lifecycle.py +544 -0
  516. django_spec_mcp-0.1.0/tests/tasks/test_task_progress.py +214 -0
  517. django_spec_mcp-0.1.0/tests/tasks/test_task_stores.py +364 -0
  518. django_spec_mcp-0.1.0/tests/tasks/test_task_wire_shape.py +392 -0
  519. django_spec_mcp-0.1.0/tests/tasks/test_tasks_endtoend.py +288 -0
  520. django_spec_mcp-0.1.0/tests/tasks/test_tasks_methods.py +276 -0
  521. django_spec_mcp-0.1.0/tests/test_affordances_endtoend.py +106 -0
  522. django_spec_mcp-0.1.0/tests/test_agent_audience_endtoend.py +107 -0
  523. django_spec_mcp-0.1.0/tests/test_agent_conventions_endtoend.py +212 -0
  524. django_spec_mcp-0.1.0/tests/test_conf.py +27 -0
  525. django_spec_mcp-0.1.0/tests/test_consumer_ergonomics.py +171 -0
  526. django_spec_mcp-0.1.0/tests/test_display_metadata.py +80 -0
  527. django_spec_mcp-0.1.0/tests/test_dropped_surface.py +258 -0
  528. django_spec_mcp-0.1.0/tests/test_empty_result_endtoend.py +385 -0
  529. django_spec_mcp-0.1.0/tests/test_endtoend.py +237 -0
  530. django_spec_mcp-0.1.0/tests/test_examples_smoke.py +708 -0
  531. django_spec_mcp-0.1.0/tests/test_icons_endtoend.py +194 -0
  532. django_spec_mcp-0.1.0/tests/test_kernel_endtoend.py +83 -0
  533. django_spec_mcp-0.1.0/tests/test_multi_server.py +158 -0
  534. django_spec_mcp-0.1.0/tests/test_offered_tools_endtoend.py +131 -0
  535. django_spec_mcp-0.1.0/tests/test_pool_seeds_endtoend.py +371 -0
  536. django_spec_mcp-0.1.0/tests/test_prompts_endtoend.py +89 -0
  537. django_spec_mcp-0.1.0/tests/test_public_surface.py +101 -0
  538. django_spec_mcp-0.1.0/tests/test_re_exports.py +51 -0
  539. django_spec_mcp-0.1.0/tests/test_root_re_exports.py +43 -0
  540. django_spec_mcp-0.1.0/tests/test_scripts_smoke.py +156 -0
  541. django_spec_mcp-0.1.0/tests/test_stored_names.py +69 -0
  542. django_spec_mcp-0.1.0/tests/test_tracing.py +197 -0
  543. django_spec_mcp-0.1.0/tests/test_validation_value_echo.py +206 -0
  544. django_spec_mcp-0.1.0/tests/testapp/__init__.py +0 -0
  545. django_spec_mcp-0.1.0/tests/testapp/affordances.py +67 -0
  546. django_spec_mcp-0.1.0/tests/testapp/apps.py +9 -0
  547. django_spec_mcp-0.1.0/tests/testapp/async_urls.py +11 -0
  548. django_spec_mcp-0.1.0/tests/testapp/auth_backend.py +36 -0
  549. django_spec_mcp-0.1.0/tests/testapp/conventions.py +111 -0
  550. django_spec_mcp-0.1.0/tests/testapp/kernel_shapes.py +37 -0
  551. django_spec_mcp-0.1.0/tests/testapp/mcp.py +105 -0
  552. django_spec_mcp-0.1.0/tests/testapp/models.py +15 -0
  553. django_spec_mcp-0.1.0/tests/testapp/multi_urls.py +36 -0
  554. django_spec_mcp-0.1.0/tests/testapp/permissions.py +18 -0
  555. django_spec_mcp-0.1.0/tests/testapp/services.py +43 -0
  556. django_spec_mcp-0.1.0/tests/testapp/templates/mcp_ui/body.html +9 -0
  557. django_spec_mcp-0.1.0/tests/testapp/templates/mcp_ui/view.html +5 -0
  558. django_spec_mcp-0.1.0/tests/testapp/urlconf_for.py +35 -0
  559. django_spec_mcp-0.1.0/tests/testapp/urls.py +11 -0
  560. django_spec_mcp-0.1.0/tests/testing/__init__.py +0 -0
  561. django_spec_mcp-0.1.0/tests/testing/test_assert_tool_result_conforms.py +252 -0
  562. django_spec_mcp-0.1.0/tests/transport/__init__.py +0 -0
  563. django_spec_mcp-0.1.0/tests/transport/conftest.py +87 -0
  564. django_spec_mcp-0.1.0/tests/transport/test_a_challenge_names_an_error_only_for_a_credential_sent.py +103 -0
  565. django_spec_mcp-0.1.0/tests/transport/test_a_session_rejection_logs_on_its_views_logger.py +27 -0
  566. django_spec_mcp-0.1.0/tests/transport/test_accept_naming_only_a_stream.py +76 -0
  567. django_spec_mcp-0.1.0/tests/transport/test_agent_conventions_over_http.py +86 -0
  568. django_spec_mcp-0.1.0/tests/transport/test_an_auth_backend_that_raises.py +81 -0
  569. django_spec_mcp-0.1.0/tests/transport/test_async_streamable_http_view.py +405 -0
  570. django_spec_mcp-0.1.0/tests/transport/test_dispatch_backstop.py +689 -0
  571. django_spec_mcp-0.1.0/tests/transport/test_endpoint_http_semantics.py +371 -0
  572. django_spec_mcp-0.1.0/tests/transport/test_in_memory_sse_replay_buffer.py +87 -0
  573. django_spec_mcp-0.1.0/tests/transport/test_json_bodies_use_the_json_dumps_defaults.py +66 -0
  574. django_spec_mcp-0.1.0/tests/transport/test_modern_era.py +415 -0
  575. django_spec_mcp-0.1.0/tests/transport/test_origin_validation.py +30 -0
  576. django_spec_mcp-0.1.0/tests/transport/test_progress_stream.py +693 -0
  577. django_spec_mcp-0.1.0/tests/transport/test_protocol_version.py +80 -0
  578. django_spec_mcp-0.1.0/tests/transport/test_redis_sse_broker.py +181 -0
  579. django_spec_mcp-0.1.0/tests/transport/test_redis_sse_replay_buffer.py +205 -0
  580. django_spec_mcp-0.1.0/tests/transport/test_review_findings.py +271 -0
  581. django_spec_mcp-0.1.0/tests/transport/test_session_security.py +268 -0
  582. django_spec_mcp-0.1.0/tests/transport/test_session_stores.py +171 -0
  583. django_spec_mcp-0.1.0/tests/transport/test_sessionless_legacy.py +225 -0
  584. django_spec_mcp-0.1.0/tests/transport/test_sse_broker.py +96 -0
  585. django_spec_mcp-0.1.0/tests/transport/test_sse_endpoint.py +255 -0
  586. django_spec_mcp-0.1.0/tests/transport/test_sse_response.py +207 -0
  587. django_spec_mcp-0.1.0/tests/transport/test_streamable_http_view.py +296 -0
  588. django_spec_mcp-0.1.0/tests/transport/test_streamable_http_view_branches.py +211 -0
  589. django_spec_mcp-0.1.0/tests/transport/test_the_request_carries_the_tokens_principal.py +255 -0
  590. django_spec_mcp-0.1.0/tests/transport/test_unauthenticated_method_support.py +216 -0
  591. django_spec_mcp-0.1.0/tests/transport/test_utils.py +22 -0
  592. django_spec_mcp-0.1.0/tests/transport/types/__init__.py +0 -0
  593. django_spec_mcp-0.1.0/tests/transport/types/test_sse_replay_buffer.py +94 -0
  594. django_spec_mcp-0.1.0/tests/types/__init__.py +0 -0
  595. django_spec_mcp-0.1.0/tests/types/test_json_rpc_error_code.py +14 -0
  596. django_spec_mcp-0.1.0/tests/types/test_jsonrpc_version.py +9 -0
  597. django_spec_mcp-0.1.0/tests/types/test_reserved_post_fetch_keys.py +19 -0
  598. django_spec_mcp-0.1.0/tests/types/test_types_imports.py +45 -0
  599. django_spec_mcp-0.1.0/tests/ui/__init__.py +0 -0
  600. django_spec_mcp-0.1.0/tests/ui/bridge_harness.mjs +370 -0
  601. django_spec_mcp-0.1.0/tests/ui/test_bridge.py +252 -0
  602. django_spec_mcp-0.1.0/tests/ui/test_build_app_document.py +131 -0
  603. django_spec_mcp-0.1.0/tests/utils.py +226 -0
  604. 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