# Agent Tools

> Add connected MCP server tools to your agents so they can interact with external services.

**URL:** https://inference.sh/docs/connectors/agent-tools
**Last updated:** 2026-10-31

---

Add connected MCP server tools to your agents so they can interact with external services.

---

## Adding via the UI

1. Open your agent's settings → **Tools** tab
2. Click **Add Tool** → **Connector Tool**
3. Select a connected MCP server
4. Choose which tools to expose to the agent

The agent sees each tool with its name, description, and input schema — just like any other tool.

---

## Adding via YAML

In your agent's YAML config, add tools with `type: mcp`:

```yaml
tools:
  - name: list_issues
    type: mcp
    description: List Linear issues with filtering
    mcp:
      server: "linear"
      tool_name: "list_issues"

  - name: create_issue
    type: mcp
    description: Create a new Linear issue
    require_approval: true
    mcp:
      server: "linear"
      tool_name: "create_issue"
```

| Field | Description |
|-------|-------------|
| `server` | The MCP server's slug (`belt mcp list`). Saving the agent refuses a server your workspace cannot use |
| `tool_name` | The tool name as listed by `belt mcp tools <slug>` |

Each call signs in with the runner's own credential for the server's [auth provider](/docs/api/rest/auth-providers#mcp-resource-auth-providers) (`linear-mcp`): theirs, else their team's or org's. With none, the chat pauses and asks them to connect. A tool doesn't pin a credential; a `credential_id` from an older config is ignored.

---

## Tool Schema and Description

When an agent loads a connector tool, the runtime resolves the name, description, and input schema the LLM sees:

| Step | Source | When it applies |
|------|--------|-----------------|
| 1. Cached schema | Credential metadata from connect time | Default. Populated when you connect via `belt mcp connect`, the web app, or `POST /credentials` |
| 2. Live fetch | MCP server `tools/list` | Cache is missing or the tool was added after connect |
| 3. Generic fallback | Single `arguments` object | Cache and live fetch both fail (unreachable server, legacy credential without a server URL) |

**Description** — The `description` on your agent tool overrides the MCP server's tool description. If you omit it, the runtime uses the server's description from the cached or live tool definition.

### JSON Schema dialect

MCP tool `inputSchema` values are JSON Schema documents. Per the MCP specification (rev 2025-11-25, [SEP-1613](https://spec.modelcontextprotocol.io/)), schemas that omit `"$schema"` default to **JSON Schema 2020-12**. When a tool declares an explicit `"$schema"` (for example draft-07), inference shell parses the schema under that dialect.

When converting MCP schemas to the typed parameters agents and `belt mcp tools` display, the platform resolves array `items` across dialects — including 2020-12 single-schema arrays and draft-07 tuple forms.

**Unions and enums** — When an MCP tool parameter accepts more than one JSON Schema shape, the runtime keeps every non-null branch instead of picking a single “simplest” type:

| MCP / JSON Schema pattern | What the agent sees |
|---------------------------|---------------------|
| `anyOf` / `oneOf` with two or more non-null branches | One parameter whose schema includes `anyOf` with each branch converted recursively (for example `string` **or** `integer` for the same field) |
| `anyOf` / `oneOf` where every branch is `null` | `type: null` (nothing useful to send — check the MCP server schema) |
| Nullable idiom (`anyOf: [{type: string}, {type: null}]`) | The non-null branch only — whether a value may be omitted is controlled by top-level `required`, not by a `null` union member |
| JSON Schema `type` array with multiple types (for example `["string", "number"]`) | Treated as a union and emitted as `anyOf` when more than one type applies |
| `enum` on a scalar | Closed set preserved as an `enum` array **and** appended to the description (`Possible values: …`) for older UIs |

Provider tool APIs (OpenAI, Anthropic, and others) accept `anyOf` in function parameters, so multi-shape MCP inputs reach the model intact. Reconnect MCP servers after platform upgrades if cached schemas were converted under the older single-branch rules.

### Troubleshooting

If an agent tool shows a generic `arguments` parameter instead of typed fields, or array parameters look like untyped `object[]` instead of the element type the MCP server defines, reconnect the MCP server (`belt mcp connect <slug>`) so the platform can refresh cached tool schemas.

---

## How It Works at Runtime

1. Agent decides to call the tool based on the conversation
2. Runtime loads your workspace's credentials
3. Opens a fresh MCP session to the remote server
4. Calls the tool with the agent's arguments
5. Returns the result to the agent

Credentials are never exposed to the agent. Each tool call gets a fresh session — no state leaks between calls.

---

## Require Approval

For destructive operations (creating issues, sending messages, deleting records), enable `require_approval`:

```yaml
- name: send_slack_message
  type: mcp
  description: Send a message to a Slack channel
  require_approval: true
  mcp:
    server: "slack"
    tool_name: "send_message"
```

The agent pauses and waits for user confirmation before executing.

---

## Example: Triage Agent

An agent that reads Linear issues and posts summaries to Slack:

```yaml
name: triage-agent
description: Summarizes new Linear issues and posts to Slack
core_app:
  ref: openrouter/claude-sonnet-45
system_prompt: |-
  You triage incoming Linear issues. For each new issue:
  1. Read the issue details
  2. Categorize by severity and area
  3. Post a summary to the #triage Slack channel
tools:
  - name: list_issues
    type: mcp
    description: List recent Linear issues
    mcp:
      server: "linear"
      tool_name: "list_issues"

  - name: get_issue
    type: mcp
    description: Get full issue details
    mcp:
      server: "linear"
      tool_name: "get_issue"

  - name: post_to_slack
    type: mcp
    description: Post a message to Slack
    require_approval: true
    mcp:
      server: "slack"
      tool_name: "send_message"
```

---

## SDK Builders

### Python

```python
from inferencesh import mcp_tool

list_issues = (
    mcp_tool("list_issues", server="linear", tool_name="list_issues")
    .describe("List Linear issues with filtering")
    .build()
)

create_issue = (
    mcp_tool("create_issue", server="linear", tool_name="create_issue")
    .describe("Create a new Linear issue")
    .require_approval()
    .build()
)
```

The signature is `mcp_tool(name, *, server, tool_name)` (**inferencesh ≥ v0.32.0**; older versions also took `credential_id`).

### JavaScript

```typescript
import { mcpTool } from '@inferencesh/sdk';

const listIssues = mcpTool('list_issues', { server: 'linear', toolName: 'list_issues' })
  .describe('List Linear issues with filtering')
  .build();

const createIssue = mcpTool('create_issue', { server: 'linear', toolName: 'create_issue' })
  .describe('Create a new Linear issue')
  .requireApproval()
  .build();
```

`mcpTool(name, { server, toolName })` needs **@inferencesh/sdk ≥ v0.30.0** (older versions also took `credentialId`).

---

## MCP Apps UI (interactive tool pages)

Some MCP tools ship an interactive HTML page alongside the tool result ([MCP Apps](https://spec.modelcontextprotocol.io/specification/draft/apps/), SEP-1865). When the platform attaches that UI to a chat message, it appears as an **A2UI** component with `component: "McpApp"` (catalog `inferencesh/v1`).

The host runs the page in a sandbox, passes the tool **input** and **result** over `postMessage`, and proxies any in-page `tools/call` requests back to the MCP server using the credential stored on the component. Content Security Policy from the resource (`_meta.ui.csp`) is applied as `mcpCsp` on the component.

| Field | Role |
|-------|------|
| `mcpHtml` | HTML document stored on the message so the chat can render without a separate fetch |
| `mcpPageHash` | When the page is stored by hash, names [`GET /mcp-ui-pages/{hash}`](/docs/api/rest/mcp-servers#get-stored-page) instead of inlining HTML |
| `mcpResourceUri` | MCP Apps resource URI when the page came from a tool resource |
| `mcpServerSlug` / `mcpCredentialId` / `mcpToolName` | Route proxied tool calls to the right connector (`mcpCredentialId` uses [`POST /mcp-credentials/{id}/tools/{tool}`](/docs/api/rest/mcp-servers#call-tool-by-connection-credential_id) when `mcpServerSlug` is empty) |
| `mcpToolInput` / `mcpToolResult` | Arguments and result in the shape the page expects (CallToolResult content blocks, not remapped platform output) |
| `mcpPrefersBorder` | Optional chrome hint for the embed |
| `mcpArtifactId` | When the page is a **platform artifact** (your app's own UI), runtime serves artifact data endpoints instead of `/mcps` |

Custom app UIs use the same sandbox model: set `ui.artifact` on an app version (`AppVersionDTO.ui`, mirrored from `metadata.ui`) to an artifact ref (`namespace/name` or `@version`) so task views can show the page beside generic JSON output. See [Apps API — Custom UI page](/docs/api/rest/apps#custom-ui-page-ui).

Agent and SDK consumers see the surface on `ToolInvocationDTO.widget` (`A2UISurface` / `@inferencesh/sdk` type `Widget`). TypeScript field names match `A2UIComponent` in `@inferencesh/sdk` (`McpApp`, `MCPUICSP`, `A2UIMcpToolResult`).

---

## Next

- [MCP Server](/docs/connectors/mcp-server) — use inference shell as an MCP server for Claude Code and Cursor
- [Adding Tools](/docs/agents/adding-tools) — all agent tool types
