descry-tool-core 0.3.1

Core traits and types for descry-tool framework
Documentation

descry-tool-core

Crates.io Documentation License: MIT

Core traits and types for the descry-tool framework - a modern, async-first Rust framework for building LLM-compatible tools.

Features

  • Unified async Tool trait - Single trait for all tools, no SyncTool/AsyncTool separation
  • Arc - Thread-safe context that works seamlessly across await points
  • Compile-time registration - Zero-cost tool registration using inventory
  • Thread-safe extensions - Concurrent extensions using DashMap
  • Multi-protocol adapters - Built-in support for MCP, OpenAI, and Anthropic
  • Tower Service integration - Optional middleware ecosystem support
  • Auto-generated schemas - Type-safe JSON Schema with thread-local caching
  • Error chaining - Rich error types with thiserror and #[source] support

Quick Start

Define a Tool

use descry_tool_core::{Tool, ToolContext, ToolError};
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
use std::sync::Arc;

#[derive(Deserialize, JsonSchema)]
struct AddParams {
    a: i32,
    b: i32,
}

#[derive(Serialize, JsonSchema)]
struct AddOutput {
    result: i32,
}

struct AddTool;

impl Tool for AddTool {
    type Params = AddParams;
    type Output = AddOutput;

    const NAME: &'static str = "add";
    const DESCRIPTION: &'static str = "Add two numbers";

    async fn call(
        _ctx: Arc<ToolContext>,
        params: Self::Params,
    ) -> Result<Self::Output, ToolError> {
        Ok(AddOutput {
            result: params.a + params.b,
        })
    }
}

// Register the tool
inventory::submit! {
    descry_tool_core::ToolMeta {
        name: AddTool::NAME,
        description: AddTool::DESCRIPTION,
        call: |ctx, params| {
            Box::pin(async move {
                let params: AddParams = serde_json::from_value(params)?;
                let result = <AddTool as Tool>::call(ctx, params).await?;
                Ok(serde_json::to_value(result)?)
            })
        },
        schema: || AddTool::schema(),
        examples: || AddTool::EXAMPLES,
    }
}

Use the Tool

use descry_tool_core::{call_tool, tool_names, ToolContext};
use std::sync::Arc;

#[tokio::main]
async fn main() {
    // List all registered tools
    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 API

Tool Trait

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>;
}

Context System

use descry_tool_core::ToolContext;
use std::sync::Arc;

let ctx = Arc::new(ToolContext::new());

// Store extensions
#[derive(Debug)]
struct Database;
ctx.insert(Database);

// Retrieve extensions
let db: Arc<Database> = ctx.get().unwrap();

Registry Functions

// 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

Multi-Protocol Adapters

MCP (Model Context Protocol)

use descry_tool_core::adapters::{McpAdapter, ToolAdapter};

let mcp_spec = McpAdapter::to_spec(tool_meta);
// {
//   "name": "add",
//   "description": "Add two numbers",
//   "inputSchema": {...}
// }

OpenAI Function Calling

use descry_tool_core::adapters::{OpenAiAdapter, ToolAdapter};

let openai_spec = OpenAiAdapter::to_spec(tool_meta);
// {
//   "type": "function",
//   "function": {
//     "name": "add",
//     "description": "Add two numbers",
//     "parameters": {...}
//   }
// }

Anthropic (Claude)

use descry_tool_core::adapters::{AnthropicAdapter, ToolAdapter};

let anthropic_spec = AnthropicAdapter::to_spec(tool_meta);
// {
//   "name": "add",
//   "description": "Add two numbers",
//   "input_schema": {...}
// }

Tower Service (Optional)

Enable the tower feature for middleware support:

[dependencies]
descry-tool-core = { version = "0.3", features = ["tower"] }
tower = "0.5"
use descry_tool_core::tower::{tool_service, ToolRequest};
use tower::{Service, ServiceBuilder};
use std::time::Duration;

let service = ServiceBuilder::new()
    .timeout(Duration::from_secs(30))
    .service(tool_service());

let response = service.call(request).await?;

Error Handling

use descry_tool_core::ToolError;

match call_tool("add", params, ctx).await {
    Ok(result) => println!("Success: {:?}", result),
    Err(ToolError::ToolNotFound(name)) => {
        eprintln!("Tool '{}' not found", name);
    }
    Err(ToolError::InvalidParameters(msg)) => {
        eprintln!("Invalid parameters: {}", msg);
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}

Examples

See the examples directory:

  • basic.rs - Basic tool definition and usage
  • macro_example.rs - Using with #[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

Related Crates

Requirements

  • Rust 1.85.0+ (Edition 2024)

License

MIT