adk-tool 0.7.0

Tool system for Rust Agent Development Kit (ADK-Rust) agents (FunctionTool, MCP, Google Search)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# adk-tool

Tool system for Rust Agent Development Kit (ADK-Rust) agents (FunctionTool, MCP, Google Search).

[![Crates.io](https://img.shields.io/crates/v/adk-tool.svg)](https://crates.io/crates/adk-tool)
[![Documentation](https://docs.rs/adk-tool/badge.svg)](https://docs.rs/adk-tool)
[![License](https://img.shields.io/crates/l/adk-tool.svg)](LICENSE)

## Overview

`adk-tool` provides the tool infrastructure for the Rust Agent Development Kit ([ADK-Rust](https://github.com/zavora-ai/adk-rust)):

- **FunctionTool** - Create tools from async Rust functions
- **StatefulTool\<S\>** - Wrap shared state (`Arc<S>`) with a tool handler
- **SimpleToolContext** - Lightweight `ToolContext` for non-agent callers (testing, MCP servers)
- **AgentTool** - Use agents as callable tools for composition (runs sub-agents in non-streaming mode for reliable response capture)
- **GoogleSearchTool** - Web search via Gemini's grounding
- **Provider-native wrappers** - Typed declarations for Gemini, Anthropic, and OpenAI built-in tools
- **McpToolset** - Model Context Protocol integration (local & remote servers)
- **McpServerManager** - Multi-server lifecycle management with health monitoring and auto-restart
- **BasicToolset** - Group multiple tools together
- **FilteredToolset** - Filter tools from any toolset by predicate
- **MergedToolset** - Combine multiple toolsets into one
- **PrefixedToolset** - Namespace tool names with a prefix
- **ExitLoopTool** - Control flow for loop agents
- **LoadArtifactsTool** - Inject binary artifacts into context

## Installation

```toml
[dependencies]
adk-tool = "0.6.0"

# For remote MCP servers via HTTP:
adk-tool = { version = "0.6.0", features = ["http-transport"] }
```

Or use the meta-crate:

```toml
[dependencies]
adk-rust = { version = "0.6.0", features = ["tools"] }
```

## Quick Start

### Function Tool

```rust
use adk_tool::FunctionTool;
use adk_core::{ToolContext, Result};
use serde_json::{json, Value};
use std::sync::Arc;

async fn get_weather(_ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
    let city = args["city"].as_str().unwrap_or("Unknown");
    Ok(json!({
        "city": city,
        "temperature": 72,
        "condition": "sunny"
    }))
}

let tool = FunctionTool::new(
    "get_weather",
    "Get current weather for a city",
    get_weather,
);
```

### With Parameter Schema (Recommended)

Always add a schema so the LLM knows what parameters to pass:

```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(JsonSchema, Serialize, Deserialize)]
struct WeatherParams {
    /// The city to get weather for
    city: String,
}

let tool = FunctionTool::new("get_weather", "Get weather", get_weather)
    .with_parameters_schema::<WeatherParams>();
```

### Tool Metadata

Mark tools as read-only or concurrency-safe for smarter dispatch:

```rust
let lookup = FunctionTool::new("lookup", "Look up data", handler)
    .with_read_only(true)        // safe for concurrent dispatch in Auto mode
    .with_concurrency_safe(true);
```

### StatefulTool

Wrap shared state with a tool handler — the `Arc<S>` is cloned per invocation:

```rust
use adk_tool::StatefulTool;
use tokio::sync::RwLock;

struct Counter { count: RwLock<u64> }

let state = Arc::new(Counter { count: RwLock::new(0) });

let tool = StatefulTool::new("increment", "Increment counter", state, |s, _ctx, _args| async move {
    let mut count = s.count.write().await;
    *count += 1;
    Ok(json!({"count": *count}))
});
```

### SimpleToolContext

Call tools outside the agent loop (testing, MCP servers, sub-agent delegation):

```rust
use adk_tool::SimpleToolContext;

let ctx = SimpleToolContext::new("my-test-harness");
let result = my_tool.execute(Arc::new(ctx), json!({"key": "value"})).await?;
```

Defaults: `user_id()` → `"anonymous"`, `session_id()` → `""`, unique UUIDs for invocation and function call IDs.

### MCP Server Manager (Multi-Server Lifecycle)

Manage multiple MCP server processes with health monitoring, auto-restart, and tool aggregation:

```rust
use adk_tool::mcp::manager::{McpServerManager, McpServerConfig};
use std::collections::HashMap;
use std::time::Duration;

// Load from Kiro mcp.json format
let manager = McpServerManager::from_json(r#"{
    "mcpServers": {
        "playwright": {
            "command": "npx",
            "args": ["--yes", "@playwright/mcp@latest"],
            "autoApprove": ["browser_click"]
        },
        "filesystem": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
        }
    }
}"#)?
    .with_health_check_interval(Duration::from_secs(30))
    .with_grace_period(Duration::from_secs(5));

// Start all non-disabled servers
let results = manager.start_all().await;

// Use as a Toolset — tools from all servers are aggregated
// Name collisions are resolved with {server_id}__{tool_name} prefixes
let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .toolset(Arc::new(manager))
    .build()?;

// Dynamic management at runtime
manager.add_server("github".into(), github_config).await?;
manager.start_server("github").await?;
manager.remove_server("github").await?;

// Graceful shutdown
manager.shutdown().await?;
```

### MCP Tools (Local Server via stdio)

Connect to local MCP servers running as child processes:

```rust
use adk_tool::McpToolset;
use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;

// Connect to a local MCP server
let cmd = Command::new("npx")
    .arg("-y")
    .arg("@modelcontextprotocol/server-filesystem")
    .arg("/path/to/files");

let client = ().serve(TokioChildProcess::new(cmd)?).await?;

let toolset = McpToolset::new(client)
    .with_name("filesystem-tools")
    .with_filter(|name| matches!(name, "read_file" | "write_file"));

// Get cancellation token for graceful shutdown
let cancel_token = toolset.cancellation_token().await;

// ... use toolset with agent ...

// Cleanup before exit
cancel_token.cancel();
```

### MCP Tools (Remote Server via HTTP)

Connect to remote MCP servers using HTTP transport (requires `http-transport` feature):

```rust
use adk_tool::McpHttpClientBuilder;
use std::time::Duration;

// Connect to a public remote MCP server
let toolset = McpHttpClientBuilder::new("https://remote.mcpservers.org/fetch/mcp")
    .timeout(Duration::from_secs(30))
    .connect()
    .await?;
```

### MCP Authentication

Connect to authenticated MCP servers:

```rust
use adk_tool::{McpHttpClientBuilder, McpAuth, OAuth2Config};
use std::time::Duration;

// Bearer token (e.g., GitHub Copilot MCP)
let toolset = McpHttpClientBuilder::new("https://api.githubcopilot.com/mcp/")
    .with_auth(McpAuth::bearer(std::env::var("GITHUB_TOKEN")?))
    .timeout(Duration::from_secs(60))
    .connect()
    .await?;

// API key in custom header
let toolset = McpHttpClientBuilder::new("https://mcp.example.com/v1")
    .with_auth(McpAuth::api_key("X-API-Key", "your-api-key"))
    .connect()
    .await?;

// OAuth2 client credentials flow
let oauth_config = OAuth2Config::new(
    "your-client-id",
    "https://auth.example.com/oauth/token"
)
.with_secret("your-client-secret")
.with_scopes(vec!["mcp:read".into(), "mcp:write".into()]);

let toolset = McpHttpClientBuilder::new("https://mcp.example.com/v1")
    .with_auth(McpAuth::oauth2(oauth_config))
    .connect()
    .await?;
```

### MCP Task Support (Long-Running Operations)

Enable async task lifecycle for long-running MCP operations (SEP-1686):

```rust
use adk_tool::{McpToolset, McpTaskConfig};
use std::time::Duration;

let toolset = McpToolset::new(client)
    .with_task_support(
        McpTaskConfig::enabled()
            .poll_interval(Duration::from_secs(2))
            .timeout(Duration::from_secs(300))
            .max_poll_attempts(100)
    );
```

### MCP Auto-Reconnect (Connection Resilience)

For long-running agents, use `ConnectionRefresher` to automatically reconnect when connections fail:

```rust
use adk_tool::mcp::{ConnectionRefresher, ConnectionFactory, RefreshConfig};
use rmcp::{RoleClient, ServiceExt, service::RunningService, transport::TokioChildProcess};
use std::sync::Arc;
use tokio::process::Command;

// Define a factory that can create new connections
struct MyConnectionFactory {
    command: String,
    args: Vec<String>,
}

#[async_trait::async_trait]
impl<S> ConnectionFactory<S> for MyConnectionFactory
where
    S: rmcp::service::Service<RoleClient> + Send + Sync + 'static,
{
    async fn create_connection(&self) -> Result<RunningService<RoleClient, S>, String> {
        let cmd = Command::new(&self.command)
            .args(&self.args)
            .spawn()
            .map_err(|e| e.to_string())?;
        
        ().serve(TokioChildProcess::new(cmd).map_err(|e| e.to_string())?)
            .await
            .map_err(|e| e.to_string())
    }
}

// Create initial connection
let cmd = Command::new("npx")
    .arg("-y")
    .arg("@modelcontextprotocol/server-filesystem")
    .arg("/path/to/files");
let client = ().serve(TokioChildProcess::new(cmd)?).await?;

// Wrap with auto-reconnect
let factory = Arc::new(MyConnectionFactory {
    command: "npx".to_string(),
    args: vec!["-y".into(), "@modelcontextprotocol/server-filesystem".into(), "/path".into()],
});

let refresher = ConnectionRefresher::new(client, factory)
    .with_config(RefreshConfig::default()
        .with_max_attempts(5)
        .with_retry_delay_ms(2000));

// Operations automatically retry on connection failure
let tools = refresher.list_tools().await?;
if tools.reconnected {
    println!("Connection was refreshed during operation");
}
```

The refresher handles these error conditions automatically:
- Connection closed / EOF
- Broken pipe / transport errors
- Session not found (server restart)
- Connection reset

### Google Search

```rust
use adk_tool::GoogleSearchTool;

let search = GoogleSearchTool::new();
// Add to agent - enables grounded web search
```

## Features

| Feature | Description |
|---------|-------------|
| (default) | Local MCP servers via stdio transport |
| `http-transport` | Remote MCP servers via streamable HTTP |

## MCP Server Examples

### Available Public MCP Servers

- `https://remote.mcpservers.org/fetch/mcp` - Web content fetching
- `https://remote.mcpservers.org/sequentialthinking/mcp` - Step-by-step reasoning

### GitHub Copilot MCP (40+ tools)

```rust
// Requires GITHUB_TOKEN with Copilot access
let toolset = McpHttpClientBuilder::new("https://api.githubcopilot.com/mcp/")
    .with_auth(McpAuth::bearer(std::env::var("GITHUB_TOKEN")?))
    .connect()
    .await?;

// Discovered tools include:
// - search_repositories, search_code, search_issues
// - create_pull_request, merge_pull_request
// - get_file_contents, create_or_update_file
// - issue_read, issue_write, add_issue_comment
// - and 30+ more GitHub operations
```

## Toolset Composition

Compose, filter, and namespace toolsets for complex agent configurations:

```rust
use adk_tool::{BasicToolset, FilteredToolset, MergedToolset, PrefixedToolset, string_predicate};
use std::sync::Arc;

// Group tools into named toolsets
let weather = Arc::new(BasicToolset::new("weather", vec![get_weather, get_forecast]));
let utils = Arc::new(BasicToolset::new("utils", vec![search, calculate]));

// Filter: expose only specific tools from a toolset
let filtered = FilteredToolset::new(weather.clone(), string_predicate(vec!["get_weather".into()]));

// Or use a custom predicate
let custom = FilteredToolset::with_name(
    weather.clone(),
    Box::new(|tool| tool.name().starts_with("get_")),
    "get_only",
);

// Merge: combine multiple toolsets (first-wins deduplication)
let merged = MergedToolset::new("all_tools", vec![weather.clone(), utils.clone()]);

// Prefix: namespace tool names to avoid collisions
let prefixed = PrefixedToolset::new(weather.clone(), "wx"); // wx_get_weather, wx_get_forecast

// Chain them: prefix → filter → merge
let composed = MergedToolset::new("composed", vec![
    Arc::new(PrefixedToolset::new(weather, "wx")) as Arc<dyn Toolset>,
    Arc::new(FilteredToolset::new(utils, string_predicate(vec!["search".into()]))),
]);

// Register with an agent
let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .toolset(Arc::new(composed))
    .build()?;
```

All composition utilities implement `Toolset` and work with any `Toolset` implementation including `McpToolset` and `BrowserToolset`.

## Migration from rmcp 0.9

**No changes required!** The rmcp 0.14 breaking changes were handled internally:

| What Changed | Impact |
|--------------|--------|
| `CallToolRequestParam``CallToolRequestParams` | Internal only |
| Added `meta: None` field | Internal only |
| HTTP transport API | Internal only |

Your existing code using `McpToolset::new(client)` continues to work unchanged.

## Related Crates

- [adk-rust]https://crates.io/crates/adk-rust - Meta-crate with all components
- [adk-core]https://crates.io/crates/adk-core - Core `Tool` trait
- [adk-agent]https://crates.io/crates/adk-agent - Agents that use tools

## License

Apache-2.0

## Part of ADK-Rust

This crate is part of the [ADK-Rust](https://adk-rust.com) framework for building AI agents in Rust.