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)

allow_python_tools

tools[].python: factories — Python code the operator declared as a tool

allow_embedder

extensions.embedder loaders — typically a Python class that produces vector embeddings

allow_query_preprocessor

extensions.cypher_preprocessor hooks — Python or other rewriters that fire before each query

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:

  1. Parses the manifest, validating allow_query_preprocessor is a bool.

  2. Stores extensions.cypher_preprocessor as opaque JSON under manifest.extensions.

  3. Boots, exposing the manifest via Manifest::to_json() and the Rust struct.

  4. Does NOT instantiate the preprocessor. The framework has no Python runtime.

The downstream consumer (kglite-mcp-server) then:

  1. Reads manifest.trust.allow_query_preprocessor.

  2. If false AND extensions.cypher_preprocessor is set, refuses to boot with an error like: extensions.cypher_preprocessor requires trust.allow_query_preprocessor: true.

  3. 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:

  1. 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.

  2. 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 through extensions: for hidden gates. The block becomes the canonical “what does this server load?” surface.

  3. 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:

  1. The flag goes in ALLOWED_TRUST_KEYS (in crates/mcp-methods/src/server/manifest.rs) and TrustConfig.

  2. The parser in build_trust() reads it as a bool.

  3. Manifest::to_json() emits it under trust.

  4. The snapshot test (to_json_shape_is_stable) is updated to include it.

  5. 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