Chio/Docs
LOGIN · JOIN

Signed Tool Manifests

Declare a tool server's tools, schemas, permissions, and prices in one signed manifest the kernel verifies before any tool registers.

Concept chio@5f63e6e

On this page

A tool provider can publish exactly what its server offers, and at what price, in one document the operator inspects before any tool goes live. The manifest names each tool, its input and output schemas, its side effects, and its advertised price, and the server's Ed25519 key signs the whole of it.

The kernel registers a server only after the manifest validates and its signature verifies against the key the operator registered for that server. Other operators find the same server later through a signed listing that points back to this manifest.

Manifest to listingChoose a step.
listsimportsignsregisterdtool serverσmanifestSignedManifestℓlistingKkernelK′peer operatordTools registeredschemas, pricingRefusedManifestErrorℓActivatedapproved for local useℓVisible onlyno activation authoritylistsimportsignsregisterdtool serverσmanifestℓlistingKkernelK′peer operatordTools registeredschemas, pricingRefusedManifestErrorℓActivatedapproved for local useℓVisible onlyno activation authority

The kernel registers only a verified manifest

A tool server signs a SignedManifest with Ed25519. The kernel checks its structure, its tool definitions, and their uniqueness, verifies the signature against the server's registered key, then registers the tools with their schemas and advertised pricing. Signed tool manifests.

A provider's signed manifest registering with one kernel, and its signed listing reaching a peer operator. Manifest fields, pricing models, and the registry's search dimensions are left out.Capability discovery

The pricing server declares a priced tool and signs it once

You publish quote on your pricing server at USD 0.01 a call, the price the outside buyer's grant in Quickstart part 2 pays. The native service builder declares the tool, its schemas, and its price in one place, and produces the manifest.

src/service.rs · build_servicerust
pub fn build_service(public_key_hex: String) -> Result<NativeChioService, ManifestError> {
    NativeChioServiceBuilder::new("pricing", public_key_hex)
        .server_name("Larkfield Pricing")
        .server_version("1.0.0")
        .tool(
            NativeTool::new(
                "quote",
                "Quote a SKU at a discount, rounded half up to the cent",
                serde_json::json!({
                    "type": "object",
                    "properties": {
                        "sku": { "type": "string" },
                        "percent": { "type": "number", "minimum": 0, "maximum": 100 }
                    },
                    "required": ["sku", "percent"]
                }),
            )
            .read_only()
            .per_invocation_price(1, "USD")
            .latency_hint(LatencyHint::Instant),
            // pricing.rs: parses sku and percent, returns the quote as JSON
            |arguments| pricing::quote_json(&arguments),
        )
        .build()
}

per_invocation_price(1, "USD") is 1 minor unit, one cent: MonetaryAmount counts whole minor units, and a dollar is 100 of them. The example then signs the manifest with the server keypair. On the kernel side, admission calls verify_manifest with the registered public key and registers the server; the tools keep their schemas, pricing, and side-effect flags.

Admission fragmentrust
use chio_manifest::verify_manifest;

// signed: &SignedManifest from the application's registration transport.
// registered_key: &PublicKey from operator-owned trust configuration.
verify_manifest(signed, registered_key)?;

// connection: Box<dyn ToolServerConnection> for the admitted server.
kernel.register_tool_server(connection);

The unit of signing is the whole manifest. Adding or removing a tool, changing a price, or bumping a version needs a fresh signature, and the kernel rejects a manifest whose body differs from the signed bytes.

A known key must sign every tool and every price the kernel registers

The signature closes two attacks on a tool catalog.

  • Unauthorized tool advertisement. A compromised server could add an entry that looks benign and route its invocations to a malicious backend. The kernel admits a manifest only under a known key, so the attacker would need both the binary and its signing key.
  • Price changes after the fact. Pricing lives inside the signed manifest. An operator who set a budget against a published price knows that price changes only with a fresh signature, and verifying the signature exposes any drift.

A valid signature authenticates the catalog the kernel registers against. Each call to a registered tool is still authorized by the caller's capability token at runtime.

Read more in Capabilities.

Registration runs five checks, and any failure refuses the whole manifest

