mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-01-28 22:59:03 -06:00
Compare commits
1 Commits
feature-da
...
feature/mc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
902ad8303b |
14
docs/api.md
14
docs/api.md
@@ -60,6 +60,20 @@ The REST api provides five different forms of authentication.
|
||||
[here](advanced_usage.md#openid-connect-and-social-authentication) for more
|
||||
information on social accounts.
|
||||
|
||||
## Model Context Protocol (MCP)
|
||||
|
||||
Paperless-ngx exposes an MCP endpoint powered by `django-mcp-server` so MCP
|
||||
clients can query data collections, run full-text document search, and invoke
|
||||
DRF-backed CRUD tools.
|
||||
|
||||
- Endpoint: `/mcp/`
|
||||
- Authentication: identical to the REST API (Basic, Session, Token, or Remote
|
||||
User depending on your configuration).
|
||||
|
||||
The MCP server uses existing DRF viewsets and permissions. It also exposes a
|
||||
`query_data_collections` tool for structured querying across published models
|
||||
and a `search_documents` tool for full-text search.
|
||||
|
||||
## Searching for documents
|
||||
|
||||
Full text searching is available on the `/api/documents/` endpoint. Two
|
||||
|
||||
@@ -36,6 +36,7 @@ dependencies = [
|
||||
"django-extensions~=4.1",
|
||||
"django-filter~=25.1",
|
||||
"django-guardian~=3.2.0",
|
||||
"django-mcp-server~=0.5.7",
|
||||
"django-multiselectfield~=1.0.1",
|
||||
"django-soft-delete~=1.0.18",
|
||||
"django-treenode>=0.23.2",
|
||||
|
||||
481
src/documents/mcp.py
Normal file
481
src/documents/mcp.py
Normal file
@@ -0,0 +1,481 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db.models import Q
|
||||
from django.http import QueryDict
|
||||
from mcp_server import MCPToolset
|
||||
from mcp_server import ModelQueryToolset
|
||||
from mcp_server import drf_publish_create_mcp_tool
|
||||
from mcp_server import drf_publish_destroy_mcp_tool
|
||||
from mcp_server import drf_publish_list_mcp_tool
|
||||
from mcp_server import drf_publish_update_mcp_tool
|
||||
from rest_framework.response import Response
|
||||
|
||||
from documents.models import Correspondent
|
||||
from documents.models import CustomField
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Note
|
||||
from documents.models import SavedView
|
||||
from documents.models import ShareLink
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import Workflow
|
||||
from documents.models import WorkflowAction
|
||||
from documents.models import WorkflowTrigger
|
||||
from documents.permissions import get_objects_for_user_owner_aware
|
||||
from documents.views import CorrespondentViewSet
|
||||
from documents.views import CustomFieldViewSet
|
||||
from documents.views import DocumentTypeViewSet
|
||||
from documents.views import SavedViewViewSet
|
||||
from documents.views import ShareLinkViewSet
|
||||
from documents.views import StoragePathViewSet
|
||||
from documents.views import TagViewSet
|
||||
from documents.views import TasksViewSet
|
||||
from documents.views import UnifiedSearchViewSet
|
||||
from documents.views import WorkflowActionViewSet
|
||||
from documents.views import WorkflowTriggerViewSet
|
||||
from documents.views import WorkflowViewSet
|
||||
|
||||
VIEWSET_ACTIONS = {
|
||||
"create": {"post": "create"},
|
||||
"list": {"get": "list"},
|
||||
"update": {"put": "update"},
|
||||
"destroy": {"delete": "destroy"},
|
||||
}
|
||||
|
||||
BODY_SCHEMA = {"type": "object", "additionalProperties": True}
|
||||
|
||||
VIEWSET_INSTRUCTIONS = {
|
||||
CorrespondentViewSet: "Manage correspondents.",
|
||||
TagViewSet: "Manage tags.",
|
||||
UnifiedSearchViewSet: "Search and manage documents.",
|
||||
DocumentTypeViewSet: "Manage document types.",
|
||||
StoragePathViewSet: "Manage storage paths.",
|
||||
SavedViewViewSet: "Manage saved views.",
|
||||
ShareLinkViewSet: "Manage share links.",
|
||||
WorkflowTriggerViewSet: "Manage workflow triggers.",
|
||||
WorkflowActionViewSet: "Manage workflow actions.",
|
||||
WorkflowViewSet: "Manage workflows.",
|
||||
CustomFieldViewSet: "Manage custom fields.",
|
||||
TasksViewSet: "List background tasks.",
|
||||
}
|
||||
|
||||
|
||||
class OwnerAwareQueryToolsetMixin:
|
||||
permission: str
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return self.model.objects.none()
|
||||
if user.is_superuser:
|
||||
return self.model._default_manager.all()
|
||||
return get_objects_for_user_owner_aware(user, self.permission, self.model)
|
||||
|
||||
|
||||
class DocumentQueryToolset(ModelQueryToolset):
|
||||
model = Document
|
||||
search_fields = ["title", "content"]
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return Document.objects.none()
|
||||
if user.is_superuser:
|
||||
return Document.objects.all()
|
||||
return get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"documents.view_document",
|
||||
Document,
|
||||
)
|
||||
|
||||
|
||||
class CorrespondentQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = Correspondent
|
||||
permission = "documents.view_correspondent"
|
||||
|
||||
|
||||
class TagQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = Tag
|
||||
permission = "documents.view_tag"
|
||||
|
||||
|
||||
class DocumentTypeQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = DocumentType
|
||||
permission = "documents.view_documenttype"
|
||||
|
||||
|
||||
class StoragePathQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = StoragePath
|
||||
permission = "documents.view_storagepath"
|
||||
|
||||
|
||||
class SavedViewQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = SavedView
|
||||
permission = "documents.view_savedview"
|
||||
|
||||
|
||||
class ShareLinkQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = ShareLink
|
||||
permission = "documents.view_sharelink"
|
||||
|
||||
|
||||
class WorkflowTriggerQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = WorkflowTrigger
|
||||
permission = "documents.view_workflowtrigger"
|
||||
|
||||
|
||||
class WorkflowActionQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = WorkflowAction
|
||||
permission = "documents.view_workflowaction"
|
||||
|
||||
|
||||
class WorkflowQueryToolset(OwnerAwareQueryToolsetMixin, ModelQueryToolset):
|
||||
model = Workflow
|
||||
permission = "documents.view_workflow"
|
||||
|
||||
|
||||
class NoteQueryToolset(ModelQueryToolset):
|
||||
model = Note
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return Note.objects.none()
|
||||
if user.is_superuser:
|
||||
return Note.objects.all()
|
||||
return Note.objects.filter(
|
||||
document__in=get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"documents.view_document",
|
||||
Document,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldQueryToolset(ModelQueryToolset):
|
||||
model = CustomField
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
base = CustomField.objects.all()
|
||||
if not user or not user.is_authenticated:
|
||||
return base.none()
|
||||
if user.is_superuser:
|
||||
return base
|
||||
return base.filter(
|
||||
Q(
|
||||
fields__document__id__in=get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"documents.view_document",
|
||||
Document,
|
||||
),
|
||||
)
|
||||
| Q(fields__document__isnull=True),
|
||||
).distinct()
|
||||
|
||||
|
||||
class DocumentSearchTools(MCPToolset):
|
||||
def search_documents(
|
||||
self,
|
||||
query: str | None = None,
|
||||
more_like_id: int | None = None,
|
||||
fields: list[str] | None = None,
|
||||
page: int | None = None,
|
||||
page_size: int | None = None,
|
||||
*,
|
||||
full_perms: bool | None = None,
|
||||
) -> dict:
|
||||
"""Search documents using the full-text index."""
|
||||
if not query and not more_like_id:
|
||||
raise ValueError("Provide either query or more_like_id.")
|
||||
|
||||
request = self.request
|
||||
if request is None:
|
||||
raise ValueError("Request context is required.")
|
||||
|
||||
viewset = UnifiedSearchViewSet()
|
||||
viewset.request = request
|
||||
viewset.args = ()
|
||||
viewset.kwargs = {}
|
||||
viewset.action = "list"
|
||||
viewset.format_kwarg = None
|
||||
viewset.check_permissions(request)
|
||||
|
||||
query_params = QueryDict(mutable=True)
|
||||
if query:
|
||||
query_params["query"] = query
|
||||
if more_like_id:
|
||||
query_params["more_like_id"] = str(more_like_id)
|
||||
if full_perms is not None:
|
||||
query_params["full_perms"] = str(full_perms).lower()
|
||||
if page:
|
||||
query_params["page"] = str(page)
|
||||
if page_size:
|
||||
query_params["page_size"] = str(page_size)
|
||||
if fields:
|
||||
query_params.setlist("fields", fields)
|
||||
|
||||
request._request.GET = query_params
|
||||
response = viewset.list(request)
|
||||
if isinstance(response, Response):
|
||||
return response.data
|
||||
if hasattr(response, "data"):
|
||||
return response.data
|
||||
return {
|
||||
"detail": getattr(response, "content", b"").decode() or "Search failed.",
|
||||
}
|
||||
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
CorrespondentViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CorrespondentViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
CorrespondentViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CorrespondentViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
CorrespondentViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CorrespondentViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
CorrespondentViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CorrespondentViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
TagViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[TagViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
TagViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[TagViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
TagViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[TagViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
TagViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[TagViewSet],
|
||||
)
|
||||
|
||||
drf_publish_list_mcp_tool(
|
||||
UnifiedSearchViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UnifiedSearchViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
UnifiedSearchViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UnifiedSearchViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
UnifiedSearchViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UnifiedSearchViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
DocumentTypeViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[DocumentTypeViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
DocumentTypeViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[DocumentTypeViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
DocumentTypeViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[DocumentTypeViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
DocumentTypeViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[DocumentTypeViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
StoragePathViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[StoragePathViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
StoragePathViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[StoragePathViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
StoragePathViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[StoragePathViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
StoragePathViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[StoragePathViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
SavedViewViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[SavedViewViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
SavedViewViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[SavedViewViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
SavedViewViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[SavedViewViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
SavedViewViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[SavedViewViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
ShareLinkViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ShareLinkViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
ShareLinkViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ShareLinkViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
ShareLinkViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ShareLinkViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
ShareLinkViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ShareLinkViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
WorkflowTriggerViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowTriggerViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
WorkflowTriggerViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowTriggerViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
WorkflowTriggerViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowTriggerViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
WorkflowTriggerViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowTriggerViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
WorkflowActionViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowActionViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
WorkflowActionViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowActionViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
WorkflowActionViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowActionViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
WorkflowActionViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowActionViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
WorkflowViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
WorkflowViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
WorkflowViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
WorkflowViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[WorkflowViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
CustomFieldViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CustomFieldViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
CustomFieldViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CustomFieldViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
CustomFieldViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CustomFieldViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
CustomFieldViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[CustomFieldViewSet],
|
||||
)
|
||||
|
||||
drf_publish_list_mcp_tool(
|
||||
TasksViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[TasksViewSet],
|
||||
)
|
||||
82
src/paperless/mcp.py
Normal file
82
src/paperless/mcp.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from mcp_server import drf_publish_create_mcp_tool
|
||||
from mcp_server import drf_publish_destroy_mcp_tool
|
||||
from mcp_server import drf_publish_list_mcp_tool
|
||||
from mcp_server import drf_publish_update_mcp_tool
|
||||
|
||||
from paperless.views import ApplicationConfigurationViewSet
|
||||
from paperless.views import GroupViewSet
|
||||
from paperless.views import UserViewSet
|
||||
|
||||
VIEWSET_ACTIONS = {
|
||||
"create": {"post": "create"},
|
||||
"list": {"get": "list"},
|
||||
"update": {"put": "update"},
|
||||
"destroy": {"delete": "destroy"},
|
||||
}
|
||||
|
||||
BODY_SCHEMA = {"type": "object", "additionalProperties": True}
|
||||
|
||||
VIEWSET_INSTRUCTIONS = {
|
||||
UserViewSet: "Manage Paperless users.",
|
||||
GroupViewSet: "Manage Paperless groups.",
|
||||
ApplicationConfigurationViewSet: "Manage application configuration.",
|
||||
}
|
||||
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
UserViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UserViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
UserViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UserViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
UserViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UserViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
UserViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[UserViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
GroupViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[GroupViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
GroupViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[GroupViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
GroupViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[GroupViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
GroupViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[GroupViewSet],
|
||||
)
|
||||
|
||||
drf_publish_list_mcp_tool(
|
||||
ApplicationConfigurationViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ApplicationConfigurationViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
ApplicationConfigurationViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ApplicationConfigurationViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
@@ -348,6 +348,7 @@ INSTALLED_APPS = [
|
||||
"allauth.headless",
|
||||
"drf_spectacular",
|
||||
"drf_spectacular_sidecar",
|
||||
"mcp_server",
|
||||
"treenode",
|
||||
*env_apps,
|
||||
]
|
||||
@@ -612,6 +613,17 @@ def _parse_remote_user_settings() -> str:
|
||||
|
||||
HTTP_REMOTE_USER_HEADER_NAME = _parse_remote_user_settings()
|
||||
|
||||
DJANGO_MCP_AUTHENTICATION_CLASSES = REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]
|
||||
DJANGO_MCP_GLOBAL_SERVER_CONFIG = {
|
||||
"name": "paperless-ngx",
|
||||
"instructions": (
|
||||
"Use the MCP tools to search, query, and manage Paperless-ngx data. "
|
||||
"Use `search_documents` for full-text search, and `query_data_collections` "
|
||||
"for structured queries against available collections. "
|
||||
"Write operations are exposed via DRF-backed tools for create/update/delete."
|
||||
),
|
||||
}
|
||||
|
||||
# X-Frame options for embedded PDF display:
|
||||
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
|
||||
@@ -356,6 +356,7 @@ urlpatterns = [
|
||||
],
|
||||
),
|
||||
),
|
||||
path("", include("mcp_server.urls")),
|
||||
# Root of the Frontend
|
||||
re_path(
|
||||
r".*",
|
||||
|
||||
129
src/paperless_mail/mcp.py
Normal file
129
src/paperless_mail/mcp.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from mcp_server import ModelQueryToolset
|
||||
from mcp_server import drf_publish_create_mcp_tool
|
||||
from mcp_server import drf_publish_destroy_mcp_tool
|
||||
from mcp_server import drf_publish_list_mcp_tool
|
||||
from mcp_server import drf_publish_update_mcp_tool
|
||||
|
||||
from documents.permissions import get_objects_for_user_owner_aware
|
||||
from paperless_mail.models import MailAccount
|
||||
from paperless_mail.models import MailRule
|
||||
from paperless_mail.models import ProcessedMail
|
||||
from paperless_mail.views import MailAccountViewSet
|
||||
from paperless_mail.views import MailRuleViewSet
|
||||
from paperless_mail.views import ProcessedMailViewSet
|
||||
|
||||
VIEWSET_ACTIONS = {
|
||||
"create": {"post": "create"},
|
||||
"list": {"get": "list"},
|
||||
"update": {"put": "update"},
|
||||
"destroy": {"delete": "destroy"},
|
||||
}
|
||||
|
||||
BODY_SCHEMA = {"type": "object", "additionalProperties": True}
|
||||
|
||||
VIEWSET_INSTRUCTIONS = {
|
||||
MailAccountViewSet: "Manage mail accounts.",
|
||||
MailRuleViewSet: "Manage mail rules.",
|
||||
ProcessedMailViewSet: "List processed mail.",
|
||||
}
|
||||
|
||||
|
||||
class MailAccountQueryToolset(ModelQueryToolset):
|
||||
model = MailAccount
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return MailAccount.objects.none()
|
||||
if user.is_superuser:
|
||||
return MailAccount.objects.all()
|
||||
return get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"paperless_mail.view_mailaccount",
|
||||
MailAccount,
|
||||
)
|
||||
|
||||
|
||||
class MailRuleQueryToolset(ModelQueryToolset):
|
||||
model = MailRule
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return MailRule.objects.none()
|
||||
if user.is_superuser:
|
||||
return MailRule.objects.all()
|
||||
return get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"paperless_mail.view_mailrule",
|
||||
MailRule,
|
||||
)
|
||||
|
||||
|
||||
class ProcessedMailQueryToolset(ModelQueryToolset):
|
||||
model = ProcessedMail
|
||||
|
||||
def get_queryset(self):
|
||||
user = getattr(self.request, "user", None)
|
||||
if not user or not user.is_authenticated:
|
||||
return ProcessedMail.objects.none()
|
||||
if user.is_superuser:
|
||||
return ProcessedMail.objects.all()
|
||||
return get_objects_for_user_owner_aware(
|
||||
user,
|
||||
"paperless_mail.view_processedmail",
|
||||
ProcessedMail,
|
||||
)
|
||||
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
MailAccountViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailAccountViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
MailAccountViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailAccountViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
MailAccountViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailAccountViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
MailAccountViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailAccountViewSet],
|
||||
)
|
||||
|
||||
drf_publish_create_mcp_tool(
|
||||
MailRuleViewSet,
|
||||
actions=VIEWSET_ACTIONS["create"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailRuleViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_list_mcp_tool(
|
||||
MailRuleViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailRuleViewSet],
|
||||
)
|
||||
drf_publish_update_mcp_tool(
|
||||
MailRuleViewSet,
|
||||
actions=VIEWSET_ACTIONS["update"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailRuleViewSet],
|
||||
body_schema=BODY_SCHEMA,
|
||||
)
|
||||
drf_publish_destroy_mcp_tool(
|
||||
MailRuleViewSet,
|
||||
actions=VIEWSET_ACTIONS["destroy"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[MailRuleViewSet],
|
||||
)
|
||||
|
||||
drf_publish_list_mcp_tool(
|
||||
ProcessedMailViewSet,
|
||||
actions=VIEWSET_ACTIONS["list"],
|
||||
instructions=VIEWSET_INSTRUCTIONS[ProcessedMailViewSet],
|
||||
)
|
||||
156
uv.lock
generated
156
uv.lock
generated
@@ -1038,6 +1038,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/23/63a7d868373a73d25c4a5c2dd3cce3aaeb22fbee82560d42b6e93ba01403/django_guardian-3.2.0-py3-none-any.whl", hash = "sha256:0768565a057988a93fc4a1d93649c4a794abfd7473a8408a079cfbf83c559d77", size = 134674, upload-time = "2025-09-16T10:35:51.69Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-mcp-server"
|
||||
version = "0.5.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "djangorestframework", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "inflection", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "mcp", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "uritemplate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/70/e2cf268b77d0aa171b72763325279284561dbbd9b80ed4fd6975b4b7bd9c/django_mcp_server-0.5.7.tar.gz", hash = "sha256:5077f8fabf5fb621b5ce490afd0db60f21e57b3a451ed14a9f44aef545ea4eee", size = 23910, upload-time = "2025-10-10T17:13:34.681Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/01/f78a11f51437f70b4ff2d9f131d47acf82c2a4cf78d63e9cf291e3727054/django_mcp_server-0.5.7-py3-none-any.whl", hash = "sha256:04b58bf02623aaee59708c3661ffe17981acd4532587c38b6cfe2c9e7090c6d3", size = 26389, upload-time = "2025-10-10T17:13:33.56Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-multiselectfield"
|
||||
version = "1.0.1"
|
||||
@@ -1706,6 +1722,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056, upload-time = "2024-12-20T07:23:00.394Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huggingface-hub"
|
||||
version = "0.30.2"
|
||||
@@ -2378,6 +2403,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964, upload-time = "2025-12-22T06:53:51.801Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.26.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "httpx-sse", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "jsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "pyjwt", extra = ["crypto"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "python-multipart", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "sse-starlette", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "starlette", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "uvicorn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
@@ -2937,6 +2986,7 @@ dependencies = [
|
||||
{ name = "django-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-filter", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-guardian", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-mcp-server", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-multiselectfield", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-soft-delete", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "django-treenode", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
@@ -3085,6 +3135,7 @@ requires-dist = [
|
||||
{ name = "django-extensions", specifier = "~=4.1" },
|
||||
{ name = "django-filter", specifier = "~=25.1" },
|
||||
{ name = "django-guardian", specifier = "~=3.2.0" },
|
||||
{ name = "django-mcp-server", specifier = "~=0.5.7" },
|
||||
{ name = "django-multiselectfield", specifier = "~=1.0.1" },
|
||||
{ name = "django-soft-delete", specifier = "~=1.0.18" },
|
||||
{ name = "django-treenode", specifier = ">=0.23.2" },
|
||||
@@ -3790,6 +3841,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
@@ -4007,6 +4072,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
@@ -4948,6 +5022,32 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "3.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "starlette", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/00d280c03ffd39aaee0e86ec81e2d3b9253036a0f93f51d10503adef0e65/sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422", size = 27253, upload-time = "2026-01-17T13:11:05.62Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/7f/832f015020844a8b8f7a9cbc103dd76ba8e3875004c41e08440ea3a2b41a/sse_starlette-3.2.0-py3-none-any.whl", hash = "sha256:5876954bd51920fc2cd51baee47a080eb88a37b5b784e615abb0b283f801cdbf", size = 12763, upload-time = "2026-01-17T13:11:03.775Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.52.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.13.3"
|
||||
@@ -5108,13 +5208,13 @@ dependencies = [
|
||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp310-none-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:bf1e68cfb935ae2046374ff02a7aa73dda70351b46342846f557055b3a540bf0" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:a52952a8c90a422c14627ea99b9826b7557203b46b4d0772d3ca5c7699692425" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:287242dd1f830846098b5eca847f817aa5c6015ea57ab4c1287809efea7b77eb" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8924d10d36eac8fe0652a060a03fc2ae52980841850b9a1a2ddb0f27a4f181cd" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:bcee64ae7aa65876ceeae6dcaebe75109485b213528c74939602208a20706e3f" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:defadbeb055cfcf5def58f70937145aecbd7a4bc295238ded1d0e85ae2cf0e1d" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:886f84b181f766f53265ba0a1d503011e60f53fff9d569563ef94f24160e1072" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5138,20 +5238,20 @@ dependencies = [
|
||||
{ name = "typing-extensions", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:10866c8a48c4aa5ae3f48538dc8a055b99c57d9c6af2bf5dd715374d9d6ddca3" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7210713b66943fdbfcc237b2e782871b649123ac5d29f548ce8c85be4223ab38" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0e611cfb16724e62252b67d31073bc5c490cb83e92ecdc1192762535e0e44487" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3de2adb9b4443dc9210ef1f1b16da3647ace53553166d6360bbbd7edd6f16e4d" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3bf9b442a51a2948e41216a76d7ab00f0694cfcaaa51b6f9bcab57b7f89843e6" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7417d8c565f219d3455654cb431c6d892a3eb40246055e14d645422de13b9ea1" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3e532e553b37ee859205a9b2d1c7977fd6922f53bbb1b9bfdd5bdc00d1a60ed4" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:39b3dff6d8fba240ae0d1bede4ca11c2531ae3b47329206512d99e17907ff74b" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:01b1884f724977a20c7da2f640f1c7b37f4a2c117a7f4a6c1c0424d14cb86322" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:031a597147fa81b1e6d79ccf1ad3ccc7fafa27941d6cf26ff5caaa384fb20e92" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:65010ab4aacce6c9a1ddfc935f986c003ca8638ded04348fd326c3e74346237c" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:88adf5157db5da1d54b1c9fe4a6c1d20ceef00e75d854e206a87dbf69e3037dc" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3ac2b8df2c55430e836dcda31940d47f1f5f94b8731057b6f20300ebea394dd9" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5b688445f928f13563b7418b17c57e97bf955ab559cf73cd8f2b961f8572dbb3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5495,6 +5595,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/34/257747253ad446fd155e39f0c30afda4597b3b9e28f44a9de5dee76a6509/uv-0.9.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b31377ebf2d0499afc5abe3fe1abded5ca843f3a1161b432fe26eb0ce15bab8e", size = 21597889, upload-time = "2025-10-29T19:40:36.963Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.40.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "h11", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.21.0"
|
||||
|
||||
Reference in New Issue
Block a user