Skip to main content

paperless-mcp

A Model Context Protocol server for Paperless-ngx, powered by pypaperless 6.0. It speaks stdio — so Claude Desktop (or any other MCP client) can launch it directly — and Streamable HTTP, so one instance can serve a whole network.

[!WARNING]

Highly experimental — use at your own risk.

This project is young and moving fast. Tool names, parameters and return shapes still change; such changes are labelled breaking-change in the release notes, but they do happen, and there is no deprecation window yet. It also needs a Paperless-ngx 3.0 that is itself fresh.

Nothing here has been proven against your instance, and the caller is an LLM deciding on its own which tools to invoke. If the documents matter to you: have backups, start with PAPERLESS_MCP_READONLY=true, and leave the delete tools switched off (that is the default) until you trust the setup.

By an LLM, for LLMs

The thing on the other end of this protocol is a language model — and so is the thing that wrote most of the code behind it. @tb1337 builds this project deliberately together with Claude: he sets the direction, writes the specs and reviews every diff; Claude does the typing. That is a stated design choice, not an embarrassing detail buried in the commit log.

It also shapes the server itself. Every decision here optimises for a model as the caller, not a human clicking through a UI:

  • errors come back as structured results the model can read and recover from, never as protocol-level failures that just end the conversation,
  • list tools page server-side, so a model can walk deep into a result set without burning its context on 500 documents it did not ask for,
  • the tool surface can be narrowed (read-only, deletes off by default), because an autonomous caller should not be handed a destructive verb by accident,
  • and things like thumbnails come back as real images the model can actually look at.

Features

  • Two transports: stdio (default, for locally spawned clients) and http (Streamable HTTP at /mcp, for network use).
  • Bearer-token auth on the HTTP endpoint, plus an unauthenticated /healthz for container probes.
  • Tunable surface: writes can be disabled (PAPERLESS_MCP_READONLY=true) and deletes require explicit opt-in (PAPERLESS_MCP_ENABLE_DELETE=true). 54 tools by default, 64 with deletes enabled.
  • Workflow prompts: three slash commands — triage_inbox, monthly_review, find_duplicates — that chain the tools into the jobs an archive actually needs, with the Paperless-specific judgement calls written into them. They adapt to the visibility flags, so a read-only server hands back a proposal instead of walking the model into a write that is not there.
  • Saved views run, not just read: run_saved_view executes the queries the user curated in the web UI — "Unpaid invoices", "Tax 2024" — by translating their stored filter rules into one document query server-side, in the view's own sort order. A rule the translation table does not know is refused rather than dropped, because a view answered with too many documents is worse than one left unanswered.
  • Names, not just IDs: Paperless reports correspondents, tags, document types, storage paths and owners as bare numbers. The master data is read once per connection and cached, so every result carries correspondent_name, tag_names, owner_name and friends next to the IDs — including the label behind a select custom field — without a lookup call per document.
  • Server-side pagination on every list-shaped tool: offset/limit are translated into Paperless page requests, so paging deep into a result set costs at most two HTTP calls and each response reports total and has_more.
  • Structured errors: pypaperless exceptions become results like {"error": "not_found", "detail": "...", "cause": "..."} instead of protocol-level failures, so the model can recover rather than give up.
  • Behaviour hints on every tool: each of the 64 tools ships MCP tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) plus a display title, so a client can wave a search through and stop to ask before a rotate, a merge or an empty_trash.
  • Survives an unreachable Paperless: the connection is established lazily and retried per call, so the MCP handshake never fails just because the server was briefly down.
  • Thumbnails as real images, viewable inline in the client.
  • Built on pypaperless 6.0.0 and the MCP Python SDK 2.0 — requires Paperless-ngx 3.0+.

Requirements

  • uv on the machine that runs the MCP client. It manages the virtualenv and the Python toolchain — no system Python 3.13 needed, uv fetches one if it has to.
  • Paperless-ngx 3.0+ with an API token (Settings → API tokens, or /api/token/).

Installation — uv

paperless-mcp is on PyPI, so uv can install it without a clone. Every dependency is a stable release, so no resolver flags are needed.

Install it as a tool (recommended)

uv tool install paperless-mcp

That leaves a standalone paperless-mcp executable on your PATH. Note down its absolute path — the MCP client will need it — and check it runs:

command -v paperless-mcp   # macOS/Linux, typically ~/.local/bin/paperless-mcp
where paperless-mcp        # Windows
paperless-mcp --help

Later releases: uv tool upgrade paperless-mcp, then restart the client.

Or run it straight from PyPI, without installing

uvx --from paperless-mcp paperless-mcp --help

uvx resolves into a cache on first use and reuses it afterwards. Handy for a quick look or for running the HTTP transport ad hoc; for a client that spawns the server on every launch, the installed tool above is the tidier option.

Connect Claude Desktop

Add the paperless entry to your claude_desktop_config.json and restart the app completely (quit it, don't just close the window — the config is read once at startup).

With the tool installed, command is the only path involved:

{
  "mcpServers": {
    "paperless": {
      "command": "/absolute/path/to/paperless-mcp",
      "env": {
        "PAPERLESS_URL": "https://paperless.example.com",
        "PAPERLESS_TOKEN": "your-paperless-api-token",
        "PAPERLESS_MCP_READONLY": "false",
        "PAPERLESS_MCP_ENABLE_DELETE": "false"
      }
    }
  }
}

Or let the client drive uvx, with the invocation moved into args:

{
  "mcpServers": {
    "paperless": {
      "command": "/absolute/path/to/uvx",
      "args": ["--from", "paperless-mcp", "paperless-mcp"],
      "env": {
        "PAPERLESS_URL": "https://paperless.example.com",
        "PAPERLESS_TOKEN": "your-paperless-api-token",
        "PAPERLESS_MCP_READONLY": "false",
        "PAPERLESS_MCP_ENABLE_DELETE": "false"
      }
    }
  }
}

command must be absolute in both cases. Claude Desktop does not inherit your shell PATH, so a bare paperless-mcp or uvx usually fails to resolve — paste what command -v / where reported above.

The last two env entries are the safety switches, spelled out here so they are easy to find: PAPERLESS_MCP_READONLY=true hides every write and delete tool, PAPERLESS_MCP_ENABLE_DELETE=true adds the delete tools on top of the default read+write set. See Configuration for the rest.

Config file locations:

OS Path
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
Linux ~/.config/Claude/claude_desktop_config.json

Ready-made files live in examples/: claude_desktop_config.json for the installed tool, and claude_desktop_config.local-checkout.json for the clone below.

Any other MCP client that spawns stdio servers takes the same command/args/env triple.

From a git clone instead

For hacking on the server, or to run something newer than the last release:

git clone https://github.com/tb1337/paperless-mcp.git
cd paperless-mcp
uv sync
uv run paperless-mcp --help

The clone needs no resolver flags — uv.lock already pins the pre-release. Give the clone a permanent home and point the client at it; both paths absolute:

{
  "mcpServers": {
    "paperless": {
      "command": "/absolute/path/to/uv",
      "args": ["run", "--directory", "/absolute/path/to/paperless-mcp", "paperless-mcp"],
      "env": {
        "PAPERLESS_URL": "https://paperless.example.com",
        "PAPERLESS_TOKEN": "your-paperless-api-token",
        "PAPERLESS_MCP_READONLY": "false",
        "PAPERLESS_MCP_ENABLE_DELETE": "false"
      }
    }
  }
}

uv run switches into --directory and uses that project's environment, whatever working directory the client happens to start it in. After every git pull, run uv sync again so the environment matches the committed lockfile, then restart the client.

Troubleshooting

  • module 'httpx' has no attribute 'AsyncClient' — installed with --prerelease=allow, which lets uv pick an httpx dev build. No prerelease flag is needed any more; drop it. Releases after 0.0.1 cap httpx below 1.0 themselves.
  • "Server disconnected" right after startup — almost always the command. Use an absolute path; see above.
  • "No such file or directory" / the server starts but nothing works — for the clone setup, the --directory path does not point at the clone (it needs the directory containing pyproject.toml).
  • Tools appear but every call returns connection_errorPAPERLESS_URL is wrong or unreachable from the machine running the client. The server starts anyway by design; the error text names the cause.
  • auth_failed — the API token is wrong, or belongs to a deactivated user.
  • Self-signed certificate — set "PAPERLESS_MCP_VERIFY_SSL": "false".
  • Logs go to stderr and end up in Claude Desktop's MCP log (~/Library/Logs/Claude/mcp-server-paperless.log on macOS). Set "PAPERLESS_MCP_LOG_LEVEL": "DEBUG" for more detail.

Configuration

Every setting has an environment variable and a command-line flag; the flag wins. A .env file in the working directory is loaded automatically (override with --env-file) and never overwrites variables the MCP client already set.

Variable Flag Default Purpose
PAPERLESS_URL --url — (required) Base URL of your Paperless-ngx instance
PAPERLESS_TOKEN --token — (required) Paperless-ngx API token
PAPERLESS_MCP_TRANSPORT --transport / --stdio / --http stdio stdio or http
PAPERLESS_MCP_HOST --host 127.0.0.1 Bind address (http only)
PAPERLESS_MCP_PORT --port 8000 Bind port (http only)
PAPERLESS_MCP_AUTH_TOKEN --auth-token empty Bearer token required on /mcp
PAPERLESS_MCP_READONLY --readonly false If true: hide every write/delete tool
PAPERLESS_MCP_ENABLE_DELETE --enable-delete false If true: expose delete tools
PAPERLESS_MCP_MAX_FILE_BYTES --max-file-bytes 25000000 Cap for file/thumbnail payloads
PAPERLESS_MCP_VERIFY_SSL --no-verify-ssl true TLS certificate verification
PAPERLESS_MCP_TIMEOUT --timeout 30 Per-request HTTP timeout, seconds
PAPERLESS_MCP_NAME_CACHE_TTL --name-cache-ttl 300 Lifetime of the ID→name snapshot, seconds (0: forever)
PAPERLESS_MCP_LOG_LEVEL --log-level INFO Verbosity (always logged to stderr)

Tool visibility matrix

Mode Read tools Write tools Delete tools
READONLY=true
Default
ENABLE_DELETE=true

READONLY=true always wins, even if ENABLE_DELETE=true.

Tools

Read: search_documents, search_everywhere, get_document, get_document_content, get_document_metadata, get_document_notes, get_document_history, find_similar_documents, download_document, get_document_thumbnail, get_next_asn, list_tags, list_correspondents, list_document_types, list_storage_paths, list_custom_fields, list_share_links, list_saved_views, get_saved_view, run_saved_view, list_trash, list_active_tasks, list_tasks, get_task, get_statistics, get_system_status, get_paperless_info, search_autocomplete, get_document_suggestions, get_document_ai_suggestions.

Write (default-on, suppressed by READONLY): upload_document, update_document, add_document_note, bulk_edit_documents, bulk_reprocess_documents, bulk_merge_documents, bulk_rotate_documents, split_document, delete_document_pages, acknowledge_tasks, create_tag, update_tag, create_correspondent, update_correspondent, create_document_type, update_document_type, create_storage_path, update_storage_path, create_custom_field, update_custom_field, set_document_custom_field, remove_document_custom_field, create_share_link, restore_documents.

Delete (requires ENABLE_DELETE=true): delete_document, delete_document_note, delete_tag, delete_correspondent, delete_document_type, delete_storage_path, delete_custom_field, delete_share_link, bulk_delete_objects, empty_trash.

Notes on semantics:

  • Relations take a name or an ID. Correspondent, document type, storage path and tags can be given as document_type_name: "Kündigung" instead of document_type_id: 11, and as tag_names instead of tag_ids, on search_documents, update_document, upload_document, bulk_edit_documents and bulk_delete_objects. Names are what every result already reports back, and what a human reading the call along can veto — a wrong ID hits another valid object and relabels a document without an error anywhere. Matching is exact, then case-insensitive, never fuzzy: an archive holding MR-ST 1337 next to MR-ST 1337_2 leaves no room to guess, and an ambiguous name is refused with both candidates. An unknown name is an error listing the near misses, never a newly created tag — but it costs one snapshot reload first, so master data added elsewhere a moment ago still resolves. Passing both halves is allowed and they must agree; a mismatch is rejected rather than silently resolved.

  • search_documents combines a Whoosh full-text query with Django-style filters, and takes order_by / descending.

  • search_everywhere is the other half: one global-search call that answers "what is this called in Paperless?" across documents, tags, correspondents, document types, storage paths, custom fields and saved views, so a name becomes an ID without paging three list_* tools. It cannot filter, sort or page — limit caps each category separately and truncated says whether it bit. Users, groups, mail rules and workflows are left out on purpose; they are admin-tier resources this server does not carry.

  • search_autocomplete completes a partial word against the full-text index. It answers with vocabulary that actually occurs in the scans — whether an archive spells it "Rechnung" or "Rechnungen" — not with field names or query syntax, which live in the search_documents description.

  • split_document requires page_groups to cover every page exactly once. Paperless keeps only the pages it is handed and discards the rest silently, so a gap is refused rather than acted on; use delete_document_pages to drop pages on purpose. The results inherit the source metadata unchanged, without a "(split 1)" title suffix.

  • delete_document_pages is neither idempotent nor atomic. The survivors are renumbered, so repeating the same call removes different sheets; and the pages to keep are computed from a page count read in an earlier request, so a document that gains a version in between loses the wrong ones. Both tools take an optional page_count that skips that read — get_document reports it — though passing it does not close the window.

  • get_next_asn reports the next free archive serial number, the one written on the paper original before filing. It is only free until something claims it: fetch it immediately before the upload or update that uses it. Two calls in a row return the same number, not two.

  • get_system_status needs the view_system_monitoring permission or a staff account, and answers {"error": "forbidden"} without it. health rolls the six subsystems (database, Redis, Celery, index, classifier, sanity check) up into one verdict and problems lists only those that are not OK.

  • search_documents also takes custom_field_query, the only filter that reaches custom field values. It is Paperless' JSON expression — an atom [field, operator, value] where field is a custom field's name or ID, and ["AND", [expr, ...]] / ["OR", [expr, ...]] / ["NOT", expr] around others — accepted either as JSON text or as the structure itself:

    ["AND", [["Due", "range", ["2024-08-01", "2024-08-31"]], ["Paid", "exact", false]]]
    

    Which operators an atom may use depends on the field's data_type: exact, in, isnull and exists on any type; icontains / istartswith / iendswith on string, longtext, url and monetary; gt / gte / lt / lte / range on date, integer, float and monetary; contains on documentlink. A date field also takes a component in front of the operator, so ["Due", "month__exact", 8] matches every August. The expression is checked against the cached field definitions before the request goes out, because Paperless answers a bad one with a validation payload keyed by position rather than by name.

  • update_document replaces the tag list and accepts a clear_fields list (correspondent, document_type, storage_path, archive_serial_number) to unset foreign keys. Setting and clearing the same field in one call is rejected — including when the value came in as a name. Use bulk_edit_documents to add or remove individual tags.

  • upload_document queues a file for consumption and returns a task UUID to poll with get_task. Pass poll=true to have the server do the waiting instead: the call then blocks until the consumer is done and answers with the new document_id, the terminal status and the full task record — one call instead of an upload plus a polling loop the model has to drive itself. The wait ends after poll_timeout_seconds (default 30, maximum 300; raise it for OCR-heavy scans) and running out is not an error: the result carries timed_out: true alongside the task_uuid to keep polling with. A failure status most often means Paperless rejected the file as a duplicate, and task.result_data says so. Keep the timeout under the MCP client's own request timeout — a call the client gives up on still consumes the file, it just leaves nobody to read the result.

  • set_document_custom_field upserts one field value on one document: a field the document does not carry yet is added, an existing one is replaced. The value is checked against the field's data_type first, so 1 is not quietly stored as true and 1.0 is rejected instead of rounded. Setting the value a field already holds writes nothing and reports changed: false. Two things to know before a documentlink write: the list of IDs replaces the stored one (to add a link, read the current list from get_document and send it back with the new ID appended), and Paperless maintains the reverse link itself, so linking A to B makes B show A — never set both directions.

  • bulk_delete_objects deletes tags, correspondents, document types or storage paths in one call, and takes its selection either as object_ids / object_names or as a filter — name_contains, name_startswith, name_endswith, name_exact, plus is_root for tags and path_contains for storage paths. The filter form is what API v10 added all and filters for: clearing out 400 stale correspondents travels as one lookup instead of 400 IDs that would have to be paged out of list_correspondents first. The two forms cannot be combined, because Paperless stops looking at objects as soon as all is set — a call meaning to intersect them would silently delete by filter alone. A filter no FilterSet behind the endpoint knows is refused for the same reason: django-filter drops an unrecognized lookup, and a dropped lookup widens the selection to everything. deleted reports how many objects the selection covered, counted immediately before the delete, since the endpoint itself answers a filtered call with a bare OK; for tags it can understate, as Paperless also removes the matched tags' descendants. The endpoint has no branch for custom fields — delete_custom_field stays one-at-a-time — and its other operation, set_permissions, is not exposed: this server carries no users or groups to name in one.

  • remove_document_custom_field clears the value on one document; the field definition and its values elsewhere are untouched, which is what delete_custom_field would destroy instead. A field that is not set is not an error: the call reports removed: false and changes nothing.

  • Both write the document's custom fields as one array, because that is the only thing the API accepts — a value another client stored between the read and the write is lost.

Tool annotations

Every tool carries MCP annotations, so a client can decide how much ceremony a call deserves without parsing the description:

  • readOnlyHint is true for all 30 read tools, and only for those. destructiveHint / idempotentHint are left unset there — the spec only gives them meaning once a tool can write.
  • destructiveHint is true for 24 tools — every update_*, all ten delete tools (delete_*, bulk_delete_objects and empty_trash), the six that rewrite a document or its pages (bulk_edit_documents, bulk_reprocess_documents, bulk_merge_documents, bulk_rotate_documents, split_document, delete_document_pages), and the two custom field value tools (set_document_custom_field replaces a value, remove_document_custom_field clears one). It is false for the additive ones (upload_document, create_*, add_document_note, restore_documents, acknowledge_tasks).
  • idempotentHint is true only where repeating the identical call converges on the same state. It is false wherever a call accumulates: bulk_rotate_documents (twice by 90° is 180°), bulk_merge_documents and upload_document (each call mints another document), bulk_reprocess_documents (each call queues another task), and every create_* plus add_document_note (each call adds another row).
  • openWorldHint is false everywhere: the tools reach exactly one configured Paperless instance, not an open-ended set of external entities.

The vocabulary has no axis for reversible versus final, so tools that differ a lot end up with identical hints: delete_document moves a document to the trash and restore_documents brings it back, while empty_trash cannot be undone and purges the entire trash when called without arguments, and bulk_delete_objects can retire a whole branch of the taxonomy from a single filter. A client that wants to hold the final ones back harder has to go by the name or the description.

They are hints, not a permission system — the actual gate is PAPERLESS_MCP_READONLY / PAPERLESS_MCP_ENABLE_DELETE, which decide whether a tool is advertised at all.

Prompts

Tools are verbs; a job is a sequence of them plus the judgement about what to do when they disagree. That sequence is what the prompts hold. In Claude Desktop they show up as slash commands, and they are the user's to start — the model cannot invoke one on its own.

Prompt Arguments What it does
triage_inbox limit (default 10) Works through the inbox document by document: reads it, then cross-checks get_document_suggestions (the trained classifier) against get_document_ai_suggestions (the LLM, when the instance has AI enabled) against find_similar_documents (how the archive already files this sender), and settles on correspondent, type, storage path, tags, title and date.
monthly_review month (YYYY-MM, defaults to last month) Closes out a month: what was dated in the window versus what arrived in it, then the things that slipped — still in the inbox, untagged, no correspondent, consumption failed — and the recurring senders that went silent compared with the month before.
find_duplicates query, limit (default 25) Hunts the duplicates that survive a Paperless import: not byte-identical re-uploads (those are refused at the door) but the same paper scanned twice. Runs candidates through the similarity index, confirms each pair against get_document_metadata checksums and sizes, and rules out the look-alikes — last month's statement, last year's form.

Two things they do that a hand-written prompt would not:

  • The date arithmetic is done before the model sees it. monthly_review renders with the window already resolved — 2024-02-01 to 2024-02-29, plus the preceding month to compare against — so leap days and year boundaries are settled facts rather than something to reason about.
  • They respect the visibility flags. With PAPERLESS_MCP_READONLY=true, triage_inbox asks for a filing proposal instead of describing bulk_edit_documents; with deletes off, find_duplicates tags the losing copy for a human rather than reaching for delete_document. A plan that ends in a tool the server never advertised is a plan that gets followed until it fails.

Rendering a prompt never touches Paperless: it is a plan, built from its arguments and the server's configuration, so a slash command cannot fail because the archive was briefly unreachable.

Running over HTTP / in Docker

The server also speaks Streamable HTTP at /mcp, guarded by a bearer token, and the repo ships a Dockerfile and a docker-compose.yml for exactly that. Documentation for this path is coming — start with the uv setup above; it is the supported route for now.

Out of scope (for now)

  • Workflows, mail accounts/rules, users, groups, config: admin-tier concerns where letting an LLM make autonomous changes is rarely the right answer.
  • The document chat endpoint (/api/documents/chat/): it streams plain text, and pypaperless 6.0's DocumentChat model carries only the echoed query, so no answer ever reaches the caller. A tool for it would fail every time; it will come back if the library starts returning the response body.

Development

This repo ships a VS Code devcontainer based on mcr.microsoft.com/devcontainers/python:3-3.13, with uv, ruff and pytest preinstalled. Open the project in VS Code and select "Reopen in Container" — the dev venv lands at /home/vscode/.local/dev-venv and dependencies are synced via uv on container start. Nothing else runs inside it: no Docker daemon, no Paperless. Point PAPERLESS_URL in your .env at an instance running elsewhere — from inside the container the host is 172.17.0.1.

script/bootstrap             # uv sync --group dev
uv run pytest                # suite + coverage (gate: 80 %)
uv run ruff check --fix .    # lint
uv run ruff format .         # format
uv run mypy                  # strict, on the paperless_mcp package
prek run --all-files         # everything CI lints, in one go
uv run paperless-mcp --help

The same commands are wired up as VS Code tasks (test: pytest, lint: ruff check, validate: full suite as the default build task), and three debug configurations are ready in the Run view:

  • paperless-mcp debug script runs run/debug.py, a scratch file that builds the real server, opens one session and calls tools directly against the Paperless in your .env. This is the one to reach for when you want a breakpoint inside a tool without an MCP client in the loop.
  • paperless-mcp (http) serves Streamable HTTP on port 8000 (forwarded out of the container), for driving the server from a real client or MCP Inspector.
  • paperless-mcp (stdio) starts the transport a client would spawn — useful for checking startup and configuration errors, less so for interactive work.

AGENTS.md documents the module layout and the conventions that hold this together — the tool surface as public API, why tools never raise, why list tools must paginate. Worth reading before adding a tool, whether you are human or not.

License

MIT.

Release files for paperless-mcp 0.0.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for paperless-mcp 0.0.6
File Size Uploaded
paperless_mcp-0.0.6.tar.gz 201.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for paperless-mcp 0.0.6
File Interpreter ABI Platform
paperless_mcp-0.0.6-py3-none-any.whl Python 3 none any Details

Total release size:293.6 kB

Release files / paperless_mcp-0.0.6.tar.gz

Download URL paperless_mcp-0.0.6.tar.gz
Size 201.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e442b87aab35cdbc0d81785a15de71eb300943a2548a40123d30a7c32ffa2a56
BLAKE2b-256 checksum
How to use checksums
1d3530ec1e3c7e03182cd8f591396bd05ebdc7fc9a56c19d969fb9aefd73d337
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / paperless_mcp-0.0.6-py3-none-any.whl

Download URL paperless_mcp-0.0.6-py3-none-any.whl
Size 92.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5408d182f3869141e9fbe9624648b936299bc3422583797ba38f2eb45743a9dc
BLAKE2b-256 checksum
How to use checksums
c1afebad083cfa951b281cad3366c87b8fe8d9b017e08479daf53047f2ef18a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.0

2 release files

This release

0.0.6 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page