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.
| Consideration | MCP Wrapping | Native Server |
|---|---|---|
| Existing server | Use the proxy with zero modifications | Rewrite required |
| Typed schemas | Inherited from MCP server | Declared per-tool with JSON Schema |
| Pricing metadata | Added at the proxy layer | Declared inline on each tool definition |
| Performance | Extra process boundary + JSON-RPC hop | In-process function call |
| Resources and prompts | Proxied from MCP server | Registered directly on the builder |
| Manifest signing | Proxy generates and signs | Server 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:
$ cargo new my-tool-server
$ cd my-tool-server[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:
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 (setshas_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)
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.
The constructor takes a server ID and a hex-encoded Ed25519 public key:
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>:
.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():
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:
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.
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:
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.
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".
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.
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.
NativeTool::new("search", "Search documents", schema)
.hybrid_price(25, 10, "USD", "document")
// base_price: 25 USD + unit_price: 10 USD per documentPricing is declarative
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.
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
Manifest Structure
When .build() succeeds, the resulting NativeChioService holds a validated ToolManifest with the following fields:
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