Chio/Docs

Native Tool Server

Build a tool server that speaks the chio kernel protocol directly, without wrapping an MCP server. Native servers get typed schemas, built-in pricing metadata, manifest signing, and zero-overhead dispatch. The end-to-end reference lives at examples/hello-tool/ in the repo.

When to Use Native vs. MCP Wrapping

chio supports two modes for tool servers: wrapping an existing MCP server with chio mcp serve, or implementing a native tool server using the Rust SDK.

ConsiderationMCP WrappingNative Server
Existing serverUse the proxy with zero modificationsRewrite required
Typed schemasInherited from MCP serverDeclared per-tool with JSON Schema
Pricing metadataAdded at the proxy layerDeclared inline on each tool definition
PerformanceExtra process boundary + JSON-RPC hopIn-process function call
Resources and promptsProxied from MCP serverRegistered directly on the builder
Manifest signingProxy generates and signsServer signs its own manifest

Choose MCP wrapping when you already have a working MCP server and want governance without code changes. Choose native when you need maximum performance, first-class pricing, or want to ship a self-contained Rust binary.


Project Setup

Create a new Rust project and add the required Chio crates:

bash
$ cargo new my-tool-server
$ cd my-tool-server
Cargo.toml
[package]
name = "my-tool-server"
version = "0.1.0"
edition = "2021"

[dependencies]
chio-core = { path = "../chio/crates/chio-core" }
chio-kernel = { path = "../chio/crates/chio-kernel" }
chio-manifest = { path = "../chio/crates/chio-manifest" }
chio-mcp-adapter = { path = "../chio/crates/chio-mcp-adapter" }
serde_json = "1"

Crate overview

chio-mcp-adapter provides NativeChioServiceBuilder, NativeTool, NativeResource, and NativePrompt: the high-level authoring helpers that save you from implementing kernel traits by hand. chio-core provides cryptographic primitives including chio_core::crypto::Keypair.

Defining Tools with NativeTool

Each tool is created with NativeTool::new, which takes a name, description, and a JSON Schema for the input:

rust
use chio_mcp_adapter::NativeTool;

let greet_tool = NativeTool::new(
    "greet",
    "Returns a personalized greeting",
    serde_json::json!({
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "The name to greet"
            }
        },
        "required": ["name"]
    }),
);

The builder pattern exposes additional metadata:

  • .output_schema(json): declare the JSON Schema for the tool's return value
  • .read_only(): mark the tool as having no side effects (sets has_side_effects = false)
  • .latency_hint(LatencyHint::Instant): hint at expected response time
  • Pricing helpers (per_invocation_price(), flat_price(), per_unit_price(), hybrid_price(), see Tool Pricing below)
rust
let greet_tool = NativeTool::new(
    "greet",
    "Returns a personalized greeting",
    serde_json::json!({
        "type": "object",
        "properties": {
            "name": { "type": "string", "description": "The name to greet" }
        },
        "required": ["name"]
    }),
)
.output_schema(serde_json::json!({
    "type": "object",
    "properties": {
        "greeting": { "type": "string" }
    }
}))
.read_only()
.per_invocation_price(25, "USD")
.latency_hint(chio_manifest::LatencyHint::Instant);

Building the Service

NativeChioServiceBuilder wires tools, resources, and prompts into a NativeChioService that implements the kernel's ToolServerConnection, ResourceProvider, and PromptProvider traits.

rendering…
Native tool server lifecycle: build, sign, serve, and dispatch in-process.

The constructor takes a server ID and a hex-encoded Ed25519 public key:

rust
use chio_mcp_adapter::NativeChioServiceBuilder;

NativeChioServiceBuilder::new("srv-hello", public_key_hex)
    .server_name("Hello Tool Server")
    .server_version("0.1.0")
    .server_description("A native Chio service that exposes a greeting tool")

Registering Tool Handlers

Chain .tool(definition, handler) to register each tool. The handler is a closure that receives the arguments as a serde_json::Value and returns Result<Value, KernelError>:

rust
.tool(
    greet_tool,
    |arguments| {
        let name = arguments
            .get("name")
            .and_then(|value| value.as_str())
            .unwrap_or("stranger");
        Ok(serde_json::json!({
            "greeting": format!("Hello, {name}!")
        }))
    },
)