For each registration, the kernel walks the same path, in order.

  1. Read. Take the SignedManifest from the registration payload.
  2. Validate. Run validate_manifest: schema, identity fields, a non-empty tool list, unique tool names, a unique server-tool allowlist.
  3. Verify. Check the Ed25519 signature against the public key registered for that server_id.
  4. Cross-check. Confirm the manifest's own public_key field matches the verifying key.
  5. Register. Record the tools, schemas, pricing, and side-effect flags.
FailureErrorProducer's fix
Wrong schema idUnsupportedSchemaEmit chio.manifest.v1
Empty tool listEmptyManifestDeclare at least one tool
Duplicate tool nameDuplicateToolName(name)Collapse duplicates before signing
Bad signatureVerificationFailedRe-sign with the registered key
Unknown top-level fieldSerde deserialization errorRemove the field; rejected before validation

A bad signature means tampering or the wrong signing key. No tool from a refused manifest registers, so a producer fixes the error and signs again.

Declared permissions tell the operator what the host must allow

A server can declare what it needs from its sandbox in required_permissions: the paths it reads and writes, the network hosts it reaches, and the environment variables it reads. The operator sees the request before registration and either grants it or refuses to admit the manifest. The host that runs the server enforces the grant.

Provider-native server tools, such as Anthropic's computer use, bash, and text editor, carry a larger trust boundary than ordinary tools. The manifest allowlists them by name in server_tools, and an absent entry is refused. Versioned wire names such as bash_20241022 map to their category, so a provider's version bump stays behind the same allowlist entry.

Read more in The assurance model.

A metered tool advertises a batch it can price in whole cents

You sell an outside buyer read access to your order history through the orders server's sql tool, billed by rows returned. The billing unit is a batch of a thousand rows, and the advertised unit_price is 100 cents per batch. The field names are the manifest's own, and the keys are shortened.

orders.manifest.jsonjson
{
  "manifest": {
    "schema": "chio.manifest.v1",
    "server_id": "orders",
    "name": "Larkfield Orders",
    "description": "Read-only SQL over the orders, skus, and customers tables",
    "version": "1.0.0",
    "tools": [
      {
        "name": "sql",
        "description": "Run one read-only query and return its rows",
        "input_schema": {
          "type": "object",
          "properties": {
            "query": { "type": "string" },
            "max_rows": { "type": "integer" }
          },
          "required": ["query"]
        },
        "output_schema": null,
        "pricing": {
          "pricing_model": "per_unit",
          "unit_price": { "units": 100, "currency": "USD" },
          "billing_unit": "1000-rows"
        },
        "has_side_effects": false,
        "latency_hint": "fast"
      }
    ],
    "required_permissions": null,
    "public_key": "4f1c…a2e9"
  },
  "signature": "9f3c…20b1",
  "signer_key": "4f1c…a2e9"
}

The batch follows from the arithmetic. A manifest prices one billing unit at a time, and a MonetaryAmount has no representation for a tenth of a cent, so a tool that charges per row advertises a batch it can price in whole cents. A MeteredBillingQuote carries a quotedCost for the whole estimate, so the quote can name the finer unit and count a thousand of them. A quote for 1,000 rows comes to USD 1.00, the advertised price of one batch.

The buyer's kernel runs verify_manifest against your registered key, then carries the sql entry and its ToolPricing forward as the catalog input for a metered quote request. The fixed-price service purchase works differently: its MCP tool returns the quote as application data.

Read more in Pricing Models, which binds quoted units and cost into a governed intent.

Manifest fields and validation

Top-level fields

The ToolManifest struct lives at crates/platform/chio-manifest/src/lib.rs:28-63 and serializes with #[serde(deny_unknown_fields)], so an unknown top-level field fails deserialization closed.

schema

StringRequired

MUST equal the constant TOOL_MANIFEST_SCHEMA (lib.rs:25). Any other value returns UnsupportedSchema.

Example"chio.manifest.v1"

server_id

chio_core::ServerIdRequired

Stable unique identifier. The registration path uses it to look up the registered key. Non-empty, equal to its trimmed form, free of control characters; a violation returns InvalidManifestField.

