thulp-registry 0.3.3

Tool registry and management for thulp
Documentation
# thulp-registry

Async thread-safe **metadata catalog** for `thulp_core::ToolDefinition`.

## Overview

This crate provides a registry for managing tool *definitions* (the metadata
the LLM sees) with support for dynamic registration, tagging, and discovery.
The registry is designed for concurrent access in async environments.

## Intended Use

`thulp-registry` is a metadata-only store. It holds `ToolDefinition` values —
the JSON-schema-shaped descriptions an LLM consumes — plus tags for grouping
and discovery. It is **not** an execution runtime: there is no `Tool` trait
and no `execute()` method.

Use this crate when you need to:

- Publish or serialize a catalog of tools (MCP discovery, skill manifests,
  documentation generation)
- Filter or tag definitions before exposing them to an LLM
- Maintain a cross-process / cross-service tool catalog where the actual
  executors live elsewhere (e.g., in a remote MCP server)

If instead you need an **in-process executable registry** that can dispatch
`(name, args)` to a Rust implementation, you want a different abstraction:
typically a `HashMap<String, Arc<dyn Tool>>` where `Tool` has
`async fn execute(&self, args: Value) -> Result<Value>`. Two existing
examples in the dirmacs stack:

- `pawan::tools::ToolRegistry` — pawan's in-process executable registry
  with 3-tier visibility (Core / Standard / Extended) and scored
  `select_for_query()` for dynamic tool selection
- `ares::tools::registry::ToolRegistry` — ares-server's executable registry
  used by the agent loop

Both wrappers keep their own `Arc<dyn Tool>` storage for execution and use
`thulp-core::ToolDefinition` for the metadata side. They also integrate
`thulp-query` for DSL-driven filtering. The split is intentional: separating
metadata from execution lets the same definitions be published, queried, and
shipped to LLMs without dragging an execution runtime into every consumer.

## Features

- **Dynamic Registration**: Register and unregister tools at runtime
- **Thread-Safe**: Concurrent access via `RwLock` for safe multi-threaded use
- **Tool Discovery**: Find tools by name or tag
- **Tagging System**: Organize tools with custom tags
- **Batch Operations**: Register multiple tools at once
- **Async Design**: Built on tokio for async operations

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
thulp-registry = "0.2"
```

## Usage

### Creating a Registry

```rust
use thulp_registry::ToolRegistry;

let registry = ToolRegistry::new();
```

### Registering Tools

```rust
use thulp_registry::ToolRegistry;
use thulp_core::{ToolDefinition, Parameter, ParameterType};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let registry = ToolRegistry::new();

    // Create a tool definition
    let tool = ToolDefinition::builder("read_file")
        .description("Read file contents")
        .parameter(
            Parameter::builder("path")
                .param_type(ParameterType::String)
                .required(true)
                .build()
        )
        .build();

    // Register the tool
    registry.register(tool).await?;

    // Check if registered
    assert!(registry.contains("read_file").await);

    Ok(())
}
```

### Registering Multiple Tools

```rust
use thulp_registry::ToolRegistry;
use thulp_core::{ToolDefinition, Parameter, ParameterType};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let registry = ToolRegistry::new();

    let tools = vec![
        ToolDefinition::builder("read_file")
            .description("Read file")
            .build(),
        ToolDefinition::builder("write_file")
            .description("Write file")
            .build(),
        ToolDefinition::builder("delete_file")
            .description("Delete file")
            .build(),
    ];

    registry.register_many(tools).await?;

    assert_eq!(registry.count().await, 3);

    Ok(())
}
```

### Retrieving Tools

```rust
use thulp_registry::ToolRegistry;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let registry = ToolRegistry::new();
    // ... register tools ...

    // Get specific tool
    if let Some(tool) = registry.get("read_file").await? {
        println!("Found tool: {}", tool.name);
    }

    // List all tools
    let tools = registry.list().await?;
    for tool in tools {
        println!("Tool: {}", tool.name);
    }

    Ok(())
}
```

### Tagging Tools

```rust
use thulp_registry::ToolRegistry;
use thulp_core::ToolDefinition;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let registry = ToolRegistry::new();

    // Register tools
    registry.register(ToolDefinition::builder("read_file").build()).await?;
    registry.register(ToolDefinition::builder("write_file").build()).await?;
    registry.register(ToolDefinition::builder("http_get").build()).await?;

    // Tag tools by category
    registry.tag("read_file", "filesystem").await?;
    registry.tag("write_file", "filesystem").await?;
    registry.tag("http_get", "network").await?;

    // Find tools by tag
    let fs_tools = registry.find_by_tag("filesystem").await?;
    assert_eq!(fs_tools.len(), 2);

    let net_tools = registry.find_by_tag("network").await?;
    assert_eq!(net_tools.len(), 1);

    Ok(())
}
```

### Unregistering Tools

```rust
use thulp_registry::ToolRegistry;
use thulp_core::ToolDefinition;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let registry = ToolRegistry::new();

    registry.register(ToolDefinition::builder("temp_tool").build()).await?;
    assert!(registry.contains("temp_tool").await);

    // Remove the tool
    let removed = registry.unregister("temp_tool").await?;
    assert!(removed.is_some());
    assert!(!registry.contains("temp_tool").await);

    Ok(())
}
```

### Clearing the Registry

```rust
use thulp_registry::ToolRegistry;

#[tokio::main]
async fn main() {
    let registry = ToolRegistry::new();
    // ... register tools ...

    // Clear all tools
    registry.clear().await;
    assert_eq!(registry.count().await, 0);
}
```

## Thread Safety

The registry uses `tokio::sync::RwLock` internally, allowing multiple readers or a single writer at any time. All operations are safe to use from multiple async tasks concurrently.

```rust
use thulp_registry::ToolRegistry;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let registry = Arc::new(ToolRegistry::new());

    // Spawn multiple tasks that access the registry
    let handles: Vec<_> = (0..10)
        .map(|i| {
            let reg = registry.clone();
            tokio::spawn(async move {
                // Safe concurrent access
                let count = reg.count().await;
                println!("Task {} sees {} tools", i, count);
            })
        })
        .collect();

    for handle in handles {
        handle.await.unwrap();
    }
}
```

## Testing

```bash
cargo test -p thulp-registry
```

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE]../../LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT]../../LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.