mcpindex-gate
The in-path drift gate for agent tool calls — the open-core client of mcpindex.ai, the drift-monitored MCP server index. It runs on your host with zero egress and checks every MCP tool definition against your own pinned baseline before a call goes out.
Renamed: previously published as
mcpindex-preflight. That name remains a deprecated alias that depends on this package. New installs should usemcpindex-gate.
What it does
mcpindex-gate produces a drift decision — PROCEED / HOLD /
INCONCLUSIVE — by diffing the live tool contract against the contract you pinned.
If a server silently changes a tool's schema, description, or surface, the gate can
HOLD the call and surface why.
from mcpindex_gate import wrap, PreflightPin, PreflightHold
session = wrap(your_mcp_client_session, pin=PreflightPin(path="~/.mcpindex/pin.json"))
try:
result = await session.call_tool("transfer_funds", {...})
except PreflightHold as hold:
# The tool contract drifted from your pin — inspect `hold` and decide.
print(hold)
wrap() accepts any duck-typed client session; this package does not depend on
the mcp SDK.
What it is — and is not
- It is a contract diff, not a safety oracle. It detects that a tool changed; it does not judge whether the change is malicious.
- A
HOLDmeans "this drifted from your pin — look before you act." It is advisory. It does not block attacks, guarantee safety, or make a server tamper-proof. - It mints no clearance verdict. The offline client can detect drift and HOLD; it can never publish a "SAFE" verdict.
What status tells you (0.11.0)
mcpindex-config-wire status reports enforcement, not merely that our proxy is in
the launch line:
[x]requires that nothing is un-wired, unreadable, or non-enforcing. Before 0.11.0 the mark appeared as soon as one server was wired and could never turn off.[!]means real coverage gaps, and names them by host.notify-onlycounts servers launched with--posture monitor, which never blocks. It is a legitimate choice, but it is not protection, so it is never counted as such.hosts scanned: Nis stated because project-scoped configs (a repo's own.mcp.json,.cursor/mcp.json,.vscode/mcp.json, …) are not scanned yet. The total is a floor, not a census.
If the gate HOLDs on your baseline
A HOLD whose reason names the baseline rather than a tool contract means the pin store
for that server was present but unreadable, or was deleted after having been pinned. The
gate refuses to proceed rather than silently re-learning the contract from whatever the
server is serving right now. This holds in every posture and regardless of fail_open,
because neither is consent to trust a baseline that may have been rewritten.
To recover deliberately, delete that server's file under ~/.mcpindex/pins/ and restart
the host. That re-pins from the current contract — a fresh trust-on-first-use — so do it
only when you know why the store was unreadable (an interrupted write, a restored backup,
a machine migration).
Honest limit: this is tamper-evident, never tamper-proof. A process running as you that replaces the store with a well-formed one carrying its own baseline is still trusted. No same-user control prevents that: any secret the gate can read, a process running as you can read too.
Install
uv tool install mcpindex-gate
One-click host wiring + a resident auto-onboard watcher are available via the installer at https://mcpindex.ai/install.sh.
Call receipts (on by default)
After each gated call the gate emits a compact, credential-blind receipt: a hash of
the tool identity, the gate verdict, and the action class (read / write / execute). It
never includes tool arguments, result content, server names, or URLs. Receipts are
linked by a random per-install token stored in ~/.mcpindex/install_id -- pseudonymous,
not derived from you or your machine. Keyless installs have no account link; if you sign
in on mcpindex.ai and configure an api_key, receipts from that install are associated
with your key.
From gate v0.9.0, the first run on a machine prints a one-line disclosure of exactly this behavior to stderr and sends nothing that session -- emission starts on run 2, so no receipt ever leaves before the notice and the opt-out were visible. Earlier versions emit without the runtime notice, per this README.
After 10 gated calls the ambient notifier fires a one-line message on stderr:
mcpindex - 10 tool calls noted - mcpindex.ai/receipts?id=<your-install-id>
That link shows your per-install call log. To suppress receipt egress entirely:
export MCPINDEX_RECEIPT_INGEST_ENABLED=0
Weekly summary line (local, zero egress)
At most once per ISO week the gate prints one line summarizing what it did last week -- calls gated, tools and servers watched, drift seen, holds issued:
mcpindex · week 2026-W29: 240 call(s) gated across 12 tool(s) / 3 server(s), 0 drift - your baseline is holding · mcpindex.ai/receipts?id=<your-install-id>
The counters live only in ~/.mcpindex/weekly_stats.json; nothing about this line is
sent anywhere. To silence it:
export MCPINDEX_WEEKLY_SUMMARY=0
Project config scanning (local read, ON by default)
mcpindex-config-wire counts the MCP servers you actually run. Many of them are not in
your global host config: they are declared per-project, in a repo's own .mcp.json,
.cursor/mcp.json, .vscode/mcp.json, .gemini/settings.json or .zed/settings.json.
A gate that cannot see them reports a coverage number that is a floor, not a census — on
one real machine it said 25 servers while 35 were loaded.
To find those repos it reads the project list Claude Code already keeps in
~/.claude.json. This widens what the tool reads from about ten fixed paths in your
home directory to every repository you have opened in Claude Code. It is a local,
read-only scan; nothing about it is sent anywhere, and it obeys the same zero-egress
default as the rest of the gate.
Detect-only. Project configs are counted and reported, never wired. They are
version-controlled and shared, so wiring one would commit a change pointing your
teammates at a gate they have not installed — and every server would fail for them.
Wiring one is possible, but it takes an explicit --config <path>, after you have read
the file.
To turn the scan off, or to add a repo Claude Code does not know about:
mcpindex-config-wire status --no-project-scan # or: export MCPINDEX_PROJECT_SCAN=off
mcpindex-config-wire status --project ~/work/my-repo # repeatable; for Cursor / Gemini / Zed users
With the scan off, the server count is a floor and status says so rather than printing
a smaller number without the caveat.
Drift telemetry (opt-in, OFF by default)
The gate can report that a tool's contract drifted — so mcpindex can track drift on servers it can't crawl itself (private / auth-gated). It is off by default and sends nothing unless you turn it on:
export MCPINDEX_DRIFT_TELEMETRY=detection # off (default) | lookup | detection | contribute
off— the default. Sends nothing, asks nothing.lookup— read-only. Asks whether a tool's contract has already drifted, so the gate can warn you on the first call. Sends a salted fingerprint per tool and reports none of your own catches back.detection—lookup, plus each tool you pin and each contract drift sends one one-way signal.contribute— reserved for a future richer tier; behaves identically todetectiontoday.
Each tier is a superset of the one before it. See https://mcpindex.ai/privacy and https://mcpindex.ai/docs for exactly which fields leave the machine.
Where the setting lives (v0.13.0+)
For servers you wired through the gate, set it with the wiring command rather than in your shell — the wired entry is what the proxy actually launches with:
mcpindex-config-wire --drift-telemetry lookup # or detection / contribute / off
Your choice is recorded in ~/.mcpindex/consent.json and re-applied on every later
re-wire, including on servers already wired.
0.13.2 and earlier: this did not work. Wiring skipped servers it had already wired, so on an already-configured machine
--drift-telemetry offreported success and cleared nothing. On those versions you must pass--repin. Fixed in 0.13.3.
Before v0.13.0 the setting lived only in the host config entry, where the gate could not
tell your own opt-in from one something else had written — so wiring cleared it, and a
scripted re-wire (--yes, an editor auto-wire) silently turned your setting off.
The record holds the mode, a label for how it was set, when, and — when the setting was carried over from an existing wired config — the host label it came from. No paths, no server or tool names. (In 0.13.1 and 0.13.2 that field held a file path from your machine instead.) It is local, is never sent anywhere, and is not a security boundary: anything running as your user can edit it, exactly as it can edit your host config. What it buys is that your setting survives, and that a wired entry which disagrees with it gets reported instead of silently obeyed.
To turn everything off and clear it from every wired entry:
mcpindex-config-wire wire --drift-telemetry off
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcpindex_gate-0.13.3-py3-none-any.whl.
File metadata
- Download URL: mcpindex_gate-0.13.3-py3-none-any.whl
- Upload date:
- Size: 576.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11f816ccdb5f484e6f6917adfac5707d24545d5adde1599ce448a26223f73032
|
|
| MD5 |
a6f36e684a5c96927bae66825032840b
|
|
| BLAKE2b-256 |
ad98b5d4e7ff3872b2cab00b55496ca05a30836062172ea1c78ad13a1909632c
|
Provenance
The following attestation bundles were made for mcpindex_gate-0.13.3-py3-none-any.whl:
Publisher:
release-preflight.yml on mcpindex-ai/mcpindex-trust
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcpindex_gate-0.13.3-py3-none-any.whl -
Subject digest:
11f816ccdb5f484e6f6917adfac5707d24545d5adde1599ce448a26223f73032 - Sigstore transparency entry: 2460809843
- Sigstore integration time:
-
Permalink:
mcpindex-ai/mcpindex-trust@cd66908f094573f48ad7f592162c3174cb8f072c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mcpindex-ai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-preflight.yml@cd66908f094573f48ad7f592162c3174cb8f072c -
Trigger Event:
workflow_dispatch
-
Statement type: