# Tools

> Tools on FlyMy.AI agents: browse the MCP catalog, configure OAuth or API-key tools, attach them to your agent, and add custom MCP servers.

Tools give agents the ability to act -- search the web, browse pages, query APIs, and connect to external services. Tools are managed separately from agents and attached by their integer IDs.

## How Tools Work

The tool workflow has four steps:

```
Browse Catalog  →  Add Tool  →  Configure  →  Attach to Agent
```

1. **Browse the catalog** to see what tools are available
2. **Add a tool** from the catalog to your account
3. **Configure it** with any required credentials or settings
4. **Attach it** to one or more agents by passing its integer ID

## Tool Catalog

The catalog lists all tools available on the platform. Each entry has a name, type, title, description, and categories.

**Python**

```python
from flymyai import AgentClient

client = AgentClient(api_key="fly-***")

catalog = client.tools.available()

for tool in catalog:
    print(f"{tool.name} ({tool.type})")
    print(f"  {tool.title}: {tool.description}")
    print(f"  Categories: {tool.categories}")
```

**JavaScript**

```javascript
import { AgentClient } from "flymyai-js-client";

const client = new AgentClient({ apiKey: "fly-***" });

const catalog = await client.tools.available();

catalog.forEach(tool => {
    console.log(`${tool.name} (${tool.type})`);
    console.log(`  ${tool.title}: ${tool.description}`);
    console.log(`  Categories: ${tool.categories}`);
});
```

### AvailableTool Fields

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Identifier used when creating the tool (e.g. `"tavily"`) |
| `type` | string | Tool type |
| `title` | string | Human-readable display name |
| `description` | string | What the tool does |
| `categories` | list | Groupings for browsing (e.g. search, browser, data) |

## Your Connected Tools

`tools.available()` lists the *catalog* (everything you could add). `tools.list()`
lists the tools **you have already connected** on your account, each with the
integer `id` you need to call it or attach it to an agent. Use this to reuse an
existing connection - for example a Sentry or Slack tool you connected earlier -
instead of creating a duplicate.

**Python**

```python
for tool in client.tools.list():
    print(tool.id, tool.mcp_tool, "configured" if tool.is_configured else "needs setup")
# 2652 sentry     configured
# 101  github-mcp configured
# 11   tavily     configured
```

**JavaScript**

```javascript
for (const tool of await client.tools.list()) {
    console.log(tool.id, tool.mcp_tool, tool.isConfigured ? "configured" : "needs setup");
}
```

## Adding a Tool

Create a tool instance from the catalog using its `name` (passed as `mcp_tool`). This returns a `Tool` object with an integer `.id`.

**Python**

```python
tool = client.tools.create(mcp_tool="tavily")

print(f"Tool ID: {tool.id}")            # integer ID
print(f"MCP Tool: {tool.mcp_tool}")     # "tavily"
print(f"Configured: {tool.is_configured}")
```

**JavaScript**

```javascript
const tool = await client.tools.create({ mcpTool: "tavily" });

console.log(`Tool ID: ${tool.id}`);
console.log(`MCP Tool: ${tool.mcp_tool}`);
console.log(`Configured: ${tool.isConfigured}`);
```

### Tool Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | int | Unique identifier used when attaching to agents |
| `mcp_tool` | string | The catalog tool name |
| `is_configured` | bool | Whether all required configuration has been provided |
| `next_configuration_step` | dict or None | Describes the next required configuration input, if any |

## Configuring a Tool

Some tools require configuration (API keys, credentials, or other settings). When `is_configured` is `false`, inspect `next_configuration_step` and provide the required values.

**Python**

```python
tool = client.tools.create(mcp_tool="tavily")

# Check what configuration is needed
if not tool.is_configured:
    print(f"Next step: {tool.next_configuration_step}")

    # Provide the required configuration
    tool = client.tools.provide_config(
        tool.id,
        user_response={"TAVILY_API_KEY": "tvly-***"}
    )

    print(f"Now configured: {tool.is_configured}")
```

**JavaScript**

```javascript
let tool = await client.tools.create({ mcpTool: "tavily" });

if (!tool.isConfigured) {
    console.log("Next step:", tool.nextConfigurationStep);

    tool = await client.tools.provideConfig(tool.id, {
        userResponse: { TAVILY_API_KEY: "tvly-***" }
    });

    console.log(`Now configured: ${tool.isConfigured}`);
}
```

> **Note: Multi-step configuration**
>
> Some tools may require multiple configuration steps. After each call to `provide_config`, check `is_configured` and `next_configuration_step` again. Repeat until `is_configured` is `true`.

## Attaching Tools to Agents

Pass tool IDs as a list of integers when creating or updating an agent:

**Python**