Example"pricing"

name

StringRequired

Human-readable server name, under the same non-empty, trimmed, control-character-free rule as server_id.

Example"Larkfield Pricing"

description

Option<String>Optional

Optional server description.

version

StringRequired

Semantic version of this tool server, under the same rule as server_id.

Example"1.0.0"

tools

Vec<ToolDefinition>Required · non-empty

The tools this server provides. validate_tools (validation.rs:45-47) rejects an empty list with EmptyManifest and duplicate names with DuplicateToolName.

server_tools

Vec<ServerTool>Default: empty

Allowlist of provider-native server tools (Anthropic computer_use, bash, text_editor). Serialized with #[serde(default, skip_serializing_if = "Vec::is_empty")]. Absent entries default to deny; duplicates return DuplicateServerTool.

Example["bash"]

required_permissions

Option<RequiredPermissions>Optional

Filesystem, network, and environment-variable declarations, checked for well-formedness when present. The struct describes; the host sandbox enforces.

public_key

StringRequired

Hex-encoded Ed25519 public key of this tool server, cross-checked against the verifying key during admission.

Example"4f1c…a2e9"

Read the ToolManifest struct
crates/platform/chio-manifest/src/lib.rsrust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolManifest {
    pub schema: String,
    pub server_id: chio_core::ServerId,
    pub name: String,
    pub description: Option<String>,
    pub version: String,
    pub tools: Vec<ToolDefinition>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub server_tools: Vec<ServerTool>,
    pub required_permissions: Option<RequiredPermissions>,
    pub public_key: String,
}

ToolDefinition

Each entry in tools is a ToolDefinition: the contract the kernel admits for one tool.

FieldTypeMeaning
name
String
Tool name, unique within the server.
description
String
Human-readable description.
input_schema
JSON value
JSON Schema for the tool's input arguments.
output_schema
Option<JSON value>
Optional JSON Schema for the tool's output.
pricing
Option<ToolPricing>
Optional advertised pricing, below.
has_side_effects
bool
Writes files, sends network requests, or modifies state. Read-only tools can be cached.
latency_hint
Option<LatencyHint>
instant, fast, moderate, or slow.

Latency hints are advisory: they feed scheduling heuristics and operator dashboards. Latency commitments live on the listing's ListingSla, described in Capability Discovery.

ToolPricing

The pricing block on a ToolDefinition lives at crates/platform/chio-manifest/src/lib.rs:149-157. Its four fields honor deny_unknown_fields, and the currency travels inside MonetaryAmount { units, currency }.

crates/platform/chio-manifest/src/lib.rsrust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolPricing {
    pub pricing_model: PricingModel,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_price: Option<MonetaryAmount>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub unit_price: Option<MonetaryAmount>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub billing_unit: Option<String>,
}

PricingModel enum

Four variants, at crates/platform/chio-manifest/src/lib.rs:161-166, serialized in snake case.

crates/platform/chio-manifest/src/lib.rsrust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PricingModel {
    Flat,
    PerInvocation,
    PerUnit,
    Hybrid,
}
VariantWire formFields to set
Flat
"flat"
One fixed amount per call: base_price only.
PerInvocation
"per_invocation"
unit_price and billing_unit = "invocation".
PerUnit
"per_unit"
unit_price and a billing_unit such as tokens, rows, or bytes.
Hybrid
"hybrid"
base_price, unit_price, and billing_unit.

For PerInvocation, the metering pipeline emits one ledger entry per receipt. PerUnit charges by a count derived from the output. Hybrid is a flat connection fee plus a per-unit charge, and settlement adds the two.

Where SLA guarantees and per-call caps live

ToolPricing has no top-level currency, no max_cost_per_invocation, and no sla_guarantees. SLA commitments are carried by chio_listing::ListingSla (crates/economy/chio-listing/src/discovery.rs:113-136), paired into a ListingPricingHint:

crates/economy/chio-listing/src/discovery.rs113-121rust
/// Service-level advertisement paired with a pricing hint.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ListingSla {
    pub max_latency_ms: u64,
    /// Availability SLA expressed in basis points. `10_000` means 100.00%.
    pub availability_bps: u32,
    pub throughput_rps: u64,
}

