# Descry Tool
A modern, async-first Rust framework for building LLM-compatible tools with zero-cost abstractions and compile-time registration.
## Features
- **Single async Tool trait** - No SyncTool/AsyncTool separation
- **Arc<ToolContext>** - No borrow checker hell, works seamlessly across await points
- **Derive-first design** - One `#[tool]` macro generates everything
- **Thread-safe** - `DashMap` for concurrent extensions
- **Multi-protocol adapters** - Built-in MCP, OpenAI, Anthropic support
- **Tower Service integration** - Middleware ecosystem support (optional)
- **Auto-generated schemas** - Type-safe JSON Schema with thread-local caching
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
descry-tool-core = "0.2"
descry-tool-macros = "0.2"
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "sync"] }
# Optional: for async runtime
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
# Optional: Tower middleware support
descry-tool-core = { version = "0.2", features = ["tower"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["timeout", "limit"] }
[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "sync"] }
[features]
default = ["macros"]
tower = ["dep:tower"]
```
## Quick Start
### Define a Tool
```rust
use descry_tool_core::{Tool, ToolContext, ToolError};
use descry_tool_macros::tool;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Deserialize, JsonSchema)]
struct AddParams {
a: i32,
b: i32,
}
#[derive(Serialize, JsonSchema)]
struct AddOutput {
result: i32,
}
// One macro does it all!
#[tool(
name = "add",
description = "Add two numbers"
)]
async fn add(_ctx: Arc<ToolContext>, params: AddParams) -> Result<AddOutput, ToolError> {
Ok(AddOutput {
result: params.a + params.b,
})
}
```
### Use the Tool
```rust
use descry_tool_core::{all_tools, call_tool, tool_names, ToolContext};
use std::sync::Arc;
#[tokio::main]
async fn main() {
// Tools are automatically registered at compile time!
let names = tool_names();
println!("Available tools: {:?}", names);
// Call a tool
let ctx = Arc::new(ToolContext::new());
let params = serde_json::json!({"a": 5, "b": 3});
let result = call_tool("add", params, ctx).await.unwrap();
println!("Result: {}", result);
}
```
## Core Concepts
### Tool Trait
The unified `Tool` trait with async-first design:
```rust
pub trait Tool: Send + Sync + 'static {
type Params: DeserializeOwned + JsonSchema + Send + Sync + 'static;
type Output: Serialize + JsonSchema + Send + Sync + 'static;
const NAME: &'static str;
const DESCRIPTION: &'static str;
const EXAMPLES: &'static [(&'static str, &'static str)] = &[];
fn schema() -> &'static serde_json::Value;
async fn call(
ctx: Arc<ToolContext>,
params: Self::Params
) -> Result<Self::Output, ToolError>;
}
```
### Compile-Time Registration
Tools are automatically registered using `inventory`:
```rust
// No manual registration needed!
// The #[tool] macro automatically generates:
//
// inventory::submit! {
// ToolMeta {
// name: "add",
// description: "Add two numbers",
Generate tool specifications for different LLM platforms
```rust
use descry_tool_core::{
adapters::{McpAdapter, OpenAiAdapter, AnthropicAdapter, ToolAdapter},
all_tools,
};
// MCP (Model Context Protocol)
let mcp_tools: Vec<_> = all_tools()
.map(|meta| McpAdapter::to_spec(meta))
.collect();
// OpenAI Function Calling
let openai_tools: Vec<_> = all_tools()
.map(|meta| OpenAiAdapter::to_spec(meta))
.collect();
// Anthropic (Claude)
let anthropic_tools: Vec<_> = all_tools()
.map(|meta| AnthropicAdapter::to_spec(meta))
.collect();
```
## Tower Middleware (Optional)
Use Tower middleware for logging, timeouts, rate limiting
```rust
use descry_tool_core::tower::{tool_service, ToolRequest};
use tower::{Service, ServiceBuilder};
use tower_http::{timeout::TimeoutLayer, limit::RateLimitLayer};
use std::time::Duration;
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(RateLimitLayer::new(10, Duration::from_secs(1)))
.service(tool_service());
let response = service.call(request).await?;
```
## Examples
See `descry-tool-core/examples/`:
- `basic.rs` - Basic tool definition and usage
- `macro_example.rs` - Using the `#[tool]` macro
- `complete_example.rs` - Multiple tools with error handling
- `multi_protocol.rs` - Multi-protocol adapter usage
- `tower_basic.rs` - Tower Service integration
- `tower_middleware.rs` - Tower middleware example
- `debug_schema.rs` - Schema generation debugging
## API Reference
### Core Functions
```rust
// Get all registered tools
pub fn all_tools() -> impl Iterator<Item = &'static ToolMeta>
// Find tool by name
pub fn find_tool(name: &str) -> Option<&'static ToolMeta>
// Call a tool
pub async fn call_tool(
name: &str,
params: serde_json::Value,
ctx: Arc<ToolContext>
) -> Result<serde_json::Value, ToolError>
// Get tool names
pub fn tool_names() -> Vec<&'static str>
// Check if tool exists
pub fn tool_exists(name: &str) -> bool
```
## Project Structure
```
descry-tool/
├── descry-tool-core/ # Core traits and types
│ ├── src/
│ │ ├── lib.rs # Main exports
│ │ ├── tool.rs # Tool trait
│ │ ├── context.rs # Arc<ToolContext>
│ │ ├── error.rs # ToolError
│ │ ├── registry.rs # inventory registration
│ │ ├── adapters/ # Protocol adapters
│ │ └── tower.rs # Tower Service (optional)
│ └── examples/ # Usage examples
├── descry-tool-macros/ # Procedural macros
│ └── src/
│ └── tool.rs # #[tool] macro implementation
└── README.md