```python
# Create tools
tavily = client.tools.create(mcp_tool="tavily")
tavily = client.tools.provide_config(tavily.id, user_response={"TAVILY_API_KEY": "tvly-***"})

browser = client.tools.create(mcp_tool="browser")

# Create agent with tools attached
agent = client.agents.create(
    name="Research Assistant",
    goal="Research the given topic and produce a detailed summary with sources.",
    tools=[tavily.id, browser.id]
)

# Or update an existing agent's tools
client.agents.update(
    agent_id=agent.id,
    tools=[tavily.id, browser.id]
)
```

**JavaScript**

```javascript
// Create tools
let tavily = await client.tools.create({ mcpTool: "tavily" });
tavily = await client.tools.provideConfig(tavily.id, {
    userResponse: { TAVILY_API_KEY: "tvly-***" }
});

const browser = await client.tools.create({ mcpTool: "browser" });

// Create agent with tools attached
const agent = await client.agents.create({
    name: "Research Assistant",
    goal: "Research the given topic and produce a detailed summary with sources.",
    tools: [tavily.id, browser.id]
});
```

## Calling Tools Directly

You can invoke a tool directly without an agent, which is useful for testing or one-off operations:

**Python**

```python
result = client.tools.call(
    tool.id,
    action="search",
    arguments={"query": "latest AI research papers"}
)

print(result)
```

**JavaScript**

```javascript
const result = await client.tools.call(tool.id, {
    action: "search",
    arguments: { query: "latest AI research papers" }
});

console.log(result);
```

> **Note: Action names and OAuth connectors**
>
> `action` is the connector's own MCP action - for example the Sentry connector
> exposes `find_organizations`, `get_issue_details`, `search_issues`; Tavily
> exposes `search`. Browse a connector's page under [/mcp](https://docs.flymy.ai/mcp.md) for its verbs, or
> discover them at runtime from the connector itself.
>
> For OAuth connectors (Sentry, Slack, Linear, Google, ...), connect the tool
> **once** in app.flymy.ai before calling - until then `is_configured` is `false`
> and `tools.call` returns an error asking you to finish setup. Expired access
> tokens are refreshed automatically on the next call.

### Safe retries with Idempotency-Key

`tools.call` maps to `POST /api/v1/agents/custom-tools/call/`. A network blip makes a
raw POST unsafe to blindly retry - the tool could run twice (a duplicate message, a
double charge). Every non-retryable action therefore requires a caller-owned
**`Idempotency-Key`** header: a string of 1-255 printable ASCII characters with no
leading or trailing spaces (internal spaces are allowed). No client, SDK, or server
generates a fallback key, and the accepted value is stored byte-for-byte:

- The **first** request with a given key reserves it, runs the tool, and stores its
  `200` response.
- A **retry** with the **same key and the same body** replays that stored `200`
  **without re-running the tool**.
- The **same key with a different body** returns **`409 Conflict`**.
- A **typed pre-effect rejection** (validation, authorization, a connection that
  needs setup) releases the key, so the corrected call may reuse it.
- A **post-dispatch unknown outcome** (timeout, lost response, process loss) is
  persisted as terminal: the same key returns `409` with `tool_outcome_unknown`
  and the tool is never re-run automatically. Inspect the provider, then use a new
  key for a new intentional operation.

Use one key per logical operation - e.g. a UUID you generate before the first send.

```bash
curl -X POST https://backend.flymy.ai/api/v1/agents/custom-tools/call/ \
  -H "X-API-KEY: fly-***" \
  -H "Idempotency-Key: 5f9c1e6a-2b0d-4c3a-9e77-1a2b3c4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{"tool": "tavily", "action": "search", "arguments": {"query": "..."}}'
```

> **Note: SDK support**
>
> The Python and JS SDKs will accept `idempotency_key=...` on `tools.call` in an
> upcoming release; today, pass the header through the raw endpoint as shown above.

## Complete Example

End-to-end flow: browse catalog, add tools, configure, create agent, run it.

```python
from flymyai import AgentClient

client = AgentClient(api_key="fly-***")

# 1. Browse the catalog
catalog = client.tools.available()
for t in catalog:
    print(f"  {t.name}: {t.description}")

# 2. Add tools
tavily = client.tools.create(mcp_tool="tavily")
browser = client.tools.create(mcp_tool="browser")

# 3. Configure tools that need it
tavily = client.tools.provide_config(
    tavily.id,
    user_response={"TAVILY_API_KEY": "tvly-***"}
)

# 4. Create an agent with tools attached
agent = client.agents.create(
    name="Market Researcher",
    goal="Research current market trends and produce a structured analysis.",
    tools=[tavily.id, browser.id],
    status="active"
)

# 5. Run the agent
run = client.runs.create(agent_id=agent.id)
result = client.runs.wait(run.id, timeout=300)

print(f"Status: {result.status}")
print(f"Output: {result.output}")
```

## Next Steps

- **[Agents](https://docs.flymy.ai/agents/concepts/agents.md)** -- create agents and attach tools
- **[Runs](https://docs.flymy.ai/agents/concepts/runs.md)** -- execute agents and monitor progress