Adding Resources

Resources expose static or dynamic data that agents can read. Use NativeResource to define URI, name, description, and MIME type. For static content, use .static_resource():

rust
use chio_core::ResourceContent;
use chio_mcp_adapter::NativeResource;

.static_resource(
    NativeResource::new("memory://hello/template", "Greeting Template")
        .description("A static greeting template")
        .mime_type("text/plain"),
    vec![ResourceContent {
        uri: "memory://hello/template".to_string(),
        mime_type: Some("text/plain".to_string()),
        text: Some("Hello, {name}!".to_string()),
        blob: None,
        annotations: None,
    }],
)

For dynamic resources, use .resource(definition, handler) where the handler receives the URI and returns Result<Option<Vec<ResourceContent>>, KernelError>.

Adding Prompts

Prompts are pre-built message sequences. Use NativePrompt and .static_prompt() for fixed prompts:

rust
use chio_core::{PromptMessage, PromptResult};
use chio_mcp_adapter::NativePrompt;

.static_prompt(
    NativePrompt::new("compose_greeting")
        .description("Creates a user prompt that asks for a polite greeting"),
    PromptResult {
        description: Some("Greeting composition prompt".to_string()),
        messages: vec![PromptMessage {
            role: "user".to_string(),
            content: serde_json::json!({
                "type": "text",
                "text": "Compose a short, polite greeting for Ada."
            }),
        }],
    },
)

Calling build()

.build() validates the tool manifest and returns the finished NativeChioService. It returns Result<NativeChioService, ManifestError>: validation failures are caught at build time, not at runtime.

rust
let service = builder.build().expect("valid manifest");

Complete Example

Here is a full working example based on the examples/hello-tool/ reference in the chio repository. It defines a tool, a resource, and a prompt, then invokes the tool and signs the manifest:

src/main.rs
use chio_core::crypto::Keypair;
use chio_core::{PromptMessage, ResourceContent};
use chio_kernel::{PromptProvider, ResourceProvider, ToolServerConnection};
use chio_mcp_adapter::{
    NativeChioServiceBuilder, NativePrompt, NativeResource, NativeTool,
};

fn build_service(public_key_hex: String) -> chio_mcp_adapter::NativeChioService {
    NativeChioServiceBuilder::new("srv-hello", public_key_hex)
        .server_name("Hello Tool Server")
        .server_version("0.1.0")
        .server_description(
            "A tiny native Chio service that exposes a tool, resource,              prompt, and priced manifest",
        )
        .tool(
            NativeTool::new(
                "greet",
                "Returns a personalized greeting",
                serde_json::json!({
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "The name to greet"
                        }
                    },
                    "required": ["name"]
                }),
            )
            .output_schema(serde_json::json!({
                "type": "object",
                "properties": {
                    "greeting": { "type": "string" }
                }
            }))
            .read_only()
            .per_invocation_price(25, "USD")
            .latency_hint(chio_manifest::LatencyHint::Instant),
            |arguments| {
                let name = arguments
                    .get("name")
                    .and_then(|value| value.as_str())
                    .unwrap_or("stranger");
                Ok(serde_json::json!({
                    "greeting": format!(
                        "Hello, {name}! This greeting was served                          by a native Chio service."
                    )
                }))
            },
        )
        .static_resource(
            NativeResource::new("memory://hello/template", "Greeting Template")
                .description("A static greeting template")
                .mime_type("text/plain"),
            vec![ResourceContent {
                uri: "memory://hello/template".to_string(),
                mime_type: Some("text/plain".to_string()),
                text: Some(
                    "Hello, {name}! This greeting was served                      by a native Chio service."
                        .to_string(),
                ),
                blob: None,
                annotations: None,
            }],
        )
        .static_prompt(
            NativePrompt::new("compose_greeting")
                .description("Creates a prompt that asks for a polite greeting"),
            chio_core::PromptResult {
                description: Some("Greeting composition prompt".to_string()),
                messages: vec![PromptMessage {
                    role: "user".to_string(),
                    content: serde_json::json!({
                        "type": "text",
                        "text": "Compose a short, polite greeting for Ada."
                    }),
                }],
            },
        )
        .build()
        .expect("build native Chio service")
}

