---
title: "Connectors as Tools"
description: "An agent can call official Connector operations as tools. Each call runs in its own tool Step, with the Connector's credentials and idempotency key."
canonical_url: "https://docs.superdurable.io/agent-frameworks/connectors-as-tools/"
source: "docs/content/agent-frameworks/connectors-as-tools.mdx"
locale: "en"
framework: "claude-agent-sdk"
language: "python"
---

> Index of every page for AI agents: https://docs.superdurable.io/llms.txt

# Connectors as Tools

This file shows the Claude Agent SDK version. Other frameworks: [Google ADK](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.google-adk.md) · [LangChain and LangGraph](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.langchain-langgraph.md) · [Mastra](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.mastra.md) · [OpenAI Agents SDK](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.openai-agents-sdk.md) · [Pydantic AI](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.pydantic-ai.md) · [Vercel AI SDK](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.vercel-ai-sdk.md) · [Built-in agent loop](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.built-in-agent-loop.md)

Code on this page is in Python. Other languages: [TypeScript](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.typescript.md)

An agent can call official [Connector](https://docs.superdurable.io/concepts/connector.md) operations as tools. Each call runs in its own tool Step, with the Connector's credentials and idempotency key.

<a id="connectors-as-tools"></a>

## How it works

`ConnectorToolSource` offers the operations you name, and the agent takes it as a tool source (`tool_sources` in Python, `toolSources` in TypeScript). Each call runs one operation in its own tool Step, so the Call ID, the idempotency key, and credentials work as in a Connector Step. The Call ID stays the same on every attempt.

The Connect a Connector section shows the wiring for each framework; the [Built-in agent loop](https://docs.superdurable.io/agent-frameworks/built-in-agent-loop.md#connect-connectors) shows it without one. The [Connector tools reference](https://docs.superdurable.io/reference/agents/connector-tools.md) covers packages, tool names, result size, and connections.

<a id="connector-tools-offered"></a>

### Which operations are offered

The application names each operation an agent may use, and the Connector's manifest, `dex-connector.json`, must mark it as exposed for agents. Dex refuses any other operation when the agent is defined.

Dex checks the model's arguments against the operation's input schema first. A wrong or missing field fails the call, before the operation runs, with a message the model can correct.

<a id="connector-tools-effects"></a>

### Effect classes

The manifest decides whether a call can run again, and so which Step runs it:

| Operation                                                           | Effect class           | Runs in                       |
| ------------------------------------------------------------------- | ---------------------- | ----------------------------- |
| Query                                                               | **read**               | `CallTool`, with retries      |
| Mutation that the manifest marks retry-safe (**agent.retry: safe**) | `idempotent_write`     | `CallTool`, with retries      |
| Any other Mutation                                                  | `non_idempotent_write` | `CallSingleAttemptTool`, once |

Retry-safe means a repeat with the same Call ID makes no second effect. A call whose attempts run out records **failed** for a Query and `outcome_unknown` for a Mutation.

A session's tool policy for a Connector tool changes only its approval. The effect class always comes from the manifest.

<a id="connector-tools-approval"></a>

### Approval

By default, a Connector tool waits for approval when its manifest recommends it (**agent.approval: recommended**), as for a refund, and other Mutations run directly. The application can override that for each tool, or pass a predicate that decides per call from its arguments, such as refunds above a limit. A predicate that raises asks for approval.

<a id="connector-tools-outcomes"></a>

### Outcomes

The operation ends in one of its branches, and the branch decides the tool call's record: **succeeded**, **failed**, or `outcome_unknown` when a write may have taken effect. [Outcomes](https://docs.superdurable.io/reference/agents/connector-tools.md#outcomes) lists every branch and its record.

Reconcile `outcome_unknown` as for any tool; see [Reconcile outcome_unknown](https://docs.superdurable.io/agent-frameworks/tool-calls.md#reconcile-outcome-unknown).

<a id="connector-tools-write-order"></a>

### Write order

Connector writes of one model response run one at a time, in the model's order. Reads, and calls of other tools, run in parallel. A write that needs approval asks for it only when its turn comes.

When a write fails, ends `outcome_unknown`, or is rejected, the later Connector writes of that response are skipped. Each records **failed** with **skipped** set and an error that starts "the tool did not run, so it is safe to call again", and Dex Web shows it as skipped.

<a id="connector-tools-crashes"></a>

### Crashes

A Worker crash during a Connector write sends the write at most once. A retry-safe write retries with the same idempotency key, or looks for the earlier write at the provider first; a single-attempt write records `outcome_unknown`. [Crashes](https://docs.superdurable.io/reference/agents/connector-tools.md#crashes) shows each case.

<a id="connect-connectors"></a>

## Connect a Connector

The example adds the Slack tool `slack_post_thread_reply`, which replies in a Slack thread.

<a id="slack-connector-tool"></a>

### The Slack tool in the example

With the Slack connection **support-slack** configured, each example agent gets `slack_post_thread_reply`, the Slack operation `postThreadReply`. The Worker opens the connection from `DEX_CONNECTOR_CONFIG_FILE` locally, or from the **DEX_PROJECT\_\*** settings when deployed; [Connections](https://docs.superdurable.io/reference/agents/connector-tools.md#connector-tools-connection) covers both, and [the Slack tool in the examples](https://docs.superdurable.io/reference/agents/connector-tools.md#slack-example) covers the Slack app.

`postThreadReply` is a single-attempt Mutation, so a post runs once, in `CallSingleAttemptTool`, and a Worker crash never sends it twice. The example also sets `requires_approval=True` (**requiresApproval: true** in TypeScript), so every post waits in `AwaitToolApproval` for a person.

<a id="offer-connector-tools"></a>

### Offer the tool

**Python**

Install the **connectors** extra of **dex-agents** and the Connector package, such as **dex-connector-slack**. The install commands are in [Connector tools](https://docs.superdurable.io/reference/agents/connector-tools.md#install).

The Worker opens the Slack connection **support-slack** from the project configuration. `connector_tool` turns the operation into a tool, and `ConnectorToolSource` holds it. Without the connection, the agent gets no Slack tool.

```python
def build_slack_tool_sources(connection: slack.Connection | None) -> list[ToolSource]:
    """Returns the Slack tool source, or no source when Slack is not configured."""
    if connection is None:
        return []
    return [
        ConnectorToolSource([
            # The manifest does not ask for approval; this agent asks before every post.
            connector_tool(
                connection.post_thread_reply(), connection.reference, requires_approval=True
            ),
        ]),
    ]

# ...

async def open_slack_project_connection(
    environment: Mapping[str, str] = os.environ,
) -> SlackProjectConnection | None:
    # ...
    # An incomplete or invalid configuration raises here, so the Worker fails at startup.
    try:
        project = await load_from_environment(environment)
    except FileNotFoundError:
        _log_slack_not_set_up("the connector configuration file does not exist")
        return None
    # ...
    try:
        connection = slack.new_project_connection(project, SLACK_CONNECTION_NAME)
    except BaseException:
        await project.close()
        raise
    return SlackProjectConnection(project, connection)
```

Example: [examples/python/agent-harnesses/agent_harness_examples/slack_connector_tools.py](https://github.com/superdurable/dex/tree/main/examples/python/agent-harnesses/agent_harness_examples/slack_connector_tools.py)

The Worker opens the connection once, at startup, and every agent uses it:

**Python**

```python
async def main() -> None:
    config = AgentHarnessExamplesConfig.from_env()
    slack_setup = await open_slack_project_connection()
    try:
        app_state = AgentHarnessExamplesApp(config, slack_setup.connection if slack_setup else None)
        # ...
    finally:
        if slack_setup is not None:
            await slack_setup.aclose()
```

Example: [examples/python/agent-harnesses/agent_harness_examples/\_\_main\_\_.py](https://github.com/superdurable/dex/tree/main/examples/python/agent-harnesses/agent_harness_examples/__main__.py)

The agent passes the tool source next to its own tools:

**Python**

```python
    return claude_agent_session_flow(
        build_options,
        [look_up_order, cancel_order],
        flow_type=CLAUDE_AGENT_SDK_ORDER_SUPPORT_FLOW_TYPE,
        tool_sources=build_slack_tool_sources(slack_connection),
        # ...
    )
```

Example: [examples/python/agent-harnesses/agent_harness_examples/claude_agent_sdk_order_support.py](https://github.com/superdurable/dex/tree/main/examples/python/agent-harnesses/agent_harness_examples/claude_agent_sdk_order_support.py)

<a id="connector-tool-calls"></a>

### How a call runs

Each call of `slack_post_thread_reply` runs once, in its own `CallSingleAttemptTool` Step, after a person approves it, as [the Slack tool](https://docs.superdurable.io/agent-frameworks/connectors-as-tools.md#slack-connector-tool) explains. Your framework hands the call to that Step this way:

In the Claude Agent SDK, a Connector tool call never runs inside the CLI, not even a read. Every call ends the segment and runs in its own tool Step, because the Connector needs that Step for its Call ID. The resumed CLI then gets the recorded result. The CLI sees the tool as `mcp__tools__slack_post_thread_reply`; tool policies, approvals, and records use `slack_post_thread_reply`.

One call of a model response at a time leaves the turn to run in its tool Step. The adapter refuses a later write of that response inside the turn, so it never runs and leaves no record; the model reads the earlier write's result and then decides whether to call the later write again. Keep Connector tools out of `allowed_tools` (`allowedTools`): their calls must leave the CLI, so such an entry fails the `RunModel` attempt.

<a id="configure-connector-connection"></a>

### Configure the connection

To try it, start the example with `DEX_CONNECTOR_CONFIG_FILE` set. Then ask the agent to reply in a Slack thread, with the channel ID and the thread timestamp:

```text
Reply in Slack thread C0123456789 1710000000.000100: Order 1002 ships on Thursday.
```

The post waits for approval, like `cancel_order`.

[Connections](https://docs.superdurable.io/reference/agents/connector-tools.md#connector-tools-connection) explains where the Worker finds the **support-slack** connection and what the Slack app needs.

## Next

- [Connector tools reference](https://docs.superdurable.io/reference/agents/connector-tools.md)
