Skip to main content

mcp-fingerprint

Deterministic fingerprinting for MCP tool contracts.

If you expose tools through an MCP server, those tools have a contract: names, schemas, descriptions, and related metadata. mcp-fingerprint turns that contract into a stable SHA-256 fingerprint, can save a receipt-bound baseline, and can later tell you whether the contract changed — and if so, what changed as a structural diff.

Same input → same fingerprint. Differences are inspectable field-level changes, not opaque scores or AI judgments.

Why use it

Use MCP Fingerprint when you want to:

  • detect tool-contract drift between environments or releases
  • pin a known-good MCP tool surface as a local baseline
  • review exact structural changes (added/removed tools, schema edits, etc.)
  • keep the check deterministic and automatable in CI or scripts

Install

pip install mcp-fingerprint

Requires Python >=3.12,<3.13.

This pulls salt-grain 0.1.0 for the small deterministic primitives (canonicalize, digest, structured diff, receipt bind/verify). You do not need to install those separately.

Expected snapshot shape

Pass an MCP tool-contract snapshot shaped like:

snapshot = {
    "schema_version": "mcp.fingerprint.snapshot.v0.1",
    "server": {"name": "demo", "version": "1.0.0"},
    "tools": [
        {
            "name": "search",
            "description": "Search documents",  # optional
            "inputSchema": {
                "type": "object",
                "properties": {"q": {"type": "string"}},
            },
            # optional when present on the tool object:
            # "title": "...",
            # "outputSchema": {...},
            # "annotations": {...},
        }
    ],
}

Identity includes schema_version, server.name, server.version, each tool's name and inputSchema, plus optional description / title / outputSchema / annotations only when those keys are present. Missing optional keys are not synthesized. Tool order in the list does not matter (tools are sorted by name). Unsupported keys such as icons and _meta are rejected.

Quickstart: fingerprint → save → check

from pathlib import Path
from mcp_fingerprint import fingerprint, save, check

snapshot = {
    "schema_version": "mcp.fingerprint.snapshot.v0.1",
    "server": {"name": "demo", "version": "1.0.0"},
    "tools": [
        {
            "name": "search",
            "description": "Search documents",
            "inputSchema": {
                "type": "object",
                "properties": {"q": {"type": "string"}},
            },
        }
    ],
}

# 1) Fingerprint
fp = fingerprint(
    {
        "schema_version": "mcp.fingerprint.request.v0.1",
        "snapshot": snapshot,
    }
)
assert fp["ok"] is True
print(fp["fingerprint"])  # 64 lowercase hex chars; stable for this snapshot

# 2) Save a receipt-bound baseline (fails if the path already exists)
baseline_path = Path("baseline.json")
if baseline_path.exists():
    baseline_path.unlink()
saved = save(
    {
        "schema_version": "mcp.fingerprint.save.request.v0.1",
        "snapshot": snapshot,
        "baseline_path": str(baseline_path),
    }
)
assert saved["ok"] is True

# 3) Check unchanged
unchanged = check(
    {
        "schema_version": "mcp.fingerprint.check.request.v0.1",
        "snapshot": snapshot,
        "baseline_path": str(baseline_path),
    }
)
assert unchanged["ok"] is True
assert unchanged["status"] == "UNCHANGED"
assert unchanged["changes"] == []
print(unchanged["status"])

# 4) Change a tool contract and get CHANGED + structural diff
changed_snapshot = {
    "schema_version": "mcp.fingerprint.snapshot.v0.1",
    "server": {"name": "demo", "version": "1.0.0"},
    "tools": [
        {
            "name": "search",
            "description": "Search documents (updated)",
            "inputSchema": {
                "type": "object",
                "properties": {"q": {"type": "string"}},
            },
        }
    ],
}
changed = check(
    {
        "schema_version": "mcp.fingerprint.check.request.v0.1",
        "snapshot": changed_snapshot,
        "baseline_path": str(baseline_path),
    }
)
assert changed["ok"] is True
assert changed["status"] == "CHANGED"
assert changed["changes"]  # inspectable structured diff entries
print(changed["status"], len(changed["changes"]))
print(changed["changes"][0])

