> ## 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.

# Tools & MCP

> Custom tools, built-in tools, MCP server management, live MCP controls, and tool scoping.

For concepts, see [Tools](/concepts/tools).

<Note>
  If you are new to the tools system, start with **Enable built-in tools**, then **Register MCP servers**, then come back to **Custom tools (Rust SDK)** when you need to extend the runtime directly.
</Note>

## Start here

This page is structured from common product usage to advanced extension work:

1. enable built-in tools
2. register MCP servers
3. manage live MCP controls and tool scoping
4. implement custom Rust tools when you need to extend the runtime directly

## Enable built-in tools

Built-in tool categories include general builtins, shell, semantic memory,
schedule, WorkGraph, comms, mob orchestration, image generation, and web
search. On the CLI, common groups are selected with tool presets. Other
surfaces expose category booleans such as `enable_builtins`, `enable_shell`,
`enable_memory`, `enable_workgraph`, `enable_mob`, and `enable_web_search`.
Image generation and web search are independent from general builtins.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    # Enable builtins + shell
    rkat run --tools workspace "List files in /tmp"

    # Short flag form
    rkat run -t workspace "List files in /tmp"

    # Enable mob orchestration too
    rkat run --tools full "Coordinate the team"
    rkat run --yolo "Coordinate the team"

    ```
  </Tab>

  <Tab title="JSON-RPC">
    ```json theme={null}
    {
      "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
      }
    }
    ```
  </Tab>

  <Tab title="REST">
    ```bash theme={null}
    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
      }'
    ```
  </Tab>

  <Tab title="MCP">
    ```json theme={null}
    {
      "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.
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    session = await client.create_session(
        "List files in /tmp",
        enable_builtins=True,
        enable_shell=True,
        enable_memory=True,
        enable_mob=True,
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const session = await client.createSession("List files in /tmp", {
      enableBuiltins: true,
      enableShell: true,
      enableMemory: true,
      enableMob: true,
    });
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    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 {
        model: "gpt-5.6-sol".into(),
        prompt: "List files in /tmp".into(),
        system_prompt: SystemPromptOverride::Inherit,
        max_tokens: None,
        event_tx: None,
        initial_turn: InitialTurnPolicy::RunImmediately,
        deferred_prompt_policy: DeferredPromptPolicy::Discard,
        build: None,
        labels: None,
    }).await?;
    ```
  </Tab>
</Tabs>

## Run a durable background shell job

Ask the `shell` tool for `background: true` when you need Meerkat-owned job
truth rather than shell syntax that happens to include `&`:

```json theme={null}
{
  "command": "./scripts/long-check.sh",
  "working_dir": ".",
  "timeout_secs": 900,
  "background": true
}
```

The submission returns before the process completes:

```json theme={null}
{
  "job_id": "job_019467d9-7e3a-7000-8000-000000000000",
  "status": "queued",
  "restart_class": "non_resumable",
  "awaitable": true,
  "message": "Durable shell job submitted with ID: job_019467d9-7e3a-7000-8000-000000000000"
}
```

Use `shell_job_status`, `shell_jobs`, and `shell_job_cancel` with that stable
ID. The record survives restart, but the subprocess does not become resumable:
a lost worker is terminally classified `worker_lost` and is not replayed.

`monitor_start` is a separate high-trust, opt-in tool for scripts that emit
framed notifications, checkpoints, progress, and completion. It is not enabled
by the standard shell category alone.

## Register MCP servers

```bash theme={null}
# Add a stdio server
rkat mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp

# Add a streamable HTTP server
rkat mcp add remote-api --url https://mcp.example.com/api

# Add an OAuth-protected streamable HTTP server and log in
rkat mcp add --transport http knowledge https://mcp.example.com/v1
rkat mcp login knowledge

# 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

```

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:

```toml theme={null}
# .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.

<Note>
  CLI `rkat mcp add/remove/list/get` manages server config (persisted to `.rkat/mcp.toml` or `~/.rkat/mcp.toml`). The methods below manage servers on a **running session** — changes are staged and applied at the next turn boundary.
</Note>

<Tabs>
  <Tab title="JSON-RPC">
    ```json theme={null}
    {
      "jsonrpc": "2.0", "id": 2,
      "method": "mcp/add",
      "params": {
        "session_id": "01936f8a-...",
        "server_config": {
          "name": "my-tools",
          "command": "npx",
          "args": ["-y", "@my/mcp-server"]
        },
        "persisted": false
      }
    }
    ```

    ```json theme={null}
    {
      "jsonrpc": "2.0", "id": 3,
      "method": "mcp/remove",
      "params": {
        "session_id": "01936f8a-...",
        "server_name": "my-tools",
        "persisted": false
      }
    }
    ```

    ```json theme={null}
    {
      "jsonrpc": "2.0", "id": 4,
      "method": "mcp/reload",
      "params": {
        "session_id": "01936f8a-...",
        "server_name": "my-tools"
      }
    }
    ```
  </Tab>

  <Tab title="REST">
    ```bash theme={null}
    # Add
    curl -X POST http://localhost:8080/sessions/01936f8a-.../mcp/add \
      -H "Content-Type: application/json" \
      -d '{
        "server_config": {"name": "my-tools", "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"}'
    ```
  </Tab>

  <Tab title="MCP">
    ```json theme={null}
    // 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
    }}
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    await client.mcp_add(
        session_id="01936f8a-...",
        server_config={"name": "my-tools", "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")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await client.mcpAdd({
      session_id: "01936f8a-...",
      server_config: { name: "my-tools", command: "npx", args: ["-y", "@my/mcp-server"] },
      persisted: false,
    });

    await client.mcpRemove({ session_id: "01936f8a-...", server_name: "my-tools", persisted: false });
    await client.mcpReload({ session_id: "01936f8a-...", server_name: "my-tools" });
    ```
  </Tab>
</Tabs>

Set `persisted: true` to write the change to disk config so it survives restart.

## Tool scoping

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.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    # Allow-list specific tools for this turn
    rkat run --resume 01936f8a-... \
      --allow-tool shell --allow-tool read_file \
      --block-tool task_create \
      "Execute the next step"
    ```
  </Tab>

  <Tab title="JSON-RPC">
    ```json theme={null}
    {
      "jsonrpc": "2.0", "id": 5,
      "method": "turn/start",
      "params": {
        "session_id": "01936f8a-...",
        "prompt": "Execute the next step",
        "turn_tool_overlay": {
          "allowed_tools": ["shell", "read_file"],
          "blocked_tools": ["task_create"]
        }
      }
    }
    ```
  </Tab>

  <Tab title="REST">
    ```bash theme={null}
    curl -X POST http://localhost:8080/sessions/01936f8a-.../messages \
      -H "Content-Type: application/json" \
      -d '{
        "session_id": "01936f8a-...",
        "prompt": "Execute the next step",
        "turn_tool_overlay": {
          "allowed_tools": ["shell", "read_file"],
          "blocked_tools": ["task_create"]
        }
      }'
    ```
  </Tab>

  <Tab title="MCP">
    ```json theme={null}
    {"name": "meerkat_resume", "arguments": {
      "session_id": "01936f8a-...",
      "prompt": "Continue with restricted tools",
      "turn_tool_overlay": {
        "allowed_tools": ["shell", "task_create"],
        "blocked_tools": []
      }
    }}
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    result = await session.turn(
        "Execute the next step",
        turn_tool_overlay={
            "allowed_tools": ["shell", "read_file"],
            "blocked_tools": ["task_create"],
        },
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const result = await session.turn("Execute the next step", {
      turnToolOverlay: {
        allowedTools: ["shell", "read_file"],
        blockedTools: ["task_create"],
      },
    });
    ```
  </Tab>
</Tabs>

**Composition rule:** most-restrictive wins. Multiple allow-lists intersect, multiple deny-lists union, and deny always beats allow. The conversation records a typed `tool_config` system notice when the tool set changes, and a `tool_config_changed` event is emitted to the event stream.

## Advanced: Custom tools (Rust SDK)

Implement `AgentToolDispatcher` to define your own tools. The agent sees them alongside built-in and MCP tools.

```rust theme={null}
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"]
            }),
            provenance: Some(ToolProvenance {
                kind: ToolSourceKind::RustBundle,
                source_id: "math-tools".into(),
            }),
        })].into()
    }

    fn tool_mutation_class(&self, tool_name: &str) -> ToolMutationClass {
        if tool_name == "add" {
            ToolMutationClass::ReadOnly
        } else {
            ToolMutationClass::Unknown
        }
    }

    async fn dispatch(&self, call: ToolCallView<'_>) -> Result<ToolDispatchOutcome, 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).into())
            }
            _ => Err(ToolError::not_found(call.name)),
        }
    }
}
```

## Next step

* [Examples: Skills](/examples/skills)
* [Examples: Memory](/examples/memory)
* [Examples: Comms](/examples/comms)
