For concepts, see Tools.
Implement AgentToolDispatcher to define your own tools. The agent sees them alongside built-in and MCP tools.
struct MathTools;
#[async_trait]
impl AgentToolDispatcher for MathTools {
fn tools(&self) -> Arc<[Arc<ToolDef>]> {
vec![Arc::new(ToolDef {
name: "add".to_string(),
description: "Add two numbers".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
}),
})].into()
}
async fn dispatch(&self, call: ToolCallView<'_>) -> Result<ToolResult, ToolError> {
match call.name {
"add" => {
let args: AddArgs = call.parse_args()
.map_err(|e| ToolError::invalid_arguments("add", e.to_string()))?;
Ok(ToolResult::new(call.id.to_string(), format!("{}", args.a + args.b), false))
}
_ => Err(ToolError::not_found(call.name)),
}
}
}
Built-in tool categories: builtins (enable_builtins, including task tools plus utility helpers like wait, datetime, and apply_patch), shell (enable_shell), semantic memory (enable_memory), and mob orchestration (enable_mob).
CLI
JSON-RPC
REST
MCP
Python
TypeScript
Rust
# Enable builtins + shell
rkat run --enable-builtins --enable-shell "List files in /tmp"
# Short flags: -b for builtins, -x for shell (implies builtins)
rkat run -x "List files in /tmp"
# Enable mob orchestration tools (-M short flag)
rkat run -x --enable-mob "Coordinate the team"
rkat run -x -M "Coordinate the team"
{
"jsonrpc": "2.0", "id": 1,
"method": "session/create",
"params": {
"prompt": "List files in /tmp",
"enable_builtins": true,
"enable_shell": true,
"enable_memory": true,
"enable_mob": true
}
}
curl -X POST http://localhost:8080/sessions \
-H "Content-Type: application/json" \
-d '{
"prompt": "List files in /tmp",
"enable_builtins": true,
"enable_shell": true,
"enable_memory": true,
"enable_mob": true
}'
{
"prompt": "List files in /tmp",
"enable_builtins": true,
"builtin_config": {
"enable_shell": true,
"shell_timeout_secs": 30
},
"enable_memory": true,
"enable_mob": true
}
Called via the meerkat_run tool.session = await client.create_session(
"List files in /tmp",
enable_builtins=True,
enable_shell=True,
enable_memory=True,
enable_mob=True,
)
const session = await client.createSession("List files in /tmp", {
enableBuiltins: true,
enableShell: true,
enableMemory: true,
enableMob: true,
});
let factory = AgentFactory::new(root)
.builtins(true)
.shell(true)
.memory(true)
.mob(true);
let service = build_ephemeral_service(factory, config, 64); // Queue-only embedded/testing substrate
let result = service.create_session(CreateSessionRequest {
prompt: "List files in /tmp".into(),
..Default::default()
}).await?;
Register MCP servers
# Add a stdio server
rkat mcp add filesystem -- npx -y @anthropic/mcp-server-filesystem /tmp
# Add a streamable HTTP server
rkat mcp add remote-api --url https://mcp.example.com/api
# List all registered servers
rkat mcp list
rkat mcp list --scope project --json
# Get details for a specific server
rkat mcp get filesystem
# Remove a server
rkat mcp remove filesystem
# Reload MCP servers on a live session
rkat mcp reload --session <SESSION_ID> --live-server-url http://localhost:8080
rkat mcp reload --session <SESSION_ID> --live-server-url http://localhost:8080 --name my-tools
Config is stored in .rkat/mcp.toml (project) or ~/.rkat/mcp.toml (user). Project scope wins on name collisions.
Each server supports an optional connect_timeout_secs field (default: 10 seconds) covering the entire connect + handshake + tool enumeration budget:
# .rkat/mcp.toml
[[servers]]
name = "slow-server"
command = "npx"
args = ["-y", "@my/slow-mcp-server"]
connect_timeout_secs = 30
Live MCP controls
Add, remove, or reload MCP servers on a running session without restarting the agent. Changes are staged and applied at the next turn boundary.
CLI rkat mcp add/remove manages static server config (persisted to .rkat/mcp.toml). The methods below manage servers on a running session — changes are staged and applied at the next turn boundary.
JSON-RPC
REST
MCP
Python
TypeScript
{
"jsonrpc": "2.0", "id": 2,
"method": "mcp/add",
"params": {
"session_id": "01936f8a-...",
"server_name": "my-tools",
"server_config": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@my/mcp-server"]
},
"persisted": false
}
}
{
"jsonrpc": "2.0", "id": 3,
"method": "mcp/remove",
"params": {
"session_id": "01936f8a-...",
"server_name": "my-tools",
"persisted": false
}
}
{
"jsonrpc": "2.0", "id": 4,
"method": "mcp/reload",
"params": {
"session_id": "01936f8a-...",
"server_name": "my-tools"
}
}
# Add
curl -X POST http://localhost:8080/sessions/01936f8a-.../mcp/add \
-H "Content-Type: application/json" \
-d '{
"server_name": "my-tools",
"server_config": {"transport": "stdio", "command": "npx", "args": ["-y", "@my/mcp-server"]},
"persisted": false
}'
# Remove
curl -X POST http://localhost:8080/sessions/01936f8a-.../mcp/remove \
-H "Content-Type: application/json" \
-d '{"server_name": "my-tools", "persisted": false}'
# Reload
curl -X POST http://localhost:8080/sessions/01936f8a-.../mcp/reload \
-H "Content-Type: application/json" \
-d '{"server_name": "my-tools"}'
// Reload a specific server
{"name": "meerkat_mcp_reload", "arguments": {
"session_id": "01936f8a-...",
"server_name": "my-tools",
"persisted": false
}}
// Reload all servers
{"name": "meerkat_mcp_reload", "arguments": {
"session_id": "01936f8a-...",
"persisted": false
}}
await client.mcp_add(
session_id="01936f8a-...",
server_name="my-tools",
server_config={"transport": "stdio", "command": "npx", "args": ["-y", "@my/mcp-server"]},
persisted=False,
)
await client.mcp_remove(session_id="01936f8a-...", server_name="my-tools", persisted=False)
await client.mcp_reload(session_id="01936f8a-...", server_name="my-tools")
await client.mcpAdd({
sessionId: "01936f8a-...",
serverName: "my-tools",
serverConfig: { transport: "stdio", command: "npx", args: ["-y", "@my/mcp-server"] },
persisted: false,
});
await client.mcpRemove({ sessionId: "01936f8a-...", serverName: "my-tools", persisted: false });
await client.mcpReload({ sessionId: "01936f8a-...", serverName: "my-tools" });
Set persisted: true to write the change to disk config so it survives restart.
Tool visibility can change during a session. Changes are staged and atomically applied at the turn boundary — the LLM never sees a tool list change mid-stream.
External filters. Allow-list or deny-list filters are persisted in session metadata and survive resume.
Per-turn overlays. Mob flow steps can restrict tools for a single turn via TurnToolOverlay. The overlay is ephemeral and cleared after the turn completes.
CLI
JSON-RPC
REST
MCP
Python
TypeScript
# Allow-list specific tools for this turn
rkat resume --session 01936f8a-... \
--allow-tool shell --allow-tool read_file \
--block-tool task_create \
"Execute the next step"
{
"jsonrpc": "2.0", "id": 5,
"method": "turn/start",
"params": {
"session_id": "01936f8a-...",
"prompt": "Execute the next step",
"flow_tool_overlay": {
"allowed_tools": ["shell", "read_file"],
"blocked_tools": ["task_create"]
}
}
}
curl -X POST http://localhost:8080/sessions/01936f8a-.../messages \
-H "Content-Type: application/json" \
-d '{
"session_id": "01936f8a-...",
"prompt": "Execute the next step",
"flow_tool_overlay": {
"allowed_tools": ["shell", "read_file"],
"blocked_tools": ["task_create"]
}
}'
{"name": "meerkat_resume", "arguments": {
"session_id": "01936f8a-...",
"prompt": "Continue with restricted tools",
"flow_tool_overlay": {
"allowed_tools": ["shell", "task_create"],
"blocked_tools": []
}
}}
result = await session.turn(
"Execute the next step",
flow_tool_overlay={
"allowed_tools": ["shell", "read_file"],
"blocked_tools": ["task_create"],
},
)
const result = await session.turn("Execute the next step", {
flowToolOverlay: {
allowedTools: ["shell", "read_file"],
blockedTools: ["task_create"],
},
});
Composition rule: most-restrictive wins. Multiple allow-lists intersect, multiple deny-lists union, and deny always beats allow. The agent receives a [SYSTEM NOTICE] when its tool set changes, and a tool_config_changed event is emitted to the event stream.