Expected behavior for that example:

  • fingerprint prints the same 64-character hex string every run for the same snapshot
  • first check prints UNCHANGED
  • second check prints CHANGED with a non-empty changes list describing the description edit

How it works

normalized MCP tool contract
    ->
canonical bytes
    ->
deterministic SHA-256 fingerprint
    ->
optional receipt-bound baseline
    ->
structural diff
  1. Normalize — sort tools by name; keep only supported identity fields; reject unsupported envelope keys.
  2. Canonicalize — encode the normalized snapshot as exact deterministic bytes.
  3. SHA-256 — digest those bytes to a lowercase hex fingerprint.
  4. Baseline (optional)save writes fingerprint + snapshot + receipt to a local JSON file; check verifies the receipt before comparing.
  5. Structural diff — when fingerprints differ, check returns field-level changes (or a typed limit failure if the diff budget is exceeded).

CHANGED vs UNCHANGED

Status Meaning
UNCHANGED Current fingerprint matches the verified baseline; changes is [].
CHANGED Fingerprint differs; changes is a structural diff of identity fields.

check fails with a typed error (not CHANGED / UNCHANGED) when the baseline is missing, invalid, fails receipt verification, or when limits are exceeded.

Determinism guarantees

  • Identical normalized snapshots → identical fingerprints.
  • Tool list order does not affect identity.
  • Missing optional tool keys are not filled in; empty string ≠ absent key.
  • Caller-supplied mappings are not mutated.
  • No AI / model judgment is involved.

Failure behavior / limits

Operations return structured envelopes with ok: true|false. Common failure codes:

Code Meaning
DUPLICATE_TOOL_NAME Two tools share a name
UNSUPPORTED_SNAPSHOT Rejected fields (e.g. icons, _meta)
BASELINE_ALREADY_EXISTS save path already exists
BASELINE_INVALID Baseline file is not a valid baseline
BASELINE_VERIFICATION_FAILED Receipt verification failed (e.g. tamper)
LIMIT_EXCEEDED Canonical payload or diff change budget exceeded

Oversized or non-conforming inputs fail closed with typed codes.

What this is not

MCP Fingerprint does not:

  • use AI judgment or semantic “similarity”
  • detect malware
  • authenticate publishers
  • sign code or establish trust anchors
  • intercept or filter runtime MCP traffic

It fingerprints and diffs the tool-contract snapshot you supply.

Python support

  • Python >=3.12,<3.13

License

Apache License 2.0. See LICENSE.

Release files for mcp-fingerprint 0.1.1

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

Source distribution (sdist)

Source distribution for mcp-fingerprint 0.1.1
File Size Uploaded
mcp_fingerprint-0.1.1.tar.gz 22.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-fingerprint 0.1.1
File Interpreter ABI Platform
mcp_fingerprint-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 40.0 kB

Release files / mcp_fingerprint-0.1.1.tar.gz

Download URL mcp_fingerprint-0.1.1.tar.gz
Size 22.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f08a85c123edac4402c2e6c7cca20319b5cccd916baaf481d1a9475e17e11bf3
BLAKE2b-256 checksum
How to use checksums
f937423452d5304e51538e6503131fa0c7ad15ec5994657cf4dbc8dfe13799e4
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 10, 2026.

Transparency log

Release files / mcp_fingerprint-0.1.1-py3-none-any.whl

Download URL mcp_fingerprint-0.1.1-py3-none-any.whl
Size 18.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
395afb44027081430f89976226f4a77efc803da9b5d0aa2c7b06b8562004c7b8
BLAKE2b-256 checksum
How to use checksums
2b37b7fccab1ada07d0b3b99cb7f5c9f1e37fa2bfc402abe3f882f1b67edf883
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 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.1

2 release files

0.2.0

2 release files

This release

0.1.1 This release

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