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 two flags¶
Flag |
Gates (consumer enforces) |
|---|---|
|
|
|
|
Both 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_embedder: true
embedder:
module: ./embedder.py
class: SentenceTransformerEmbedder
the framework:
Parses the manifest, validating
allow_embedderis a bool.Parses the
embedder:block (module/class/kwargs) intomanifest.embedder.Boots, exposing the manifest via
Manifest::to_json()and the Rust struct.Does NOT instantiate the embedder. The framework has no Python runtime.
The downstream consumer (kglite-mcp-server) then:
Reads
manifest.trust.allow_embedder.If false AND
embedder:is set, refuses to boot with an error like:embedder requires trust.allow_embedder: true.If true, instantiates the embedder via its pyo3 wrapper and wires it into the
text_score()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 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.embedder and instantiates the hook regardless of trust.allow_embedder, 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 dynamic-code 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.embedder is not None
and not manifest.trust.allow_embedder
):
raise ManifestError(
"embedder requires "
"trust.allow_embedder: true"
)
One check per 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_embedder followed — proposed by kglite when they needed to load a Python embedder for semantic search, added upstream as a non-breaking patch, and enforced in their boot-time check.
See also¶
Writing a Manifest — the full
trust:block syntaxTrust Pattern — the design rationale
Downstream Binary — where enforcement lives in your code