> For the complete documentation index, see [llms.txt](https://docs.interactive.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.interactive.ai/agents/guides/connecting-tools.md).

# Connecting tools

> **Context** — Assumes the [Tools](/agents/concepts/tools.md) concept page. This guide covers the practical side: building a server, wiring it in, authentication, and — most importantly — designing tools a language model uses well.
>
> YAML examples follow **manifest schema 6.1.1**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning.md).

## 1. Build an MCP server

The agent consumes tools over the Model Context Protocol with the `streamable-http` transport. Any MCP-compliant server works; in Python, `fastmcp` is the shortest path:

```bash
pip install fastmcp
```

```python
"""CRM tool server example."""

from fastmcp import FastMCP

mcp = FastMCP("crm")


@mcp.tool
def get_account_status(party_id: str) -> dict:
    """Fetch the current account status for a customer.

    Args:
        party_id: The customer's PARTYID exactly as provided by
            authentication — do not transform it.

    Returns:
        {"status": "ACTIVE" | "SUSPENDED" | "CLOSED",
         "since": "YYYY-MM-DD",
         "reason": str | None}
    """
    record = lookup_account(party_id)
    return {
        "status": record.status,
        "since": record.status_since.isoformat(),
        "reason": record.status_reason,
    }


if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8001)
```

Requirements on your side:

* **Transport:** `streamable-http` only.
* **Reachability:** the platform-hosted agent must be able to reach the `hostname:port` your manifest declares — a public URL, or an address your platform networking allows. The agent connects out to your server; it never needs to reach in.
* **Statelessness between calls** is your design choice; the agent passes whatever parameters the tool declares, nothing more.

## 2. Declare it in the manifest

```yaml
agent_config:
  mcps:
    - id: crm
      hostname: https://crm-tools.internal.example.com
      port: 443
      transport: streamable-http
      api_key: ${CRM_MCP_KEY}
```

* `id` becomes the namespace: this server's `get_account_status` is referenced everywhere as `crm:get_account_status`.
* `api_key` (optional) is a `${VAR}` env-ref; the agent sends it on every MCP request as `Authorization: Bearer`. Your server should verify it.
* The agent connects at **boot** and fails startup if the server is unreachable; at runtime, transient failures reconnect automatically.

## 3. Designing tools the agent calls well

The model sees three things: the tool's **name**, its **description**, and its **parameter schema** (names, types, defaults, per-parameter docs). It decides when and how to call based on those alone. Treat them as a prompt:

**Name by intent, verb-first.** `search_cars`, `create_booking`, `initiate_human_handoff` — not `cars_api_v2` or `do_action`.

**Document the contract, not the implementation.** Say what goes in, what comes out, and the gotchas:

```python
@mcp.tool
def create_booking(
    car_id: str,
    pickup_location: str,
    pickup_date: str,
    return_date: str,
    customer_name: str,
    driver_age: int,
    extras: list[str] | None = None,
    member_email: str | None = None,
    return_location: str | None = None,
) -> dict:
    """Create a car rental booking.

    Args:
        car_id: id from search_cars results (e.g. "suv-1").
        pickup_location: free-text location as the customer gave it.
        pickup_date: YYYY-MM-DD.
        return_date: YYYY-MM-DD; must be after pickup_date.
        customer_name: full name for the booking.
        driver_age: primary driver's age in years; must be >= 21.
        extras: optional list of extra_ids from list_extras.
        member_email: only when a member lookup succeeded; lowercase.
        return_location: only when dropping off somewhere else.

    Returns:
        On success: {"booking_id": str, "total_eur": int, ...}.
        On failure: {"error": str} with a human-readable reason
        (e.g. "driver_age below minimum of 21").
    """
```

**Return errors as data, not exceptions.** A structured `{"error": "driver_age below minimum of 21"}` lands in the conversation history where the model can read it, explain it, and recover. An exception gives it nothing to work with.

**Keep results lean and labelled.** The entire result enters the model's context. Return the fields the agent needs, with self-explanatory names and enum-like values (`"ACTIVE"`, not `2`). Page or summarise large sets — "the cheapest 5 of 23 matches" is a better tool result than 23 rows.

**Make state-changing tools idempotent where possible.** The model may retry after ambiguous failures; accepting an idempotency parameter or deduplicating server-side prevents double bookings.

**One capability per tool.** A `manage_booking(action=...)` multiplexer forces the model to learn your switch statement. Separate `cancel_booking`, `update_booking_dates`, `get_booking` tools each carry their own focused documentation.

## 4. The other half: tool use in content

The server defines what *can* be called; routines and policies define *when and how*:

* A routine [tool node](/agents/guides/authoring-routines.md#tool-nodes-instructions-are-parameter-maps) pairs `tools: crm:get_account_status` with a `tool_instruction` explaining parameter derivation in this flow's context.
* A [policy](/agents/guides/authoring-policies.md) lists `tools:` it may invoke from its action.
* If a tool changes what the engine should believe (authentication, status changes), register it in `context.reevaluation_tools` or the policy's `reevaluate_after` — see [Reevaluation tools](/agents/concepts/tools.md#reevaluation-tools).

Tool descriptions answer "how do I call this correctly?"; instructions answer "why now, with which values from this conversation?". Don't duplicate one into the other.

## 5. Verify the wiring

1. Boot the agent; startup fails fast with the server name if an MCP server is unreachable.
2. Send a message that should trigger the tool; watch the event stream for the `tool` event carrying `tool_id`, `arguments`, and `result` (`isinstance(ev, ToolEvent)` in the SDK).
3. Check the trace: each tool call appears with its arguments and result — see [Observability](/agents/guides/observability.md).
4. Test the failure path: stop your MCP server mid-conversation and confirm the agent degrades the way you want (the failed call is visible to the model, which explains per its instructions).

## Checklist

* [ ] `streamable-http` transport; reachable from the agent's network
* [ ] `api_key` verification on the server if it's not network-isolated
* [ ] Verb-first names; contract-grade docstrings; typed parameters
* [ ] Errors returned as structured data
* [ ] Results lean, labelled, paged
* [ ] State-changing tools idempotent
* [ ] Reevaluation registered for state-flipping tools


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.interactive.ai/agents/guides/connecting-tools.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
