feat(mcp): add get_checksum tool for document integrity verification - #78
Merged
Conversation
Add a get_checksum MCP tool that computes a fresh SHA-256 over a document stored bytes on every call, so a scoped user can prove their file was stored exactly as sent and detect drift. Returns the digest, algorithm, byte size, and the document ingestion date, plus an optional original=true flag to checksum an attached original source file (parity with download_document). Reuses _resolve_storage_and_key for read authorization + storage resolution; the hash is streamed a block at a time and offloaded to a worker thread so it never blocks the event loop. The digest is recomputed from storage on every call (nothing cached), so it always reflects the current bytes. Integration tests cover hash correctness, recompute-on-every-call, the original flag, empty (0-byte) objects, missing bytes (404), and out-of-scope access (403).
Weave the new get_checksum tool into the SKILL.md that the console generates for download: list integrity verification in the frontmatter capabilities and add a short "Verifying a stored file" section so an agent loading the skill knows to reach for get_checksum to prove a stored file is byte-identical to what was sent (and to read ingested_at for freshness). The tool already appears in the auto-generated references/tools.md; this adds the narrative guidance that only lives in the hand-written SKILL.md.
api/requirements.txt pinned mcp with an open lower bound (mcp>=1.0.0), so a fresh CI install resolved to the newly released mcp 2.0.0, which dropped the mcp.server.fastmcp module. That broke `from mcp.server.fastmcp import FastMCP` in api/services/mcp/server.py, failing tests/conftest.py import and thus every unit and integration test at collection (lint passes via --ignore-missing-imports and the docker-build job only builds, so only the test jobs went red). Pin to 1.28.1 — the version the MCP server is verified against (see the note atop server.py) and the version that passes locally. Pre-existing latent breakage exposed on this branch; unrelated to the get_checksum change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a new MCP tool
get_checksumthat computes a fresh SHA-256 over a documents stored bytes on every call, returning the digest alongside the documents ingestion date.Why
Users ingesting files want assurance that whats stored is byte-identical to what they sent, and a way to tell whether a stored copy is stale versus a newer local file.
get_checksumanswers both: compare the returned digest against a localsha256sum <file>, and readingested_atfor freshness. The hash is recomputed from storage on every call (nothing cached), so it always reflects the current stored bytes.API
get_checksum(document_id, original=false)— requires areadgrant on the document.Returns
{document_id, filename, original, algorithm, checksum, file_size, ingested_at}.404if the document is missing or its bytes are unavailable (upload never completed / purged).original=truechecksums an attached original source file instead of the parse (404 if none attached) — parity withdownload_document.Design notes
documents.py::compute_checksum; the MCPtools.pyfn is a thin keyword-only delegate; the@server.tool()wrapper carries the schema + LLM docstring and groups under Documents._resolve_storage_and_key; hashing is over theStorageprotocol, so it is backend-agnostic (local disk + S3/MinIO).asyncio.to_thread; the DB session is never crossed into the thread.GET /mcp-tools(build_tool_catalog) introspects registered tools, so the Settings -> MCP page and the generatedembedbase.zipskill bundle pick it up automatically (guarded by the existing catalogue drift test).Decisions
ingested_atcomes fromdocuments.created_at(the documents entry timestamp). For the presigned MCP flow this is the reservation instant, typically seconds before confirm.original=trueflag for parity withdownload_document.Tests / gate
6 integration tests: hash correctness, recompute-on-every-call,
original, empty (0-byte) object, missing bytes (404), out-of-scope (403).