Per-call cost caps live on the issued capability. ToolGrant::max_cost_per_invocation (chio-core-types/src/capability/scope.rs:110) is an Option<MonetaryAmount> checked against parent grants in ToolGrant::is_subset_of (capability/scope.rs:129).

SignedManifest and signing functions

Manifests are signed with Ed25519 over the canonical JSON encoding of the manifest body. The envelope carries the signing key, so a verifier needs no out-of-band lookup to check it. A service built with NativeChioServiceBuilder signs the manifest its builder produced with the server keypair.

SignedManifestrust
pub struct SignedManifest {
    /// The tool manifest.
    pub manifest: ToolManifest,
    /// Ed25519 signature over the canonical JSON encoding of manifest.
    pub signature: Signature,
    /// The signing key (for verification without out-of-band lookup).
    pub signer_key: PublicKey,
}
Signing functionsrust
pub fn sign_manifest(
    manifest: &ToolManifest,
    keypair: &Keypair,
) -> Result<SignedManifest, ManifestError>;

pub fn verify_manifest(
    signed: &SignedManifest,
    public_key: &PublicKey,
) -> Result<(), ManifestError>;

Both functions call validate_manifest first, which enforces these invariants:

  • schema equals chio.manifest.v1, else UnsupportedSchema.
  • server_id, name, and version are non-empty, equal to their trimmed form, and free of control characters, else InvalidManifestField.
  • tools is non-empty, else EmptyManifest.
  • Tool names are unique, else DuplicateToolName.
  • Server-tool allowlist entries are unique, else DuplicateServerTool.
  • required_permissions entries are well-formed when present.

required_permissions

RequiredPermissions is optional. Permission overreach is a deployment-time decision: the operator grants the declared permissions or refuses to admit the manifest, and the host that runs the server enforces them.

FieldTypeMeaning
read_paths
Option<Vec<String>>
Filesystem paths the server reads.
write_paths
Option<Vec<String>>
Filesystem paths the server writes.
network_hosts
Option<Vec<String>>
Network hosts the server reaches.
environment_variables
Option<Vec<String>>
Environment variables the server reads.

Provider-native server tools

The ServerTool enum names the provider-native tools a manifest may allowlist (Anthropic computer_use_*, bash_*, text_editor_*). The server_tools field is the allowlist, and absent entries default to deny.

ServerToolrust
#[serde(rename_all = "snake_case")]
pub enum ServerTool {
    ComputerUse,
    Bash,
    TextEditor,
}

// Anthropic versions server-tool names with a trailing date,
// e.g. bash_20241022. Treat known categories as server tools so
// version bumps stay fail-closed behind the same allowlist entry.
ServerTool::from_anthropic_wire_name("bash_20241022")
    == Some(ServerTool::Bash);

x-chio-* extensions for OpenAPI sources

When chio-openapi generates a manifest from an OpenAPI document, five vendor extensions on each operation feed the manifest and the chio api protect proxy. spec/OPENAPI-INTEGRATION.md defines them in sections 2.1 to 2.5 and their precedence in section 3. Pricing on an OpenAPI-derived manifest is set by the operator at signing time; the extension set has no x-chio-pricing or x-chio-sla.

ExtensionTypeEffectSpec
x-chio-sensitivity
string
Log granularity: public, internal, sensitive, restricted. Default internal.
§2.1
x-chio-side-effects
boolean
Overrides the method default: true forces deny-by-default, false forces session-scoped allow.
§2.2
x-chio-approval-required
boolean
Forces deny-by-default and sets annotations.requires_approval on the generated ToolDefinition.
§2.3
x-chio-budget-limit
u64
Per-invocation cost cap in minor units, read by the budget guard at request time.
§2.4
x-chio-publish
boolean
false excludes the operation when respect_publish_flag is true.
§2.5

x-chio-approval-required: true beats both the HTTP method default and any x-chio-side-effects value, so a GET operation marked approval-required still produces deny-by-default. OpenAPI Integration carries the full precedence matrix.

Next: publish the registered server as a listing

Signed Tool Manifests · Chio Docs