Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt

Use this file to discover all available pages before exploring further.

Tool system

Defining tools

Define tools using JSON Schema:
use meerkat::ToolDef;
use serde_json::json;

let tool = ToolDef::new(
    "get_weather",
    "Get current weather for a city",
    json!({
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "City name"
            },
            "units": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"],
                "default": "celsius"
            }
        },
        "required": ["city"]
    }),
);

Implementing AgentToolDispatcher

The AgentToolDispatcher trait connects your tools to the agent:
use async_trait::async_trait;
use meerkat::{AgentToolDispatcher, ToolDef, ToolError, ToolResult};
use meerkat_core::{ToolCallView, ToolDispatchOutcome};
use serde_json::json;
use std::sync::Arc;

struct MyToolDispatcher;

#[async_trait]
impl AgentToolDispatcher for MyToolDispatcher {
    fn tools(&self) -> Arc<[Arc<ToolDef>]> {
        vec![
            Arc::new(ToolDef::new(
                "search",
                "Search the web",
                json!({
                    "type": "object",
                    "properties": {
                        "query": {"type": "string"}
                    },
                    "required": ["query"]
                }),
            )),
        ].into()
    }

    async fn dispatch(&self, call: ToolCallView<'_>) -> Result<ToolDispatchOutcome, ToolError> {
        match call.name {
            "search" => {
                #[derive(serde::Deserialize)]
                struct Args { query: String }

                let args: Args = call.parse_args()
                    .map_err(|e| ToolError::invalid_arguments("search", e.to_string()))?;
                Ok(ToolResult::new(call.id.to_string(), format!("Results for: {}", args.query), false).into())
            }
            _ => Err(ToolError::not_found(call.name)),
        }
    }
}
For dynamic tool registration:
use meerkat::ToolRegistry;

let mut registry = ToolRegistry::new();

registry.register(tool_def);

let weather = registry.get("get_weather");
let tools = registry.list();

Multimodal tool results

Tool results can carry either plain text or multimodal content blocks:
use meerkat::ToolResult;
use meerkat_core::{ContentBlock, ImageData};

// Standard text result
let output = ToolResult::new("tool-1".into(), "{\"status\":\"ok\",\"count\":42}".into(), false);

// Multimodal result with image content
let output = ToolResult::with_blocks("tool-2".into(), vec![
    ContentBlock::Text { text: "Screenshot captured:".to_string() },
    ContentBlock::Image {
        media_type: "image/png".to_string(),
        data: ImageData::Inline { data: base64_encoded_png },
    },
], false);
ToolResult::new(...) creates a text-only result. ToolResult::with_blocks(...) passes content blocks directly into ToolResult.content, enabling tools to return images and other rich content to vision-capable models. The built-in view_image tool uses the same content-block mechanism to read image files from disk and return them as ContentBlock::Image blocks. It is automatically hidden from models that lack vision or image tool result support.

Session stores

File-based persistence using JSONL format:
use meerkat::JsonlStore;
use std::path::PathBuf;

let store = JsonlStore::new(PathBuf::from("./sessions"));
store.init().await?;

store.save(&session).await?;
let session = store.load(&session_id).await?;
store.delete(&session_id).await?;
Implement the SessionStore trait and pass it to AgentFactory::session_store(). Then build a runtime-backed SessionService so the custom store participates in the canonical session lifecycle instead of only a one-off direct agent.
use async_trait::async_trait;
use meerkat::{Session, SessionFilter, SessionId, SessionMeta, SessionStore, SessionStoreError};
use std::sync::Arc;

struct BigQueryStore { /* your storage backend */ }

#[async_trait]
impl SessionStore for BigQueryStore {
    async fn save(&self, session: &Session) -> Result<(), SessionStoreError> {
        // Persist session to BigQuery
        Ok(())
    }

    async fn load(&self, id: &SessionId) -> Result<Option<Session>, SessionStoreError> {
        // Load session by ID
        Ok(None)
    }

    async fn list(&self, filter: SessionFilter) -> Result<Vec<SessionMeta>, SessionStoreError> {
        Ok(vec![])
    }

    async fn delete(&self, id: &SessionId) -> Result<(), SessionStoreError> {
        Ok(())
    }
}

// Use as a custom store implementation boundary
let store: Arc<dyn SessionStore> = Arc::new(BigQueryStore { /* ... */ });
Prefer SessionStore + session_store() over implementing AgentSessionStore directly. SessionStore is the richer trait (with list, delete, exists) and AgentFactory automatically wraps it via StoreAdapter. Implementing AgentSessionStore directly bypasses the factory and loses runtime-backed session orchestration.For runtime-backed persistent services, realm persistence still flows through the PersistenceBundle opened for that realm. Treat custom SessionStore implementations as a storage seam for custom builders or direct agent/store integration, not as a drop-in replacement for the entire runtime persistence bundle.

MCP integration

Route tool calls across multiple MCP servers:
use meerkat::{McpRouter, McpServerConfig};
use std::collections::HashMap;

let mut router = McpRouter::new();

// Add stdio-based MCP server
let config = McpServerConfig::stdio(
    "my-server",
    "/path/to/mcp-server".to_string(),
    vec!["--arg".to_string()],
    HashMap::new(),
);
router.add_server(config).await?;

// Add HTTP/SSE-based MCP server
let config = McpServerConfig::streamable_http(
    "remote-server",
    "https://mcp.example.com/sse".to_string(),
    HashMap::new(),
);
router.add_server(config).await?;

// List all available tools
let tools = router.list_tools().await?;

// Call a tool
let result = router.call_tool("tool_name", &args).await?;

// Graceful shutdown
router.shutdown().await;

See also