djangorestframework-mcp-server 0.1.0__py3-none-any.whl

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 (110) hide show
  1. djangorestframework_mcp_server-0.1.0.dist-info/METADATA +151 -0
  2. djangorestframework_mcp_server-0.1.0.dist-info/RECORD +110 -0
  3. djangorestframework_mcp_server-0.1.0.dist-info/WHEEL +4 -0
  4. djangorestframework_mcp_server-0.1.0.dist-info/licenses/LICENSE +21 -0
  5. rest_framework_mcp/__init__.py +80 -0
  6. rest_framework_mcp/_compat/__init__.py +3 -0
  7. rest_framework_mcp/_compat/acall.py +28 -0
  8. rest_framework_mcp/_compat/tracing.py +63 -0
  9. rest_framework_mcp/_compat/utils.py +39 -0
  10. rest_framework_mcp/adapters/__init__.py +4 -0
  11. rest_framework_mcp/adapters/selector_to_resource.py +71 -0
  12. rest_framework_mcp/adapters/service_to_tool.py +40 -0
  13. rest_framework_mcp/auth/__init__.py +19 -0
  14. rest_framework_mcp/auth/audience.py +28 -0
  15. rest_framework_mcp/auth/auth_backend.py +33 -0
  16. rest_framework_mcp/auth/backends/__init__.py +4 -0
  17. rest_framework_mcp/auth/backends/allow_any_backend.py +41 -0
  18. rest_framework_mcp/auth/backends/django_oauth_toolkit_backend.py +101 -0
  19. rest_framework_mcp/auth/insufficient_scope_response.py +20 -0
  20. rest_framework_mcp/auth/permissions/__init__.py +5 -0
  21. rest_framework_mcp/auth/permissions/django_perm_required.py +29 -0
  22. rest_framework_mcp/auth/permissions/mcp_permission.py +35 -0
  23. rest_framework_mcp/auth/permissions/scope_required.py +25 -0
  24. rest_framework_mcp/auth/protected_resource_metadata.py +35 -0
  25. rest_framework_mcp/auth/rate_limits/__init__.py +11 -0
  26. rest_framework_mcp/auth/rate_limits/fixed_window_rate_limit.py +83 -0
  27. rest_framework_mcp/auth/rate_limits/mcp_rate_limit.py +33 -0
  28. rest_framework_mcp/auth/rate_limits/sliding_window_rate_limit.py +91 -0
  29. rest_framework_mcp/auth/rate_limits/token_bucket_rate_limit.py +98 -0
  30. rest_framework_mcp/auth/token_info.py +36 -0
  31. rest_framework_mcp/auth/unauthenticated_response.py +20 -0
  32. rest_framework_mcp/conf.py +58 -0
  33. rest_framework_mcp/handlers/__init__.py +23 -0
  34. rest_framework_mcp/handlers/async_dispatch.py +35 -0
  35. rest_framework_mcp/handlers/context.py +31 -0
  36. rest_framework_mcp/handlers/dispatch.py +61 -0
  37. rest_framework_mcp/handlers/handle_initialize.py +56 -0
  38. rest_framework_mcp/handlers/handle_ping.py +18 -0
  39. rest_framework_mcp/handlers/handle_prompts_get.py +106 -0
  40. rest_framework_mcp/handlers/handle_prompts_get_async.py +103 -0
  41. rest_framework_mcp/handlers/handle_prompts_list.py +45 -0
  42. rest_framework_mcp/handlers/handle_resources_list.py +45 -0
  43. rest_framework_mcp/handlers/handle_resources_read.py +106 -0
  44. rest_framework_mcp/handlers/handle_resources_read_async.py +106 -0
  45. rest_framework_mcp/handlers/handle_resources_templates_list.py +42 -0
  46. rest_framework_mcp/handlers/handle_tools_call.py +203 -0
  47. rest_framework_mcp/handlers/handle_tools_call_async.py +151 -0
  48. rest_framework_mcp/handlers/handle_tools_list.py +52 -0
  49. rest_framework_mcp/handlers/pagination.py +63 -0
  50. rest_framework_mcp/handlers/render_prompt_messages.py +42 -0
  51. rest_framework_mcp/handlers/utils.py +113 -0
  52. rest_framework_mcp/output/__init__.py +6 -0
  53. rest_framework_mcp/output/encode_json.py +17 -0
  54. rest_framework_mcp/output/encode_toon.py +30 -0
  55. rest_framework_mcp/output/format.py +34 -0
  56. rest_framework_mcp/output/tool_result.py +58 -0
  57. rest_framework_mcp/protocol/__init__.py +47 -0
  58. rest_framework_mcp/protocol/client_capabilities.py +33 -0
  59. rest_framework_mcp/protocol/get_prompt_result.py +27 -0
  60. rest_framework_mcp/protocol/implementation.py +18 -0
  61. rest_framework_mcp/protocol/initialize_params.py +37 -0
  62. rest_framework_mcp/protocol/initialize_result.py +30 -0
  63. rest_framework_mcp/protocol/json_rpc_error.py +26 -0
  64. rest_framework_mcp/protocol/json_rpc_error_code.py +29 -0
  65. rest_framework_mcp/protocol/json_rpc_message.py +9 -0
  66. rest_framework_mcp/protocol/json_rpc_notification.py +24 -0
  67. rest_framework_mcp/protocol/json_rpc_request.py +25 -0
  68. rest_framework_mcp/protocol/json_rpc_response.py +28 -0
  69. rest_framework_mcp/protocol/jsonrpc_constants.py +7 -0
  70. rest_framework_mcp/protocol/parse_message.py +50 -0
  71. rest_framework_mcp/protocol/prompt.py +37 -0
  72. rest_framework_mcp/protocol/prompt_argument.py +29 -0
  73. rest_framework_mcp/protocol/prompt_message.py +29 -0
  74. rest_framework_mcp/protocol/resource.py +38 -0
  75. rest_framework_mcp/protocol/resource_contents.py +31 -0
  76. rest_framework_mcp/protocol/resource_template.py +35 -0
  77. rest_framework_mcp/protocol/server_capabilities.py +38 -0
  78. rest_framework_mcp/protocol/tool.py +36 -0
  79. rest_framework_mcp/protocol/tool_content_block.py +34 -0
  80. rest_framework_mcp/protocol/tool_result.py +35 -0
  81. rest_framework_mcp/registry/__init__.py +6 -0
  82. rest_framework_mcp/registry/prompt_binding.py +37 -0
  83. rest_framework_mcp/registry/prompt_registry.py +34 -0
  84. rest_framework_mcp/registry/resource_binding.py +55 -0
  85. rest_framework_mcp/registry/resource_registry.py +66 -0
  86. rest_framework_mcp/registry/tool_binding.py +47 -0
  87. rest_framework_mcp/registry/tool_registry.py +35 -0
  88. rest_framework_mcp/schema/__init__.py +4 -0
  89. rest_framework_mcp/schema/input_schema.py +32 -0
  90. rest_framework_mcp/schema/output_schema.py +28 -0
  91. rest_framework_mcp/schema/utils.py +122 -0
  92. rest_framework_mcp/server/__init__.py +3 -0
  93. rest_framework_mcp/server/mcp_server.py +451 -0
  94. rest_framework_mcp/server/mcp_service_view.py +33 -0
  95. rest_framework_mcp/transport/__init__.py +21 -0
  96. rest_framework_mcp/transport/async_streamable_http_view.py +276 -0
  97. rest_framework_mcp/transport/django_cache_session_store.py +35 -0
  98. rest_framework_mcp/transport/in_memory_session_store.py +29 -0
  99. rest_framework_mcp/transport/in_memory_sse_broker.py +63 -0
  100. rest_framework_mcp/transport/in_memory_sse_replay_buffer.py +68 -0
  101. rest_framework_mcp/transport/origin_validation.py +27 -0
  102. rest_framework_mcp/transport/protocol_version.py +24 -0
  103. rest_framework_mcp/transport/redis_sse_broker.py +158 -0
  104. rest_framework_mcp/transport/redis_sse_replay_buffer.py +117 -0
  105. rest_framework_mcp/transport/session_store.py +27 -0
  106. rest_framework_mcp/transport/sse_broker.py +40 -0
  107. rest_framework_mcp/transport/sse_replay_buffer.py +68 -0
  108. rest_framework_mcp/transport/sse_response.py +117 -0
  109. rest_framework_mcp/transport/streamable_http_view.py +226 -0
  110. rest_framework_mcp/version.py +5 -0
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: djangorestframework-mcp-server
3
+ Version: 0.1.0
4
+ Summary: Expose djangorestframework-services services and selectors as an MCP (Model Context Protocol) server.
5
+ Project-URL: Homepage, https://github.com/Artui/djangorestframework-mcp-server
6
+ Project-URL: Repository, https://github.com/Artui/djangorestframework-mcp-server
7
+ Project-URL: Issues, https://github.com/Artui/djangorestframework-mcp-server/issues
8
+ Author-email: Artur Veres <artur8118@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,django,drf,llm,mcp,model-context-protocol,rest-framework
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: Django :: 4.2
16
+ Classifier: Framework :: Django :: 5.0
17
+ Classifier: Framework :: Django :: 5.1
18
+ Classifier: Framework :: Django :: 5.2
19
+ Classifier: Framework :: Django :: 6.0
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Programming Language :: Python :: 3
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Programming Language :: Python :: 3.11
27
+ Classifier: Programming Language :: Python :: 3.12
28
+ Classifier: Programming Language :: Python :: 3.13
29
+ Classifier: Programming Language :: Python :: 3.14
30
+ Classifier: Topic :: Internet :: WWW/HTTP
31
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
+ Requires-Python: >=3.10
33
+ Requires-Dist: django>=4.2
34
+ Requires-Dist: djangorestframework-dataclasses>=1.3.1
35
+ Requires-Dist: djangorestframework-services==0.6.0
36
+ Requires-Dist: djangorestframework>=3.14
37
+ Requires-Dist: typing-extensions>=4.6
38
+ Provides-Extra: oauth
39
+ Requires-Dist: django-oauth-toolkit>=2.3; extra == 'oauth'
40
+ Provides-Extra: otel
41
+ Requires-Dist: opentelemetry-api>=1.27; extra == 'otel'
42
+ Provides-Extra: redis
43
+ Requires-Dist: redis>=5.0; extra == 'redis'
44
+ Provides-Extra: toon
45
+ Requires-Dist: python-toon>=0.1; extra == 'toon'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # djangorestframework-mcp-server
49
+
50
+ [![tests](https://github.com/Artui/djangorestframework-mcp-server/actions/workflows/tests.yml/badge.svg)](https://github.com/Artui/djangorestframework-mcp-server/actions/workflows/tests.yml)
51
+
52
+ Expose [`djangorestframework-services`](https://github.com/arturveres/djangorestframework-services)
53
+ services and selectors as a [Model Context Protocol](https://modelcontextprotocol.io)
54
+ (MCP) server, conforming to MCP **2025-11-25** (Streamable HTTP).
55
+
56
+ > **Status: alpha.** API surfaces and wire details may shift before `0.1` is tagged.
57
+
58
+ ## Idea
59
+
60
+ Register `ServiceSpec` instances directly — no DRF router or viewset
61
+ involvement. The unit of registration is the `ServiceSpec`, not a view.
62
+
63
+ ```python
64
+ from django.urls import include, path
65
+ from rest_framework_services.types.service_spec import ServiceSpec
66
+
67
+ from rest_framework_mcp import MCPServer
68
+
69
+ server = MCPServer(name="my-app")
70
+
71
+ server.register_tool(
72
+ name="invoices.create",
73
+ spec=ServiceSpec(
74
+ service=create_invoice,
75
+ input_serializer=InvoiceInputSerializer,
76
+ output_serializer=InvoiceOutputSerializer,
77
+ ),
78
+ )
79
+
80
+ server.register_resource(
81
+ name="invoice",
82
+ uri_template="invoices://{pk}",
83
+ selector=get_invoice,
84
+ output_serializer=InvoiceOutputSerializer,
85
+ )
86
+
87
+ urlpatterns = [path("mcp/", include(server.urls))]
88
+ ```
89
+
90
+ A decorator form is also supported (`@server.tool(...)` / `@server.resource(...)`).
91
+ See the [quickstart](docs/quickstart.md) for the full end-to-end recipe.
92
+
93
+ - **Services** (mutations) → MCP **tools**.
94
+ - **Selectors** (reads) → MCP **resources**.
95
+ - A single `/mcp` endpoint speaks Streamable HTTP. The
96
+ `/.well-known/oauth-protected-resource` endpoint comes mounted alongside.
97
+
98
+ ## What ships in v1
99
+
100
+ - `tools/list`, `tools/call`, `resources/list`, `resources/templates/list`,
101
+ `resources/read`.
102
+ - Pluggable auth: `DjangoOAuthToolkitBackend` (default when DOT is installed)
103
+ and `AllowAnyBackend` (dev only). Per-binding `MCPPermission` classes
104
+ (`ScopeRequired`, `DjangoPermRequired`) plus your own.
105
+ - RFC 8707 audience binding when `RESOURCE_URL` is configured; RFC 9728 PRM
106
+ served from the configured backend.
107
+ - Output formats: JSON (default) and TOON (token-oriented; optional extra
108
+ with safe JSON fallback).
109
+ - Origin allowlist + protocol-version validation + session lifecycle per
110
+ the 2025-11-25 transport rules.
111
+
112
+ ## Install
113
+
114
+ ```bash
115
+ pip install djangorestframework-mcp-server # JSON only
116
+ pip install "djangorestframework-mcp-server[toon]" # +TOON encoder
117
+ pip install "djangorestframework-mcp-server[oauth]" # +django-oauth-toolkit backend
118
+ pip install "djangorestframework-mcp-server[redis]" # +Redis SSE broker for multi-worker ASGI
119
+ pip install "djangorestframework-mcp-server[otel]" # +OpenTelemetry instrumentation
120
+ pip install "djangorestframework-mcp-server[toon,oauth,redis,otel]" # all optional extras
121
+ ```
122
+
123
+ Optional extras degrade gracefully: TOON falls back to JSON with a runtime
124
+ warning if `python-toon` is not installed, and the OAuth backend module
125
+ imports cleanly without `oauth2_provider` — the `ImportError` only fires when
126
+ you actually configure it.
127
+
128
+ ## Try it
129
+
130
+ Install [mcp-inspector](https://github.com/modelcontextprotocol/inspector) and
131
+ point it at your dev server:
132
+
133
+ ```bash
134
+ npx @modelcontextprotocol/inspector --url http://localhost:8000/mcp/
135
+ ```
136
+
137
+ Inspector lists tools, fills in arguments from the generated JSON Schema, and
138
+ walks the OAuth auth flow against your configured Authorization Server.
139
+
140
+ ## Documentation
141
+
142
+ - [Quickstart](docs/quickstart.md) — copy-pasteable end-to-end.
143
+ - [Concepts](docs/concepts.md) — tools vs resources, sessions, output formats.
144
+ - [Authentication](docs/auth.md) — backends, permissions, audience binding,
145
+ bring-your-own AS recipe.
146
+ - [Recipes](docs/recipes/index.md) — focused cookbook entries.
147
+ - [Reference](docs/reference/index.md) — autodocs for every public symbol.
148
+
149
+ ## License
150
+
151
+ MIT.
@@ -0,0 +1,110 @@
1
+ rest_framework_mcp/__init__.py,sha256=zBpah0qrQdvM3fUXRiW0NVhmmF0mVd_m7Iq3iap-U_s,3787
2
+ rest_framework_mcp/conf.py,sha256=HyT1tt9XhIubL982syLIi5NK3nj0MzN5TYB4O-MCF0o,2732
3
+ rest_framework_mcp/version.py,sha256=YtKE5kaJx3eLef8IxINR-rwWwGss90i5tQB7CyRvkj8,90
4
+ rest_framework_mcp/_compat/__init__.py,sha256=kaiQ6o7AKBrlTjyB1L29U3bp7_x8_fvNqUD1U00onTU,72
5
+ rest_framework_mcp/_compat/acall.py,sha256=YepNHSp7TmpghGJ5esf0pmZjDkOpLj1heo8k7LCzWn8,994
6
+ rest_framework_mcp/_compat/tracing.py,sha256=eO-DZJkzlm_0f4e4wQ-q2zUZ0LFMeF-nOptE7b7gopU,2154
7
+ rest_framework_mcp/_compat/utils.py,sha256=N2LTGISGQYhXg_2E99PyFdvLQW3Zb88zaLHdFZF5wtU,1664
8
+ rest_framework_mcp/adapters/__init__.py,sha256=1Jc15brrF_1UqSIEj49LlCgKXbgRP0VU4i45s5uM8nM,219
9
+ rest_framework_mcp/adapters/selector_to_resource.py,sha256=G8E4JIhrbceArjEdApzhVvGo-xR2YtvOqkMZ2D61xMc,2593
10
+ rest_framework_mcp/adapters/service_to_tool.py,sha256=Vziqpc3AT3TWhN-nAAmiv7DIa6rDAeNs-u_wJGyHx9A,1131
11
+ rest_framework_mcp/auth/__init__.py,sha256=BVWNlfmLFzsyRZmQYxjVlP9h_39kA4OrPlxjUuP3WGo,885
12
+ rest_framework_mcp/auth/audience.py,sha256=9uYieTk0ete2J_P_jayM-LTTcndg2SajjP20vdQJU60,1098
13
+ rest_framework_mcp/auth/auth_backend.py,sha256=RrnlSR7VRUhCvYoyW4EW3ZJi7KGD7csDpslBSChHnUQ,1093
14
+ rest_framework_mcp/auth/insufficient_scope_response.py,sha256=7B_5ebWHNBo5NctYpxrCHihI3CVUIy0g9PlzMd62GLk,653
15
+ rest_framework_mcp/auth/protected_resource_metadata.py,sha256=tNLgNC7MR8fZXy5j0F0b8otoVvsdDGV1-1w6CJ3hFus,1309
16
+ rest_framework_mcp/auth/token_info.py,sha256=EZkqCcX4A5CtocLCxIxfvohwrO6UKG1CHYbdm7096Us,1239
17
+ rest_framework_mcp/auth/unauthenticated_response.py,sha256=ypI1Wk7pn-L_6dz-3XHA8KlfIVQ3ErfmD3J2wECINS4,669
18
+ rest_framework_mcp/auth/backends/__init__.py,sha256=qm8WIWKB-AxS8fTJXMkGP6_16hgjGt5_thWiZiOVMX8,239
19
+ rest_framework_mcp/auth/backends/allow_any_backend.py,sha256=eeE1bjWttXt0IduQx9Ech4c9-PYJ1NxMk2vNKyTE6nw,1344
20
+ rest_framework_mcp/auth/backends/django_oauth_toolkit_backend.py,sha256=8sN9knRzHLf26VdeCOUY9ADg_WY_dtmiL3sxOTOLZUM,4425
21
+ rest_framework_mcp/auth/permissions/__init__.py,sha256=kHYh5dI1fwxyYl9uly96xi56o7U41TPjQrAGuaLpWmA,310
22
+ rest_framework_mcp/auth/permissions/django_perm_required.py,sha256=meGI-HiIm--pi_1ZRq83-8jdAjGvGd8AgFnxmU2STUc,933
23
+ rest_framework_mcp/auth/permissions/mcp_permission.py,sha256=wmzhzv6Yt4NaR0dHItZsULwNpjqRhe3xTqHDzaeu_b0,1212
24
+ rest_framework_mcp/auth/permissions/scope_required.py,sha256=dAKEgwf-7MDQnThj45jbVj3prRBnKDdwYR0JTOfUw0Q,685
25
+ rest_framework_mcp/auth/rate_limits/__init__.py,sha256=yqPiR8AXNVy9VNt7umJ5A96mpfZfEISC_KlJ5bVP9SI,480
26
+ rest_framework_mcp/auth/rate_limits/fixed_window_rate_limit.py,sha256=0EUKxKGtW-8PrDevjuK1Vyq_Wr8PqaKFCkazBeqij7M,3219
27
+ rest_framework_mcp/auth/rate_limits/mcp_rate_limit.py,sha256=Syw5MVMfNTTchfKayvRXiSVMeBSn_6T7Wu2dhiYm5Nw,1260
28
+ rest_framework_mcp/auth/rate_limits/sliding_window_rate_limit.py,sha256=8P15HN5FyhRwVx33W505LBYiFbrrny30UQEw-EKgLH8,3757
29
+ rest_framework_mcp/auth/rate_limits/token_bucket_rate_limit.py,sha256=O6nW2JtZprsvG1LmUKEEvCdPDK6aMfxfAR1y0GmFjfk,4109
30
+ rest_framework_mcp/handlers/__init__.py,sha256=EPVNRii5oHDhDl3aq28W8dGSvF89B7woOP87U8TyLJA,938
31
+ rest_framework_mcp/handlers/async_dispatch.py,sha256=47Q7OeVNzBmKv5TTQAwiO1vW_b9Rct5f93CBxHrOUD8,1382
32
+ rest_framework_mcp/handlers/context.py,sha256=PYM6PQ3OxsvEpprJTQ4Mg3qMBvt4tubBoVgLgqZVSvA,939
33
+ rest_framework_mcp/handlers/dispatch.py,sha256=V89tvdHWTR29jOlZm17fnRIbdiwW1YhdC3EIc5xVBRM,2472
34
+ rest_framework_mcp/handlers/handle_initialize.py,sha256=XNgnzmpILRWKA4i7CBfj3yQArRKVM_sdOSFk6zIRhIA,2306
35
+ rest_framework_mcp/handlers/handle_ping.py,sha256=nq8mZEnzK9zon9i3KposSkgXSKv_8eNxxswOG73CwrU,474
36
+ rest_framework_mcp/handlers/handle_prompts_get.py,sha256=SNQ1vaYQ1gymGjLpRP8yU9mw8Z15heWM_7992jEABSI,4184
37
+ rest_framework_mcp/handlers/handle_prompts_get_async.py,sha256=LTRvqZZ10RxzHBTBkewCPb-7c8C39PbtiGMmo0Xi1rk,3980
38
+ rest_framework_mcp/handlers/handle_prompts_list.py,sha256=Yct3O_r3ruOt5q8tS1x2gIYS-dYizFje_SWSyK6nH74,1625
39
+ rest_framework_mcp/handlers/handle_resources_list.py,sha256=ttmBIDeiD4cyNsEBiCoMjpfJxtWu2TDdSWxYWrhkqG4,1637
40
+ rest_framework_mcp/handlers/handle_resources_read.py,sha256=f2o2ICfQgLkn_eOGBFdJnIjkXEabyLluU6dkG2G-WFg,4095
41
+ rest_framework_mcp/handlers/handle_resources_read_async.py,sha256=J6e9Hnio4cgPk5q2eKUTVYP2IHjH-hs8oL1RnddK7rg,4175
42
+ rest_framework_mcp/handlers/handle_resources_templates_list.py,sha256=ay-oJT3uxPQlLiM__cBRxTOpKPiS7UtZrU6PAHCTv_Q,1621
43
+ rest_framework_mcp/handlers/handle_tools_call.py,sha256=R5g6Dra0tesq9Bt-tFKowfxJulPcthutNk72ZoryHfo,8605
44
+ rest_framework_mcp/handlers/handle_tools_call_async.py,sha256=ui_RXcBXOO6Ynv0J8CCzpMIdGN1IvSQTVHg2OWoR2pU,6200
45
+ rest_framework_mcp/handlers/handle_tools_list.py,sha256=Cxc1becBFlHvaHundzaOY2mXEarrRBUG40hp48R40xQ,2070
46
+ rest_framework_mcp/handlers/pagination.py,sha256=tc7fYV-4d79gSf8nM_9-K2wgFdI4A5cOp_f7QX51WyU,2318
47
+ rest_framework_mcp/handlers/render_prompt_messages.py,sha256=0yrwymaQujw9mchetXdN8L3s_419Whl0Dkp1FxdiQG8,1673
48
+ rest_framework_mcp/handlers/utils.py,sha256=MXViAZ0X-Z_9drE_UE4AgRvHBKMmv1ME2GDTcorfzK4,4532
49
+ rest_framework_mcp/output/__init__.py,sha256=fP3Tu99o6pOQ7QXhz-zMZNeUqKySP1BGQ5GXlIhVpQg,329
50
+ rest_framework_mcp/output/encode_json.py,sha256=twBZi0ZM7t29nRuoKxKSh2wctThpQEnYN_p-ii_bSvw,514
51
+ rest_framework_mcp/output/encode_toon.py,sha256=6DWyiS5IxY9LDCX1CvrRdselxiWThY7QIDxLiczZeC4,969
52
+ rest_framework_mcp/output/format.py,sha256=ID1D0lKEXaMW4LAoC0F8it4AJyJkPrcnnyjbV00PEz8,1007
53
+ rest_framework_mcp/output/tool_result.py,sha256=_u_zSZmuP-o0KmNYJG6WwjkzgIURbpMoBMBZLLwhfhM,2142
54
+ rest_framework_mcp/protocol/__init__.py,sha256=_O78Qb5aL5E6n_tECZY3nwageVB73Tv81RfCYA07Tes,2050
55
+ rest_framework_mcp/protocol/client_capabilities.py,sha256=ghkzIzY0IttelkscysL7OPxMLO8xXTzWD9_igTMjZfM,1000
56
+ rest_framework_mcp/protocol/get_prompt_result.py,sha256=2viUdc3IgSWGofeAjBcxuHTAFoMs6C2UfOAQ0TLMK_w,784
57
+ rest_framework_mcp/protocol/implementation.py,sha256=58Mx94i4cH6-6m9ughnWrhTYSHkN33TsgOLMVYS5cX4,368
58
+ rest_framework_mcp/protocol/initialize_params.py,sha256=svvmSD2UjG7KARtNuHSEkzMP6yE3ieRtMXMnCRmk9sI,1229
59
+ rest_framework_mcp/protocol/initialize_result.py,sha256=IkFeVlibYhGPW5LISnqro-aa8CUIzD6GqJHjBbmkSmM,882
60
+ rest_framework_mcp/protocol/json_rpc_error.py,sha256=Q3-cJaB7buooOnMzXjulGll0lSLEbc4CHXclwCm1gKE,616
61
+ rest_framework_mcp/protocol/json_rpc_error_code.py,sha256=KIcdOebBg8PBtz-B8l8puq_BXHBBtEQ8LQ_VI0PrVgo,788
62
+ rest_framework_mcp/protocol/json_rpc_message.py,sha256=ngDRdEyiiufZ4I0mmW9hXNmK_FJL4HS3JGRsGgnWAnA,367
63
+ rest_framework_mcp/protocol/json_rpc_notification.py,sha256=jd3ewvMHI8RMDhwScfCwlyA3pyDV-0_JpbnEsclm0GY,650
64
+ rest_framework_mcp/protocol/json_rpc_request.py,sha256=VSjIYkS7qBDr7FC5qX8P_KHZLCV8pyHN28pX6JY8byg,681
65
+ rest_framework_mcp/protocol/json_rpc_response.py,sha256=KVCKI9nVMUvPaCAhEIA0Y5kBd-ODuEbMUP7_QNlHuag,794
66
+ rest_framework_mcp/protocol/jsonrpc_constants.py,sha256=Qn4GfX4OFhvTI42f2pCWCoDUxoPG7_mkvXAUi_WMxiw,139
67
+ rest_framework_mcp/protocol/parse_message.py,sha256=2Ikuj8pA8pXpZBNRfOyoet-NXpjbbka2_6JhEbFlJwc,2158
68
+ rest_framework_mcp/protocol/prompt.py,sha256=b6NA1DdFp_VZlw05k9EhiXKdelqjPvksAQqxCJIFxn4,1183
69
+ rest_framework_mcp/protocol/prompt_argument.py,sha256=aqjEgl3KnsraUnAIHSKhT-1KVht-wnFjTh4Ft_msGPE,776
70
+ rest_framework_mcp/protocol/prompt_message.py,sha256=54yo60M1Q95Jh7Z5p-5HXm30qhIkLGt25WEii6SBqSI,877
71
+ rest_framework_mcp/protocol/resource.py,sha256=anoG5VxftA4iIl6DXK8naFk100mwqUmYdHMkv0nZj0Q,1120
72
+ rest_framework_mcp/protocol/resource_contents.py,sha256=mDmvMfRFDNV6NwkCzE5hmRNYCz1gBJyvXxlPI_9OSdM,788
73
+ rest_framework_mcp/protocol/resource_template.py,sha256=MKC_3gkOaqpBHp-qq-1rPnw2LkH6sYLSvaGGoPlZhyA,1051
74
+ rest_framework_mcp/protocol/server_capabilities.py,sha256=AcpDmFEZK4rLZnxpneWfWb-OvLl4kKUcK370TZpWr-0,1251
75
+ rest_framework_mcp/protocol/tool.py,sha256=o-vZzmcNWn4V_MTGayfxaNpqxoRgsT-SLlFYSnn5bRg,1167
76
+ rest_framework_mcp/protocol/tool_content_block.py,sha256=nZl3ISv84lBQXWrNjtCDaBkMFbHGiyPyc9L8Jw7dqZs,967
77
+ rest_framework_mcp/protocol/tool_result.py,sha256=LxpEETicsO6AE8iiHjw3Vt48iuw07AYkDL2WBT4Va6Y,1177
78
+ rest_framework_mcp/registry/__init__.py,sha256=1aRIry_tvYAPy3sq5f370lews9aCfFAF3aOKa74KzjY,362
79
+ rest_framework_mcp/registry/prompt_binding.py,sha256=j7QhO0Bej_6I-rEmva36aK2UZhPMAOc1Wy5_P4ZurCA,1160
80
+ rest_framework_mcp/registry/prompt_registry.py,sha256=vBq2jWbBqFt4uu-wEZLHSZXfrpBzAHME0k8HCVsXfD4,979
81
+ rest_framework_mcp/registry/resource_binding.py,sha256=KFqoSmhCYO1Ebame-Pn6ieFst3_KyxyXuv5v8uo7TPA,2132
82
+ rest_framework_mcp/registry/resource_registry.py,sha256=6TEUie7VqotovupzgYKj5bC14pSkdniQt57OgmGKANA,2384
83
+ rest_framework_mcp/registry/tool_binding.py,sha256=hgZ6XbLJugvUUt_NIXWlpDO-hSiWxd-g6bM6fUhAb3o,1618
84
+ rest_framework_mcp/registry/tool_registry.py,sha256=SLKDC13Tt1pRru3qyE91Pxr-E9qqiEsr73m6B2YyE9g,1024
85
+ rest_framework_mcp/schema/__init__.py,sha256=3aSBm_fQZCAzoo1PwkeOdOjNZRFI0hymmWjRilf-CPc,199
86
+ rest_framework_mcp/schema/input_schema.py,sha256=BuZKGcXtASAInHnBJoJ8U0_86czW1u8imxTlYrQQRLU,1114
87
+ rest_framework_mcp/schema/output_schema.py,sha256=27TDSpK8TwbveK6KcebiiATHqGld509Fu3AjMJAbAgI,957
88
+ rest_framework_mcp/schema/utils.py,sha256=MbclhlI1mZAOPAUSzMtcYVuI96h4ijbx4uHAl7kDO3c,5351
89
+ rest_framework_mcp/server/__init__.py,sha256=qyAGvV1tEYwY5fV9ZB4NYNa1dOw1XjULcGbqLyIZjO4,84
90
+ rest_framework_mcp/server/mcp_server.py,sha256=7_hxTyQWtE6I6oloVIdbhxC4Kr6qXwYirzglJboFcYc,17484
91
+ rest_framework_mcp/server/mcp_service_view.py,sha256=AlMaDKW1TZFAYDQGd9YRwPiaoItWMxAjbeTg0u-LkPg,1209
92
+ rest_framework_mcp/transport/__init__.py,sha256=oT8Cqwypxpf9iPbE5XDK8SvC2u5gKYGsQuHFg_FaCcU,971
93
+ rest_framework_mcp/transport/async_streamable_http_view.py,sha256=Eo3-O1SJCBeYPjIGLiu52GX2q7FgI-HWVd07oJu7p50,11906
94
+ rest_framework_mcp/transport/django_cache_session_store.py,sha256=bW01KyNTV_nZGPWYYHVEFr5tsoWHTkzaFlrr2ErP6UU,958
95
+ rest_framework_mcp/transport/in_memory_session_store.py,sha256=Q5ukRmja7sOJgjWC-wuLQqhd5zbvyBQuatEjgYHFfNk,813
96
+ rest_framework_mcp/transport/in_memory_sse_broker.py,sha256=HjsvAsGDY8OK8i7ZwGKxoBTpppEg2E5z-YLzSCMXdhA,2510
97
+ rest_framework_mcp/transport/in_memory_sse_replay_buffer.py,sha256=ddzXKyKaOjyJcQY3aoXlT5Xq7rnF1dBzVnf7EH0Flx8,2945
98
+ rest_framework_mcp/transport/origin_validation.py,sha256=_m94RAetv29FCwae_NedVbh3tUgxTa9EuGyqF2P5wDA,946
99
+ rest_framework_mcp/transport/protocol_version.py,sha256=KVMUl6ICt71LFVFqqPza1lN8phUqD5r4HyqRsnVokhw,876
100
+ rest_framework_mcp/transport/redis_sse_broker.py,sha256=AIl2ECImrdRqzsWV-FsBRrDH1MFSGDr3x9TLG-DvfRw,6966
101
+ rest_framework_mcp/transport/redis_sse_replay_buffer.py,sha256=vbMtYPG-EeqI4f-24ohyadKo9S2q8tBga4EcA-iagjQ,4593
102
+ rest_framework_mcp/transport/session_store.py,sha256=Iq1BqabhkeSvtXqPyjxDaI6B_7LBAP50r1g62qvQ1TE,811
103
+ rest_framework_mcp/transport/sse_broker.py,sha256=NA7DzYy22Wl3xlxUADmIb7sZ8A0wN00JeT22N4wWFMY,1481
104
+ rest_framework_mcp/transport/sse_replay_buffer.py,sha256=bbH2AHKCHZbnS3w1oMJf_augdlKkgrMjS_8-syUrPNw,2921
105
+ rest_framework_mcp/transport/sse_response.py,sha256=-mHL-FAauz5MRCayB-B2yqnSBtIYfxtovMW91or8MTU,4420
106
+ rest_framework_mcp/transport/streamable_http_view.py,sha256=lNS33UcoU5zHqQFG1oBf_hnnUOWK0_ouBeolVJaRc0M,9408
107
+ djangorestframework_mcp_server-0.1.0.dist-info/METADATA,sha256=KZBVuSGEkjOynVH9xqwBWyQrZY7ui6H6ebzQVxn7H24,6092
108
+ djangorestframework_mcp_server-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
109
+ djangorestframework_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=0rCDnUPa87Fm67SxYMrdqEdLV6UiMENH6NCPLZ_KT5M,1068
110
+ djangorestframework_mcp_server-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Artur Veres
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ # Re-exports from the sister package — provided here so MCP consumers don't
2
+ # need to know which sub-package each symbol lives in. The dependency on
3
+ # ``djangorestframework-services`` is mandatory anyway, so this is purely an
4
+ # ergonomic convenience.
5
+ from rest_framework_services.selectors.list_selector import ListSelector
6
+ from rest_framework_services.selectors.output_selector import OutputSelector
7
+ from rest_framework_services.selectors.retrieve_selector import RetrieveSelector
8
+ from rest_framework_services.selectors.strict_list_selector import StrictListSelector
9
+ from rest_framework_services.selectors.strict_output_selector import StrictOutputSelector
10
+ from rest_framework_services.selectors.strict_retrieve_selector import StrictRetrieveSelector
11
+ from rest_framework_services.services.create_service import CreateService
12
+ from rest_framework_services.services.delete_service import DeleteService
13
+ from rest_framework_services.services.strict_create_service import StrictCreateService
14
+ from rest_framework_services.services.strict_delete_service import StrictDeleteService
15
+ from rest_framework_services.services.strict_update_service import StrictUpdateService
16
+ from rest_framework_services.services.update_service import UpdateService
17
+ from rest_framework_services.types.selector_spec import SelectorSpec
18
+ from rest_framework_services.types.service_spec import ServiceSpec
19
+ from rest_framework_services.types.service_view import ServiceView
20
+
21
+ from rest_framework_mcp.auth.auth_backend import MCPAuthBackend
22
+ from rest_framework_mcp.auth.permissions.django_perm_required import DjangoPermRequired
23
+ from rest_framework_mcp.auth.permissions.mcp_permission import MCPPermission
24
+ from rest_framework_mcp.auth.permissions.scope_required import ScopeRequired
25
+ from rest_framework_mcp.auth.token_info import TokenInfo
26
+ from rest_framework_mcp.output.format import OutputFormat
27
+ from rest_framework_mcp.protocol.prompt_argument import PromptArgument
28
+ from rest_framework_mcp.protocol.prompt_message import PromptMessage
29
+ from rest_framework_mcp.registry.prompt_registry import PromptRegistry
30
+ from rest_framework_mcp.registry.resource_registry import ResourceRegistry
31
+ from rest_framework_mcp.registry.tool_registry import ToolRegistry
32
+ from rest_framework_mcp.server.mcp_server import MCPServer
33
+ from rest_framework_mcp.server.mcp_service_view import MCPServiceView
34
+ from rest_framework_mcp.transport.django_cache_session_store import DjangoCacheSessionStore
35
+ from rest_framework_mcp.transport.in_memory_session_store import InMemorySessionStore
36
+ from rest_framework_mcp.transport.in_memory_sse_broker import InMemorySSEBroker
37
+ from rest_framework_mcp.transport.in_memory_sse_replay_buffer import InMemorySSEReplayBuffer
38
+ from rest_framework_mcp.transport.session_store import SessionStore
39
+ from rest_framework_mcp.transport.sse_broker import SSEBroker
40
+ from rest_framework_mcp.transport.sse_replay_buffer import SSEReplayBuffer
41
+ from rest_framework_mcp.version import __version__
42
+
43
+ __all__ = [
44
+ "CreateService",
45
+ "DeleteService",
46
+ "DjangoCacheSessionStore",
47
+ "DjangoPermRequired",
48
+ "InMemorySSEBroker",
49
+ "InMemorySSEReplayBuffer",
50
+ "InMemorySessionStore",
51
+ "ListSelector",
52
+ "MCPAuthBackend",
53
+ "MCPPermission",
54
+ "MCPServer",
55
+ "MCPServiceView",
56
+ "OutputFormat",
57
+ "OutputSelector",
58
+ "PromptArgument",
59
+ "PromptMessage",
60
+ "PromptRegistry",
61
+ "ResourceRegistry",
62
+ "RetrieveSelector",
63
+ "SSEBroker",
64
+ "SSEReplayBuffer",
65
+ "ScopeRequired",
66
+ "SelectorSpec",
67
+ "ServiceSpec",
68
+ "ServiceView",
69
+ "SessionStore",
70
+ "StrictCreateService",
71
+ "StrictDeleteService",
72
+ "StrictListSelector",
73
+ "StrictOutputSelector",
74
+ "StrictRetrieveSelector",
75
+ "StrictUpdateService",
76
+ "TokenInfo",
77
+ "ToolRegistry",
78
+ "UpdateService",
79
+ "__version__",
80
+ ]
@@ -0,0 +1,3 @@
1
+ from rest_framework_mcp._compat.acall import acall
2
+
3
+ __all__ = ["acall"]
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+ from asgiref.sync import sync_to_async
8
+
9
+
10
+ async def acall(fn: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
11
+ """Invoke ``fn`` from async code regardless of whether it's async or sync.
12
+
13
+ Async callables are awaited directly; sync callables are dispatched to a
14
+ thread via :func:`asgiref.sync.sync_to_async` so they don't block the
15
+ event loop. This is the bridge the async transport uses for collaborators
16
+ (auth backend, session store, custom permissions) that are not required
17
+ to be async-native.
18
+
19
+ Consumers writing genuinely async backends just declare their methods
20
+ ``async def`` — :func:`inspect.iscoroutinefunction` picks that up at
21
+ runtime, and the thread hop is skipped.
22
+ """
23
+ if inspect.iscoroutinefunction(fn):
24
+ return await fn(*args, **kwargs)
25
+ return await sync_to_async(fn)(*args, **kwargs)
26
+
27
+
28
+ __all__ = ["acall"]
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ from collections.abc import Iterator
5
+ from contextlib import contextmanager
6
+ from typing import Any
7
+
8
+
9
+ # OpenTelemetry is an optional extra; the package must remain usable without
10
+ # it. Resolve the trace module via ``importlib`` so the binding is plain
11
+ # ``Any`` (or ``None``) — no narrowing for the type checker to fight, and
12
+ # the no-extras smoke job still exercises the ``None`` fallback.
13
+ def _resolve_otel_trace() -> Any:
14
+ try:
15
+ return importlib.import_module("opentelemetry.trace")
16
+ except ImportError: # pragma: no cover - exercised by the no-extras smoke job
17
+ return None
18
+
19
+
20
+ _otel_trace: Any = _resolve_otel_trace()
21
+
22
+ # Tracer name follows the package's import path so OTel collectors can group
23
+ # spans coherently. ``__version__`` is included so users can correlate trace
24
+ # data with a specific release.
25
+ _TRACER_NAME: str = "rest_framework_mcp"
26
+
27
+
28
+ class _NoopSpan:
29
+ """Fallback span used when ``opentelemetry`` is not installed.
30
+
31
+ Mirrors the subset of the OTel ``Span`` API the handlers use:
32
+ ``set_attribute``, ``set_status``, ``record_exception``. Every method is
33
+ a no-op so caller code stays branch-free.
34
+ """
35
+
36
+ def set_attribute(self, key: str, value: Any) -> None:
37
+ return None
38
+
39
+ def set_status(self, status: Any) -> None:
40
+ return None
41
+
42
+ def record_exception(self, exc: BaseException) -> None:
43
+ return None
44
+
45
+
46
+ @contextmanager
47
+ def span(name: str, attributes: dict[str, Any] | None = None) -> Iterator[Any]:
48
+ """Start an MCP span; no-op if OpenTelemetry isn't installed.
49
+
50
+ ``name`` follows the convention ``mcp.<method>`` (``mcp.tools.call``,
51
+ ``mcp.resources.read``, ``mcp.prompts.get``). Attributes are set on the
52
+ span at start time; handlers can grab the yielded span object to record
53
+ additional fields (binding name, error code, etc.) before exit.
54
+ """
55
+ if _otel_trace is None:
56
+ yield _NoopSpan()
57
+ return
58
+ tracer = _otel_trace.get_tracer(_TRACER_NAME)
59
+ with tracer.start_as_current_span(name, attributes=attributes or {}) as otel_span:
60
+ yield otel_span
61
+
62
+
63
+ __all__ = ["span"]
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ from rest_framework_services._compat.arun_service import arun_service
7
+ from rest_framework_services._compat.is_async import is_async
8
+ from rest_framework_services._compat.run_service import run_service
9
+ from rest_framework_services.selectors.utils import arun_selector, run_selector
10
+
11
+ from rest_framework_mcp._compat.acall import acall
12
+
13
+ # These thin wrappers exist because the sister repo's `arun_service` /
14
+ # `arun_selector` route sync callables in ways that don't compose cleanly with
15
+ # the Django async ORM:
16
+ # - `arun_service` with atomic=True wraps the callable in `async_to_sync`,
17
+ # which only works for async callables (sync ones raise "can't be awaited").
18
+ # - `arun_selector` runs sync callables inline, so any ORM call inside them
19
+ # raises `SynchronousOnlyOperation` under an event loop.
20
+ # Both helpers below detect sync vs async at the boundary and route through
21
+ # `sync_to_async` (via :func:`acall`) for the sync branch — preserving native
22
+ # async-dispatch performance for genuinely async services and selectors.
23
+
24
+
25
+ async def arun_service_sync_safe(
26
+ fn: Callable[..., Any], kwargs: dict[str, Any], *, atomic: bool
27
+ ) -> Any:
28
+ if is_async(fn):
29
+ return await arun_service(fn, kwargs, atomic=atomic)
30
+ return await acall(run_service, fn, kwargs, atomic=atomic)
31
+
32
+
33
+ async def arun_selector_sync_safe(fn: Callable[..., Any], kwargs: dict[str, Any]) -> Any:
34
+ if is_async(fn):
35
+ return await arun_selector(fn, kwargs)
36
+ return await acall(run_selector, fn, kwargs)
37
+
38
+
39
+ __all__ = ["arun_selector_sync_safe", "arun_service_sync_safe"]
@@ -0,0 +1,4 @@
1
+ from rest_framework_mcp.adapters.selector_to_resource import selector_to_resource
2
+ from rest_framework_mcp.adapters.service_to_tool import service_spec_to_tool
3
+
4
+ __all__ = ["selector_to_resource", "service_spec_to_tool"]
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ from rest_framework_services.types.selector_spec import SelectorSpec
7
+
8
+ from rest_framework_mcp.registry.resource_binding import ResourceBinding
9
+
10
+
11
+ def selector_to_resource(
12
+ *,
13
+ name: str,
14
+ uri_template: str,
15
+ selector: SelectorSpec,
16
+ description: str | None = None,
17
+ title: str | None = None,
18
+ output_serializer: type | None = None,
19
+ mime_type: str = "application/json",
20
+ permissions: tuple[Any, ...] = (),
21
+ rate_limits: tuple[Any, ...] = (),
22
+ annotations: dict[str, Any] | None = None,
23
+ ) -> ResourceBinding:
24
+ """Lift a :class:`SelectorSpec` into a :class:`ResourceBinding`.
25
+
26
+ Mirrors :func:`service_spec_to_tool` — the unit of registration is always
27
+ a spec from ``djangorestframework-services``. ``.selector`` must be set
28
+ (a spec with ``selector=None`` is rejected because there's nothing to
29
+ dispatch to), ``.output_serializer`` fills in when the caller didn't pass
30
+ one explicitly, and ``.kwargs`` becomes the binding's per-request kwargs
31
+ provider.
32
+
33
+ The selector is dispatched at ``resources/read`` time via
34
+ ``run_selector`` / ``arun_selector`` so async selectors work
35
+ transparently.
36
+ """
37
+ if not isinstance(selector, SelectorSpec):
38
+ raise TypeError(
39
+ f"register_resource(selector=...) requires a SelectorSpec; got "
40
+ f"{type(selector).__name__}. Wrap your callable in "
41
+ f"`SelectorSpec(selector=fn)` (or use the @server.resource "
42
+ f"decorator, which wraps the function automatically)."
43
+ )
44
+ if selector.selector is None:
45
+ raise ValueError(
46
+ f"SelectorSpec for resource {name!r} has selector=None — MCP needs a "
47
+ "concrete callable to dispatch to."
48
+ )
49
+ # Spec values fill in caller-omitted kwargs. We don't override
50
+ # explicit caller args because those represent intentional choices.
51
+ resolved_callable: Callable[..., Any] = selector.selector
52
+ if output_serializer is None:
53
+ output_serializer = selector.output_serializer
54
+ kwargs_provider = selector.kwargs
55
+
56
+ return ResourceBinding(
57
+ name=name,
58
+ uri_template=uri_template,
59
+ description=description,
60
+ title=title,
61
+ selector=resolved_callable,
62
+ output_serializer=output_serializer,
63
+ mime_type=mime_type,
64
+ permissions=permissions,
65
+ rate_limits=rate_limits,
66
+ annotations=annotations or {},
67
+ kwargs_provider=kwargs_provider,
68
+ )
69
+
70
+
71
+ __all__ = ["selector_to_resource"]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from rest_framework_services.types.service_spec import ServiceSpec
6
+
7
+ from rest_framework_mcp.output.format import OutputFormat
8
+ from rest_framework_mcp.registry.tool_binding import ToolBinding
9
+
10
+
11
+ def service_spec_to_tool(
12
+ *,
13
+ name: str,
14
+ spec: ServiceSpec,
15
+ description: str | None = None,
16
+ title: str | None = None,
17
+ output_format: OutputFormat = OutputFormat.JSON,
18
+ permissions: tuple[Any, ...] = (),
19
+ rate_limits: tuple[Any, ...] = (),
20
+ annotations: dict[str, Any] | None = None,
21
+ ) -> ToolBinding:
22
+ """Lift a ``ServiceSpec`` into a :class:`ToolBinding`.
23
+
24
+ Pure projection — no side effects on the spec or its callable. The
25
+ handler layer (``handlers/handle_tools_call.py``) is what eventually
26
+ invokes ``spec.service``.
27
+ """
28
+ return ToolBinding(
29
+ name=name,
30
+ description=description,
31
+ title=title,
32
+ spec=spec,
33
+ output_format=output_format,
34
+ permissions=permissions,
35
+ rate_limits=rate_limits,
36
+ annotations=annotations or {},
37
+ )
38
+
39
+
40
+ __all__ = ["service_spec_to_tool"]
@@ -0,0 +1,19 @@
1
+ from rest_framework_mcp.auth.auth_backend import MCPAuthBackend
2
+ from rest_framework_mcp.auth.insufficient_scope_response import build_insufficient_scope_response
3
+ from rest_framework_mcp.auth.permissions.django_perm_required import DjangoPermRequired
4
+ from rest_framework_mcp.auth.permissions.mcp_permission import MCPPermission
5
+ from rest_framework_mcp.auth.permissions.scope_required import ScopeRequired
6
+ from rest_framework_mcp.auth.protected_resource_metadata import ProtectedResourceMetadataView
7
+ from rest_framework_mcp.auth.token_info import TokenInfo
8
+ from rest_framework_mcp.auth.unauthenticated_response import build_unauthenticated_response
9
+
10
+ __all__ = [
11
+ "DjangoPermRequired",
12
+ "MCPAuthBackend",
13
+ "MCPPermission",
14
+ "ProtectedResourceMetadataView",
15
+ "ScopeRequired",
16
+ "TokenInfo",
17
+ "build_insufficient_scope_response",
18
+ "build_unauthenticated_response",
19
+ ]