Skip to content

feat(mcp): add get_checksum tool for document integrity verification - #78

Merged
jaymeklein merged 3 commits into
mainfrom
feat/mcp-get-checksum
Jul 30, 2026
Merged

feat(mcp): add get_checksum tool for document integrity verification#78
jaymeklein merged 3 commits into
mainfrom
feat/mcp-get-checksum

Conversation

@jaymeklein

Copy link
Copy Markdown
Owner

What

Adds a new MCP tool get_checksum that 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_checksum answers both: compare the returned digest against a local sha256sum <file>, and read ingested_at for 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 a read grant on the document.

Returns {document_id, filename, original, algorithm, checksum, file_size, ingested_at}.

  • 404 if the document is missing or its bytes are unavailable (upload never completed / purged).
  • original=true checksums an attached original source file instead of the parse (404 if none attached) — parity with download_document.

Design notes

  • Layering: logic in documents.py::compute_checksum; the MCP tools.py fn is a thin keyword-only delegate; the @server.tool() wrapper carries the schema + LLM docstring and groups under Documents.
  • Reuse: authorization + storage/key resolution reuse the existing _resolve_storage_and_key; hashing is over the Storage protocol, so it is backend-agnostic (local disk + S3/MinIO).
  • Non-blocking: the fetch+hash is streamed a block at a time and offloaded via asyncio.to_thread; the DB session is never crossed into the thread.
  • Auto-surfaced: no skill/catalogue edits needed — GET /mcp-tools (build_tool_catalog) introspects registered tools, so the Settings -> MCP page and the generated embedbase.zip skill bundle pick it up automatically (guarded by the existing catalogue drift test).

Decisions

  • ingested_at comes from documents.created_at (the documents entry timestamp). For the presigned MCP flow this is the reservation instant, typically seconds before confirm.
  • Kept the optional original=true flag for parity with download_document.

Tests / gate

6 integration tests: hash correctness, recompute-on-every-call, original, empty (0-byte) object, missing bytes (404), out-of-scope (403).

  • ruff, mypy, pytest all green (970 passed, 13 pre-existing skips).
  • standards-check clean; security review found no issues; code-review (high) surfaced 4 minor findings (1 fixed via the 0-byte test, 1 accepted, 2 skipped with justification).

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.
@jaymeklein
jaymeklein merged commit db18cfa into main Jul 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant