Trust Gates¶
The trust: block in a manifest is advisory metadata. The framework parses + surfaces it; downstream consumers (kglite-mcp-server, your own binary) enforce it. This page explains the model and how to use it correctly.
The three flags¶
Flag |
Gates (consumer enforces) |
|---|---|
|
|
|
|
|
|
All three default to false. The framework writes them to:
Rust:
manifest.trust.allow_python_tools(etc.)JSON:
manifest.to_json()["trust"]["allow_python_tools"]pyo3:
manifest.as_dict()["trust"]["allow_python_tools"]
The “framework records, consumer enforces” contract¶
When you write:
trust:
allow_query_preprocessor: true
extensions:
cypher_preprocessor:
module: ./preprocessor.py
class: WikidataPreprocessor
the framework:
Parses the manifest, validating
allow_query_preprocessoris a bool.Stores
extensions.cypher_preprocessoras opaque JSON undermanifest.extensions.Boots, exposing the manifest via
Manifest::to_json()and the Rust struct.Does NOT instantiate the preprocessor. The framework has no Python runtime.
The downstream consumer (kglite-mcp-server) then:
Reads
manifest.trust.allow_query_preprocessor.If false AND
extensions.cypher_preprocessoris set, refuses to boot with an error like:extensions.cypher_preprocessor requires trust.allow_query_preprocessor: true.If true, instantiates the preprocessor via its pyo3 wrapper and wires it into the Cypher dispatch path.
This separation is intentional — see Trust Pattern for the design rationale.
Why advisory, not enforced?¶
Three reasons:
Domain-agnostic framework. The framework doesn’t know what an embedder is, what a query preprocessor does, or what makes a
python:tool “safe.” Each consumer has different semantics for the same flag. Enforcement belongs where the semantics live.Audit-readable in one place. An operator reviewing a manifest for security implications can scan
trust:to see every dynamic-code hook the server allows, without hunting throughextensions:for hidden gates. The block becomes the canonical “what does this server load?” surface.Forward-compatibility. Adding a new trust gate is a non-breaking patch release (the framework just accepts and stores a new boolean key). Consumers adopt the new gate at their own pace, and the audit story scales.
What happens if you forget to enforce?¶
The framework can’t catch it. If a consumer reads manifest.extensions.cypher_preprocessor and instantiates the hook regardless of trust.allow_query_preprocessor, the operator’s false is silently ignored.
This is why the contract is documented in CHANGELOG entries (every new gate ships with the enforcement responsibility spelled out) and in Trust Pattern.
If you’re writing a downstream binary: write a single boot-time check that walks each extension hook + its corresponding trust flag and raises a clean error before any hook is instantiated. The kglite pattern is:
# In the consumer's manifest loader
if (
manifest.extensions.get("cypher_preprocessor") is not None
and not manifest.trust.allow_query_preprocessor
):
raise ManifestError(
"extensions.cypher_preprocessor requires "
"trust.allow_query_preprocessor: true"
)
One check per extension hook, fail-loud, before serve().
Adding a new trust gate¶
If your downstream binary needs a new extension category, propose a new trust flag upstream:
The flag goes in
ALLOWED_TRUST_KEYS(incrates/mcp-methods/src/server/manifest.rs) andTrustConfig.The parser in
build_trust()reads it as a bool.Manifest::to_json()emits it undertrust.The snapshot test (
to_json_shape_is_stable) is updated to include it.CHANGELOG entry documents the consumer-enforcement contract.
This is exactly the path allow_query_preprocessor followed in mcp-methods 0.3.29 — proposed by kglite for their 0.9.25 release, shipped same-day after a brief design review.
See also¶
Writing a Manifest — the full
trust:block syntaxTrust Pattern — the design rationale
Downstream Binary — where enforcement lives in your code