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.
- djangorestframework_mcp_server-0.1.0.dist-info/METADATA +151 -0
- djangorestframework_mcp_server-0.1.0.dist-info/RECORD +110 -0
- djangorestframework_mcp_server-0.1.0.dist-info/WHEEL +4 -0
- djangorestframework_mcp_server-0.1.0.dist-info/licenses/LICENSE +21 -0
- rest_framework_mcp/__init__.py +80 -0
- rest_framework_mcp/_compat/__init__.py +3 -0
- rest_framework_mcp/_compat/acall.py +28 -0
- rest_framework_mcp/_compat/tracing.py +63 -0
- rest_framework_mcp/_compat/utils.py +39 -0
- rest_framework_mcp/adapters/__init__.py +4 -0
- rest_framework_mcp/adapters/selector_to_resource.py +71 -0
- rest_framework_mcp/adapters/service_to_tool.py +40 -0
- rest_framework_mcp/auth/__init__.py +19 -0
- rest_framework_mcp/auth/audience.py +28 -0
- rest_framework_mcp/auth/auth_backend.py +33 -0
- rest_framework_mcp/auth/backends/__init__.py +4 -0
- rest_framework_mcp/auth/backends/allow_any_backend.py +41 -0
- rest_framework_mcp/auth/backends/django_oauth_toolkit_backend.py +101 -0
- rest_framework_mcp/auth/insufficient_scope_response.py +20 -0
- rest_framework_mcp/auth/permissions/__init__.py +5 -0
- rest_framework_mcp/auth/permissions/django_perm_required.py +29 -0
- rest_framework_mcp/auth/permissions/mcp_permission.py +35 -0
- rest_framework_mcp/auth/permissions/scope_required.py +25 -0
- rest_framework_mcp/auth/protected_resource_metadata.py +35 -0
- rest_framework_mcp/auth/rate_limits/__init__.py +11 -0
- rest_framework_mcp/auth/rate_limits/fixed_window_rate_limit.py +83 -0
- rest_framework_mcp/auth/rate_limits/mcp_rate_limit.py +33 -0
- rest_framework_mcp/auth/rate_limits/sliding_window_rate_limit.py +91 -0
- rest_framework_mcp/auth/rate_limits/token_bucket_rate_limit.py +98 -0
- rest_framework_mcp/auth/token_info.py +36 -0
- rest_framework_mcp/auth/unauthenticated_response.py +20 -0
- rest_framework_mcp/conf.py +58 -0
- rest_framework_mcp/handlers/__init__.py +23 -0
- rest_framework_mcp/handlers/async_dispatch.py +35 -0
- rest_framework_mcp/handlers/context.py +31 -0
- rest_framework_mcp/handlers/dispatch.py +61 -0
- rest_framework_mcp/handlers/handle_initialize.py +56 -0
- rest_framework_mcp/handlers/handle_ping.py +18 -0
- rest_framework_mcp/handlers/handle_prompts_get.py +106 -0
- rest_framework_mcp/handlers/handle_prompts_get_async.py +103 -0
- rest_framework_mcp/handlers/handle_prompts_list.py +45 -0
- rest_framework_mcp/handlers/handle_resources_list.py +45 -0
- rest_framework_mcp/handlers/handle_resources_read.py +106 -0
- rest_framework_mcp/handlers/handle_resources_read_async.py +106 -0
- rest_framework_mcp/handlers/handle_resources_templates_list.py +42 -0
- rest_framework_mcp/handlers/handle_tools_call.py +203 -0
- rest_framework_mcp/handlers/handle_tools_call_async.py +151 -0
- rest_framework_mcp/handlers/handle_tools_list.py +52 -0
- rest_framework_mcp/handlers/pagination.py +63 -0
- rest_framework_mcp/handlers/render_prompt_messages.py +42 -0
- rest_framework_mcp/handlers/utils.py +113 -0
- rest_framework_mcp/output/__init__.py +6 -0
- rest_framework_mcp/output/encode_json.py +17 -0
- rest_framework_mcp/output/encode_toon.py +30 -0
- rest_framework_mcp/output/format.py +34 -0
- rest_framework_mcp/output/tool_result.py +58 -0
- rest_framework_mcp/protocol/__init__.py +47 -0
- rest_framework_mcp/protocol/client_capabilities.py +33 -0
- rest_framework_mcp/protocol/get_prompt_result.py +27 -0
- rest_framework_mcp/protocol/implementation.py +18 -0
- rest_framework_mcp/protocol/initialize_params.py +37 -0
- rest_framework_mcp/protocol/initialize_result.py +30 -0
- rest_framework_mcp/protocol/json_rpc_error.py +26 -0
- rest_framework_mcp/protocol/json_rpc_error_code.py +29 -0
- rest_framework_mcp/protocol/json_rpc_message.py +9 -0
- rest_framework_mcp/protocol/json_rpc_notification.py +24 -0
- rest_framework_mcp/protocol/json_rpc_request.py +25 -0
- rest_framework_mcp/protocol/json_rpc_response.py +28 -0
- rest_framework_mcp/protocol/jsonrpc_constants.py +7 -0
- rest_framework_mcp/protocol/parse_message.py +50 -0
- rest_framework_mcp/protocol/prompt.py +37 -0
- rest_framework_mcp/protocol/prompt_argument.py +29 -0
- rest_framework_mcp/protocol/prompt_message.py +29 -0
- rest_framework_mcp/protocol/resource.py +38 -0
- rest_framework_mcp/protocol/resource_contents.py +31 -0
- rest_framework_mcp/protocol/resource_template.py +35 -0
- rest_framework_mcp/protocol/server_capabilities.py +38 -0
- rest_framework_mcp/protocol/tool.py +36 -0
- rest_framework_mcp/protocol/tool_content_block.py +34 -0
- rest_framework_mcp/protocol/tool_result.py +35 -0
- rest_framework_mcp/registry/__init__.py +6 -0
- rest_framework_mcp/registry/prompt_binding.py +37 -0
- rest_framework_mcp/registry/prompt_registry.py +34 -0
- rest_framework_mcp/registry/resource_binding.py +55 -0
- rest_framework_mcp/registry/resource_registry.py +66 -0
- rest_framework_mcp/registry/tool_binding.py +47 -0
- rest_framework_mcp/registry/tool_registry.py +35 -0
- rest_framework_mcp/schema/__init__.py +4 -0
- rest_framework_mcp/schema/input_schema.py +32 -0
- rest_framework_mcp/schema/output_schema.py +28 -0
- rest_framework_mcp/schema/utils.py +122 -0
- rest_framework_mcp/server/__init__.py +3 -0
- rest_framework_mcp/server/mcp_server.py +451 -0
- rest_framework_mcp/server/mcp_service_view.py +33 -0
- rest_framework_mcp/transport/__init__.py +21 -0
- rest_framework_mcp/transport/async_streamable_http_view.py +276 -0
- rest_framework_mcp/transport/django_cache_session_store.py +35 -0
- rest_framework_mcp/transport/in_memory_session_store.py +29 -0
- rest_framework_mcp/transport/in_memory_sse_broker.py +63 -0
- rest_framework_mcp/transport/in_memory_sse_replay_buffer.py +68 -0
- rest_framework_mcp/transport/origin_validation.py +27 -0
- rest_framework_mcp/transport/protocol_version.py +24 -0
- rest_framework_mcp/transport/redis_sse_broker.py +158 -0
- rest_framework_mcp/transport/redis_sse_replay_buffer.py +117 -0
- rest_framework_mcp/transport/session_store.py +27 -0
- rest_framework_mcp/transport/sse_broker.py +40 -0
- rest_framework_mcp/transport/sse_replay_buffer.py +68 -0
- rest_framework_mcp/transport/sse_response.py +117 -0
- rest_framework_mcp/transport/streamable_http_view.py +226 -0
- 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
|
+
[](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,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,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,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
|
+
]
|