toolguard
ToolGuard 🛡️
A sub-50µs Rust library and proxy that auto-repairs LLM tool calls and turns schema failures into model-readable self-correction prompts.
Agent / Client ───> [ ToolGuard ] ───> Tool / MCP Server
│
Safe Coercion│(e.g., "10" -> 10, ["tag"] from "tag")
Audit Logs │OTel Spans (gen_ai.tool.name)
Actionable Rejection (Sent back to model for instant retry)⚡ The Problem: Tool Calling Breaks in Trivial Ways
When Large Language Models call external tools or APIs, they frequently introduce slight schema mismatches:
Passing numeric arguments as strings:
{"limit": "25"}instead of{"limit": 25}.Passing single scalars instead of arrays:
{"tags": "rust"}instead of{"tags": ["rust"]}.Capitalization or minor typos in enum values:
{"status": "ACTIVE"}instead of"active".Injecting hallucinated keys not permitted by
additionalProperties: false.
When these calls hit standard tool servers or APIs, the server crashes with a cryptic stack trace or a 50-line raw JSON Schema validator error dump. The agent gets confused, burns context window tokens, and often enters an infinite failure loop.
ToolGuard solves this directly at the boundary.
Auto-Repairs Safe Calls in Microseconds: If an argument can be safely converted without losing semantic intent, ToolGuard coerces it instantly (median latency: ~25 µs) and attaches an audit flag to OpenTelemetry spans.
Actionable Model-Readable Rejections: If a call is genuinely broken (e.g. missing required fields), ToolGuard short-circuits execution and returns a concise, model-readable diff teaching the LLM how to self-correct on its immediate next turn.
Loop & Policy Protection: Automatically detects infinite identical tool-call loops and enforces strict mode on destructive tools.
Related MCP server: aperion-shield
🚀 Quickstart
Option A: Transparent MCP Proxy (Claude Desktop, Cursor, Cline)
Wrap any Model Context Protocol (MCP) server with toolguard to protect your desktop agent:
# Wrap a Postgres MCP server
toolguard mcp-proxy -- npx -y @modelcontextprotocol/server-postgres postgresql://...Claude Desktop Configuration (claude_desktop_config.json)
{
"mcpServers": {
"postgres": {
"command": "toolguard",
"args": [
"mcp-proxy",
"--",
"npx",
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://localhost/mydb"
]
}
}
}Option B: Embedded Rust Library (toolguard-core)
Add to your Cargo.toml:
[dependencies]
toolguard-core = "0.1"
serde_json = "1.0"Option C: CLI Schema & Mutation Inspector (CI/CD)
Test and debug coercions from the command line or CI scripts:
# Pretty terminal output
toolguard check --schema schema.json --input call.json --tool-name search_db
# Machine-readable JSON output for automated test runners
toolguard check --schema schema.json --input call.json --tool-name search_db --format jsonUse inside your agentic loop or tool dispatcher:
use serde_json::json;
use toolguard_core::{ToolDefinition, ToolGuard, GuardVerdict};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let guard = ToolGuard::default();
// 1. Register tool schema (pre-compiles JSON Schema in memory)
guard.register_tool(ToolDefinition::new(
"search_db",
json!({
"type": "object",
"properties": {
"query": { "type": "string" },
"limit": { "type": "integer" },
"status": { "type": "string", "enum": ["active", "archived"] }
},
"required": ["query"]
}),
))?;
// 2. Incoming model payload with stringified number and uppercase enum
let raw_args = json!({
"query": "Rust ecosystem",
"limit": "20",
"status": "ACTIVE"
});
// 3. Guard the call (takes ~25 microseconds)
match guard.guard_call("search_db", raw_args, None) {
GuardVerdict::Pass { arguments, flags, latency_us } => {
println!("Coerced arguments: {}", arguments);
// arguments["limit"] is now numeric 20
// arguments["status"] is now "active"
for flag in flags {
println!("Audit: {}", flag);
}
}
GuardVerdict::Reject { error_message, .. } => {
// Forward this short, crisp message back to the LLM to self-correct
println!("Send to LLM: {}", error_message);
}
}
Ok(())
}🔍 Model-Readable Error Comparison
When an agent fails to provide required parameters or provides unfixable values, ToolGuard outputs an actionable diff designed specifically for the LLM's next context turn:
Standard JSON Schema Error (Confusing for LLMs) | ToolGuard Model-Readable Error (High Recovery Rate) |
|
|
📋 Coercion Rules Matrix
Target Schema Type | Received Input | Action | Flag Emitted |
|
| Parse clean integer literal |
|
|
| Parse clean float literal |
|
|
| Case-insensitive boolean parse |
|
|
| Convert literal to string |
|
| Single scalar ( | Wrap in |
|
|
| Case-insensitive normalization |
|
|
| Fuzzy match (Levenshtein dist $\le 1$) |
|
| Extraneous keys | Prune unknown keys safely |
|
| Omitted property with | Inject explicit schema default |
|
|
| Auto-unpack stringified arguments | Evaluated on parsed object |
Strictly Forbidden Coercions (Never Applied)
Lossy numerical truncation:
3.7is never coerced to integer3.Ambiguous enum matches: If input matches multiple enum candidates within edit distance 1, it is rejected.
Missing required keys: Missing fields are never hallucinated with synthetic defaults unless explicitly specified by schema
default.Destructive tools carve-out: Any tool tagged with
is_destructive: true(e.g.delete_db,execute_bash) automatically enforcesStrictvalidation mode by default.
📊 Benchmark & Performance
Run the benchmark locally:
toolguard bench --corpus-dir corpusResults against real-world tool schemas (PostgreSQL, Filesystem, Brave Search, Weather):
================ BENCHMARK RESULTS ================
Total Test Cases Evaluated: 10
Auto-Repaired by Coercion: 6 (60.0%)
Correctly Rejected & Duffed: 4 (40.0%)
Total Handled Correctly: 10 (100.0%)
---------------------------------------------------
Latency p50: 25 µs
Latency p95: 169 µs
Latency p99: 169 µs
===================================================📡 Observability: OpenTelemetry & JSONL
Every evaluated tool call emits OpenTelemetry GenAI semantic attributes:
tool_name: Target tool name.outcome:"passed","coerced", or"rejected".coercion_count: Total number of fields mutated.validation_errors: Number of unfixable schema errors.latency_us: Execution overhead in microseconds.
Zero-dependency structured JSONL logging can also be directed to disk via TelemetryConfig.
🛑 What ToolGuard is NOT
NOT another JSON syntax repair crate: ToolGuard assumes valid JSON syntax. Upstream syntax breakage is handled by streaming token repair libraries (
llm_json). ToolGuard enforces semantic schema validity.NOT an agent framework: ToolGuard does not manage prompts, memory, or state graphs. It works with Rig, LangGraph, or raw HTTP clients.
NOT a security / authorization firewall: Schema validity is not user authorization. Use dedicated firewalls for role-based access control.
📦 Workspace Crates
Crate | Purpose |
Core schema compiler, rule-based coercer, error renderer, and policy engine. | |
Transparent Model Context Protocol (MCP) stdio and HTTP proxy. | |
Standalone command-line interface ( |
📜 License
Licensed under either of:
Apache License, Version 2.0 (LICENSE-APACHE)
MIT license (LICENSE-MIT)
at your option.
This server cannot be deployed
Maintenance
Related MCP Connectors
Deterministic JSON repair, validate, example-gen, schema-coerce for agents. Zero LLM, sub-10ms.
Real-time MCP & A2A prompt-injection, jailbreak, and tool-schema poisoning firewall for AI agents.
Up to 90% fewer tokens: deterministic, declared tool calls, each with a receipt and an audit trail.
AgentGuard — 20-tool AI safety MCP: policy preflight, risk scoring, audit logging, rate limits.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceDeterministic JSON repair, validation, example-generation, and schema-coercion for AI agents — zero LLM calls, sub-10ms, $0.0005 per call.-

aperion-shieldofficial
FlicenseAqualityAmaintenanceLocal guardrail proxy for AI coding agents. Wraps any MCP server (stdio or HTTP/SSE) and blocks destructive tool calls before they execute, with TOFU catalog pinning against rug pulls and tool-poisoning/result-injection scanning. Single Rust binary, Apache-2.0.1411-- AlicenseAqualityAmaintenanceLLM-native code tools: 44 MCP tools for AI agents to read, understand and modify code. Rust tree-sitter parse daemon (crash-isolated), SQLite symbol index (repo map 98ms), transactional journal-backed writes, deterministic quality gates (zero LLM calls). 512MB cgroup: 128 concurrent, zero OOM. MIT.131MIT
- FlicenseNot gradedqualityBmaintenanceEnables deterministic repair of malformed tool-call schemas and guards parameter projections for AI agents, ensuring reliable JSON-RPC interactions via MCP.7-