---
title: "Tools & MCP"
description: "Custom tools, built-in tools, MCP server management, live MCP controls, and tool scoping."
icon: "wrench"
---

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: builtins, shell, semantic memory, and mob orchestration. On the CLI, these are selected with tool presets. Other surfaces expose per-category booleans such as `enable_builtins`, `enable_shell`, `enable_memory`, and `enable_mob`.

<Tabs>
  <Tab title="CLI">
    ```bash
    # 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
    {
      "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
    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
    {
      "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
    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
    const session = await client.createSession("List files in /tmp", {
      enableBuiltins: true,
      enableShell: true,
      enableMemory: true,
      enableMob: true,
    });
    ```
  </Tab>
  <Tab title="Rust">
    ```rust
    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: "claude-sonnet-4-6".into(),
        prompt: "List files in /tmp".into(),
        render_metadata: None,
        system_prompt: None,
        max_tokens: None,
        event_tx: None,
        skill_references: None,
        initial_turn: InitialTurnPolicy::RunImmediately,
        deferred_prompt_policy: DeferredPromptPolicy::Discard,
        build: None,
        labels: None,
    }).await?;
    ```
  </Tab>
</Tabs>

## Register MCP servers

```bash
# Add a stdio server
rkat mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp

# Add a streamable HTTP server
rkat mcp add remote-api --transport http --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

```

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
# .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
    {
      "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
    {
      "jsonrpc": "2.0", "id": 3,
      "method": "mcp/remove",
      "params": {
        "session_id": "01936f8a-...",
        "server_name": "my-tools",
        "persisted": false
      }
    }
    ```

    ```json
    {
      "jsonrpc": "2.0", "id": 4,
      "method": "mcp/reload",
      "params": {
        "session_id": "01936f8a-...",
        "server_name": "my-tools"
      }
    }
    ```
  </Tab>
  <Tab title="REST">
    ```bash
    # 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
    // 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
    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
    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
    # 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
    {
      "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"]
        }
      }
    }
    ```
  </Tab>
  <Tab title="REST">
    ```bash
    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"]
        }
      }'
    ```
  </Tab>
  <Tab title="MCP">
    ```json
    {"name": "meerkat_resume", "arguments": {
      "session_id": "01936f8a-...",
      "prompt": "Continue with restricted tools",
      "flow_tool_overlay": {
        "allowed_tools": ["shell", "task_create"],
        "blocked_tools": []
      }
    }}
    ```
  </Tab>
  <Tab title="Python">
    ```python
    result = await session.turn(
        "Execute the next step",
        flow_tool_overlay={
            "allowed_tools": ["shell", "read_file"],
            "blocked_tools": ["task_create"],
        },
    )
    ```
  </Tab>
  <Tab title="TypeScript">
    ```typescript
    const result = await session.turn("Execute the next step", {
      flowToolOverlay: {
        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
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<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)
