Skip to main content

legion-guard — verify before connect

An agent reads an MCP server's tool descriptions as instructions. The server you approved last week can expose other tools, or the same tools with other descriptions, today. legion-guard checks the tools/list your client actually receives, at connect time.

import legion_guard
session = legion_guard.wrap(session, "com.dexpaprika/dexpaprika")   # after session.initialize(); one line, no config file
tools = await session.list_tools()
print(session.report["verdict"])     # NO_CHANGE_DETECTED | CHANGE_DETECTED | UNVERIFIED

Try it in about a minute (including the virtualenv + install)

python3 -m venv v && . v/bin/activate && pip install legion-guard
python -m legion_guard check https://mcp.dexpaprika.com/streamable-http com.dexpaprika/dexpaprika

It connects to the public DexPaprika MCP server (initialize + tools/list, no tool call), fingerprints the 20 tools it receives and checks them against the latest list signed by the LEGION log. Look for "state": "MATCHES_SIGNED" (or DIFFERS_FROM_SIGNED, UNKNOWN, UNVERIFIED); exit code 0 = no difference, 1 = difference, 2 = connection error.

Low risk to add (tested)

  • Observes by default. The default mode is warn: changes are logged (logging.getLogger("legion_guard")) and reported in session.report; no tool is hidden and no call is refused.
  • Never breaks your app. If a source is unreachable or fails, it is reported UNAVAILABLE and nothing is blocked, in every mode. Internal errors of the guard are logged, never raised.
  • Blocking is opt-in. wrap(session, server_id, mode="block") hides from list_tools() and refuses in call_tool() only the tools for which a source has evidence of a change (changed or new tool). UNKNOWN and UNAVAILABLE never block.

Removable: LEGION is the default, not a lock-in

Sources are pluggable. Each one compares the received list with its own reference:

Source Reference Network
FirstUseSource() (default) the first list seen for this server, pinned in ~/.cache/legion-guard/pins.json none
LegionSource() (default when a registry id is given) latest list signed by the LEGION transparency log: record + RFC 9162 inclusion proof + signed head, keys from a pinned root key tlog.legion-api.com
LegionSource(base=..., root_pin=...) the same check against another log that serves this API (for example a self-hosted mirror) your log
LegionSource(witnesses=[vkey, ...], min_witnesses=N) additionally requires the signed head to be a C2SP checkpoint signed by the log and cosigned by N of your witnesses as above
PinnedSource(dict_or_lockfile) fingerprints you pinned yourself (mcp-tools-lock/0.1, same format as legion-lock) none
your own object with .name and .check(server_id, fp) anything yours
session = legion_guard.wrap(session, "io.example/server", sources=[legion_guard.PinnedSource(".mcp-tools.lock.json")])  # no LEGION at all

Measured on 2026-10-17: the 20-tool list served by mcp.dexpaprika.com/streamable-http matches the list LEGION signed (MATCHES), also when the head must be a checkpoint cosigned by a chosen witness. There is no independent witness of the LEGION log yet: the only cosigning witness today is run by LEGION itself.

Limits

  • LEGION observes only part of the servers; absence from the log is not a verdict (UNKNOWN).
  • A change is not an attack. This does not certify that a server is secure, and says nothing about what the tools do.
  • First-use pinning trusts the first list it sees.
  • With witnesses=, the result is UNAVAILABLE (never blocking) until your witnesses have cosigned the log's current checkpoint. The only witness today is run by LEGION and cosigns hourly, so after each new head there is a window of up to about one hour where this option cannot confirm anything (measured on 2026-10-18).
  • Fingerprints cover name, description and inputSchema (JCS RFC 8785), as in the LEGION log.
  • Tested with the MCP Python SDK ClientSession; frameworks that build on it are not tested yet.

The 0.1 functions (guard_session, evaluate, approve, python3 -m legion_guard check|approve) are unchanged.

Release notes

  • 0.2.5 (2026-10-21): requires mcp>=1.24 (was mcp>=1.0). 1.24.0 is the first MCP Python SDK version with mcp.client.streamable_http.streamable_http_client, which the CLI uses. With 1.0–1.23.3 the package installed but python3 -m legion_guard check failed with an ImportError (measured on 2026-10-21; 0.2.1–0.2.4 declared mcp>=1.0). Tested on mcp 1.24.0, 1.30.0, 2.0.0, 2.1.1 and 2.3.0. The library itself does not import mcp. GHSA-84m7-p3x7-pcfv concerns MCP servers (fixed in 1.30.0 and 2.2.0); if the same environment also runs servers, use one of those versions. The test servers run on both the 1.x and the 2.x SDK.
  • 0.2.4 (2026-10-21): the vendored mirror verifier accepts the checkpoint note signed under the log's origin name (C2SP) as well as the historical name, same key.

Metadata

Release files for legion-guard 0.2.5

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

Source distribution (sdist)

Source distribution for legion-guard 0.2.5
File Size Uploaded Provenance
legion_guard-0.2.5.tar.gz 49.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for legion-guard 0.2.5
File Interpreter ABI Platform
legion_guard-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 97.2 kB

Release files / legion_guard-0.2.5.tar.gz

Download URL legion_guard-0.2.5.tar.gz
Size 49.9 kB
Tags Source
SHA-256 checksum
How to use checksums
769908013c1b21e115d02fe128889eca7d86793f15af2e9161e6ae7feeb04aa8
BLAKE2b-256 checksum
How to use checksums
aef4e7d523ce29d8ab9ce602ea4a91cec103263e8cb5ec8e38db92f9d4c61355
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Release files / legion_guard-0.2.5-py3-none-any.whl

Download URL legion_guard-0.2.5-py3-none-any.whl
Size 47.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
593686409cf57027ed0c43707fa7e3417c5183c0042bf287f8d44b8096ae5bb8
BLAKE2b-256 checksum
How to use checksums
98837a3b2820fb5022cf4ce91491889af0f2090d966e3ae73613d31e26a7bc46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.12

Security for legion-guard 0.2.5

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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