fn main() {
    let server_kp = Keypair::generate();
    let service = build_service(server_kp.public_key().to_hex());

    // Print the manifest
    println!("Server: {} ({})", service.manifest().name, service.server_id());
    for tool in &service.manifest().tools {
        println!("  Tool: {} - {}", tool.name, tool.description);
    }

    // Sign the manifest
    match chio_manifest::sign_manifest(service.manifest(), &server_kp) {
        Ok(_signed) => println!("Manifest signed successfully."),
        Err(error) => eprintln!("Failed to sign manifest: {error}"),
    }

    // Invoke the tool
    let result = service
        .invoke("greet", serde_json::json!({ "name": "World" }), None)
        .expect("invoke greet");
    println!("Output: {result}");
}

Tool Pricing

Every native tool can declare pricing metadata in its manifest. The kernel uses this information for economic checks: if a capability token's budget cannot cover the declared price, the call is denied before execution.

NativeTool exposes four pricing helpers:

Flat Price

A single fixed price regardless of usage. The base_price is set; there is no unit price or billing unit.

rust
NativeTool::new("lookup", "Look up a record", schema)
    .flat_price(500, "USD")

Per-Invocation Price

Charged once per call. The billing unit is automatically set to "invocation".

rust
NativeTool::new("greet", "Return a greeting", schema)
    .per_invocation_price(25, "USD")

Per-Unit Price

Scaled by a custom billing unit: tokens processed, documents returned, bytes transferred, etc.

rust
NativeTool::new("tokenize", "Tokenize text", schema)
    .per_unit_price(2, "USD", "1k_tokens")

Hybrid Price

Combines a base price with a per-unit price. Useful when a tool has fixed overhead plus variable cost.

rust
NativeTool::new("search", "Search documents", schema)
    .hybrid_price(25, 10, "USD", "document")
    // base_price: 25 USD + unit_price: 10 USD per document

Pricing is declarative

Pricing metadata is embedded in the tool manifest. The kernel reads it during economic checks, but the actual billing settlement is handled by the receipt and settlement layer. Declaring a price does not automatically debit an account; it enables the kernel to enforce budget constraints.

Manifest Signing

The tool manifest is a structured declaration of everything the server offers: its ID, name, version, tools (with schemas and pricing), and the server's public key. Signing the manifest with the server's private key proves that the declared tools and pricing are authentic.

rust
use chio_core::crypto::Keypair;

let server_kp = Keypair::generate();
let service = build_service(server_kp.public_key().to_hex());

// sign_manifest returns a SignedManifest with manifest_hash (SHA-256 hex)
// and signature (Ed25519 hex) fields.
let signed = chio_manifest::sign_manifest(service.manifest(), &server_kp)
    .expect("sign manifest");

println!("manifest_hash: {}", signed.manifest_hash);
println!("signature: {}", signed.signature);

The signed manifest can be published to a registry, exchanged during capability negotiation, or verified offline. Any tampering (changing a price, adding a tool, modifying a schema) invalidates the signature.

Protect your server keypair

The server keypair is the root of trust for the manifest. If the private key is compromised, an attacker can produce a valid-looking manifest with altered pricing or tool definitions. Store it with the same care as any signing key.

Manifest Structure

When .build() succeeds, the resulting NativeChioService holds a validated ToolManifest with the following fields:

rust
ToolManifest {
    schema: "chio.manifest.v1",
    server_id: "srv-hello",
    name: "Hello Tool Server",
    description: Some("..."),
    version: "0.1.0",
    tools: vec![ToolDefinition {
        name: "greet",
        description: "Returns a personalized greeting",
        input_schema: json!({ "type": "object", ... }),
        output_schema: Some(json!({ ... })),
        pricing: Some(ToolPricing {
            pricing_model: PricingModel::PerInvocation,
            base_price: None,
            unit_price: Some(MonetaryAmount { units: 25, currency: "USD" }),
            billing_unit: Some("invocation"),
        }),
        has_side_effects: false,
        latency_hint: Some(LatencyHint::Instant),
    }],
    required_permissions: None,
    public_key: "7b0f6f63...",
}

Next Steps

  • Custom Guards · implement your own guard to enforce domain-specific policies
  • Economics · understand how pricing flows into budgets, metering, and settlement
  • Receipts · every tool invocation produces a signed receipt, including cost data