# Providers and handlers

> Handler types (`http`, `openapi`, `mcp`, `cli`, `file_manager`, `passthrough`), tool naming (`provider:tool`), auth injection, and how manifests map to dispatch in `core/http`, `mcp_client`, and `cli_executor`.

- Repository: Parcha-ai/ati
- GitHub: https://github.com/Parcha-ai/ati
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa/llms-full.txt

## Source Files

- `src/core/manifest.rs`
- `src/core/http.rs`
- `src/core/openapi.rs`
- `src/core/mcp_client.rs`
- `src/core/cli_executor.rs`
- `manifests/finnhub.toml`
- `manifests/github-mcp.toml`
- `manifests/google-workspace.toml`

---

---
title: "Providers and handlers"
description: "Handler types (`http`, `openapi`, `mcp`, `cli`, `file_manager`, `passthrough`), tool naming (`provider:tool`), auth injection, and how manifests map to dispatch in `core/http`, `mcp_client`, and `cli_executor`."
---

Each TOML manifest under `~/.ati/manifests/` declares one `[provider]` and optional `[[tools]]`. The `handler` field (default `http`) selects the execution backend. `ManifestRegistry` indexes tools by exact name; `ati run <tool>` and proxy `POST /call` resolve the tool, check JWT scopes, then branch on `provider.handler` into `core/http`, `core/mcp_client`, `core/cli_executor`, `core/file_manager`, or (proxy-only) raw HTTP passthrough.

## Handler catalog

Six handler values are accepted at load time and in the provider store:

| Handler | Manifest tools | Execution module | Invoked via |
|---------|----------------|------------------|-------------|
| `http` (default) | Hand-written `[[tools]]` | `core/http` → `providers/generic` | `ati run`, `POST /call` |
| `openapi` | Auto-generated from spec at load | Same HTTP path as `http` | `ati run`, `POST /call` |
| `mcp` | Discovered via MCP `tools/list` | `core/mcp_client` | `ati run`, `POST /call`, `POST /mcp` |
| `cli` | One implicit tool per provider (provider name) if `[[tools]]` empty | `core/cli_executor` | `ati run`, `POST /call` |
| `file_manager` | Built-in `file_manager:download` / `file_manager:upload` | `core/file_manager` (proxy); CLI wrapper in `cli/file_manager` | `ati run` (short-circuited before handler match) |
| `passthrough` | No tools — HTTP routes only | `core/passthrough` | Proxy fallback (not `ati run`) |

<Note>
`openapi` is a load-time specialization: specs are parsed in `core/openapi`, tools are registered into the manifest, and runtime dispatch uses the same HTTP executor as hand-written providers. The manifest still stores `handler = "openapi"` for filtering and discovery.
</Note>

## Dispatch flow

```mermaid
flowchart TB
  subgraph input [Entry]
    RUN["ati run tool_name"]
    CALL["POST /call"]
  end

  subgraph registry [ManifestRegistry]
    LOAD["load manifests/*.toml"]
    OAI["openapi: load_and_register"]
    MCPD["discover_all_mcp_tools"]
    FM["register_file_manager_provider"]
    IDX["tool_index HashMap"]
  end

  subgraph gate [Pre-dispatch]
    FMCHK{"file_manager:* ?"}
    PROXY{"ATI_PROXY_URL set?"}
    SCOPE["scope + rate checks"]
  end

  subgraph exec [Handler branch]
    MCP["mcp → mcp_client::execute_with_gen"]
    CLI["cli → cli_executor::execute_with_gen"]
    FMP["file_manager → dispatch_file_manager"]
    HTTP["http / openapi → http::execute_tool_with_gen"]
    PT["passthrough → handle_passthrough fallback"]
  end

  RUN --> FMCHK
  CALL --> SCOPE
  FMCHK -->|yes| FML["cli/file_manager execute"]
  FMCHK -->|no| PROXY
  PROXY -->|yes| CALL
  PROXY -->|no| LOAD
  LOAD --> OAI --> IDX
  LOAD --> MCPD --> IDX
  LOAD --> FM --> IDX
  RUN --> LOAD
  RUN --> SCOPE
  SCOPE --> MCP
  SCOPE --> CLI
  SCOPE --> FMP
  SCOPE --> HTTP
  CALL --> MCP
  CALL --> CLI
  CALL --> FMP
  CALL --> HTTP
  PT -.->|proxy only, not /call| CALL
```

Local mode (`cli/call.rs`) matches `mcp`, `cli`, and a default arm for everything else (including `http` and `openapi`). Proxy mode (`proxy/server.rs`) adds an explicit `file_manager` arm before the HTTP default.

## Tool naming

The registry key is the tool’s `name` field exactly as indexed — there is no automatic prefixing for hand-written HTTP tools.

| Source | Naming pattern | Example |
|--------|----------------|---------|
| Hand-written HTTP | Flat name (often `{provider}_{action}`) | `hackernews_top_stories` |
| OpenAPI | `{provider}:{operationId}` | `finnhub:quote` |
| MCP (discovered) | `{provider}:{mcp_tool_name}` | `github:search_repositories` |
| CLI (auto tool) | Same as `provider.name` | `google_workspace` |
| File manager (built-in) | Fixed | `file_manager:download`, `file_manager:upload` |

Colon is the canonical separator (`TOOL_SEP` in `core/manifest.rs`). MCP execution strips the provider prefix before `tools/call`:

```text
ati run github:read_file  →  MCP tools/call name "read_file"
```

Scopes are auto-assigned at load as `tool:{tool.name}` when a tool has no explicit `scope` and the provider is not `internal = true`. Wildcard JWT scopes like `tool:github:*` match prefixed MCP/OpenAPI names via scope alias logic in `core/scope.rs`.

<Warning>
Calling `ati run finnhub:quote` requires that exact indexed name. Hand-written tools such as `hackernews_top_stories` are not namespaced with `hackernews:` — check `ati tool info` before scripting calls.
</Warning>

## `http` — hand-written REST tools

Default when `handler` is omitted. Manifests declare `base_url`, `auth_*`, and one or more `[[tools]]` with `endpoint`, `method`, and `input_schema`.

**Parameter routing** (`core/http.rs`):

- **Location-aware** (OpenAPI-generated and any tool with `x-ati-param-location` on schema properties): `path` → URL template substitution; `query` / `header` / `body` routed accordingly; optional `x-ati-body-encoding = "form"`.
- **Legacy** (no location metadata): GET sends all args as query params; POST/PUT/DELETE send JSON body.

**Response shaping**: `providers/generic.rs` calls `http::execute_tool_with_gen`, then `response::process_response` for optional JSONPath `extract` / format from `[tools.response]`.

Example (`manifests/hackernews.toml`): provider `hackernews`, tools `hackernews_top_stories`, `auth_type = "none"`, GET endpoints under Firebase API `base_url`.

## `openapi` — spec-driven HTTP tools

Set `handler = "openapi"`, `openapi_spec` (file under `~/.ati/specs/`), and optional filters (`openapi_include_tags`, `openapi_exclude_tags`, `openapi_include_operations`, `openapi_exclude_operations`, `openapi_max_operations`).

At `ManifestRegistry::load`, `core/openapi::load_and_register` parses the spec and replaces `manifest.tools` with generated tools named `{provider}:{operationId}`. Each property gets `x-ati-param-location` for HTTP classification. PATCH operations map to PUT in ATI’s `HttpMethod` enum.

Example (`manifests/finnhub.toml`):

```toml
[provider]
name = "finnhub"
handler = "openapi"
base_url = "https://finnhub.io/api/v1"
openapi_spec = "finnhub.json"
auth_type = "query"
auth_query_name = "token"
auth_key_name = "finnhub_api_key"
openapi_max_operations = 50
```

Runtime execution is identical to `http` (default `_` branch → `generic::execute_with_gen`).

## `mcp` — MCP protocol providers

No `[[tools]]` required. Tools appear after discovery (`tools/list`), registered with the `provider:` prefix.

| Field | Purpose |
|-------|---------|
| `mcp_transport` | `stdio` (default) or `http` |
| `mcp_command` / `mcp_args` | Subprocess for stdio (e.g. `npx -y @modelcontextprotocol/server-github`) |
| `mcp_url` | Streamable HTTP endpoint |
| `mcp_url_env` | Sandbox env var → proxy validates `X-Ati-Upstream-Url` against keyring allowlist (HTTP only) |
| `mcp_env` | Env map with `${keyring_key}` substitution for stdio |

**Discovery timing**:

- Proxy startup and `ati tool list`: `discover_all_mcp_tools` (30s timeout per provider, concurrent).
- First `ati run` with unknown tool: if name prefix matches an MCP provider, discover that provider once and register tools.

**Auth**: Often `auth_type = "none"` with secrets in `mcp_env` (GitHub token). HTTP MCP may use bearer from keyring or `auth_generator`. `auth_type = "url"` resolves `${key}` inside `mcp_url` at connect time.

Examples:

- `manifests/github-mcp.toml` — stdio, `GITHUB_PERSONAL_ACCESS_TOKEN = "${github_token}"`
- `manifests/deepwiki-mcp.toml` — HTTP, `mcp_url = "https://mcp.deepwiki.com/mcp"`, no auth

## `cli` — subprocess providers

Wraps a host binary (`cli_command`) with curated env (`PATH`, `HOME`, …) plus resolved `cli_env`.

| `cli_env` value | Behavior |
|-----------------|----------|
| `${key}` | Inline keyring substitution |
| `@{key}` | Materialize keyring secret to `~/.ati/.creds/` (0600), env set to file path; wiped on drop in ephemeral keyring mode |
| plain string | Passthrough |

If `[[tools]]` is empty, load registers one tool named after the provider with scope `tool:{provider_name}`. Invocation passes **positional** args after the tool name to the subprocess (`cli_default_args` prepended). Proxy mode can rewrite output paths via `cli_output_args` / `cli_output_positional`, capture files, and return base64 in JSON.

Example (`manifests/google-workspace.toml`):

```toml
[provider]
name = "google_workspace"
handler = "cli"
cli_command = "gws"
cli_timeout_secs = 120
auth_type = "none"

[provider.cli_env]
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE = "@{google_workspace_credentials}"
```

Call pattern: `ati run google_workspace -- <subcommand> <args…>`.

## `file_manager` — binary download/upload

Virtual provider registered by `register_file_manager_provider` if not fully declared by the operator. Tools are always `file_manager:download` and `file_manager:upload`.

- Operator manifest with `handler = "file_manager"` and `upload_destinations` can supply allowlisted upload sinks; built-in tools are backfilled when `[[tools]]` is empty.
- `ati run` short-circuits to `cli/file_manager` for local file I/O, then either direct `file_manager` ops (local) or proxy `POST /call` with base64 payloads (proxy).
- Proxy-only `dispatch_file_manager` in `proxy/server.rs` enforces destinations and SSRF rules from `core/file_manager`.

## `passthrough` — raw HTTP reverse proxy

Not an `ati run` handler. Enabled with `ati proxy --enable-passthrough`. `PassthroughRouter::build` compiles routes from manifests where `handler = "passthrough"`. Requires non-empty `base_url` and at least one of `host_match` or `path_prefix`.

Requests that miss named proxy routes hit `handle_passthrough`: longest-prefix match, optional `strip_prefix` / `path_replace`, `deny_paths` / `forward_authorization_paths` globs, keyring-resolved auth injected at startup, body size caps. WebSocket forwarding is rejected at load time today.

See `deploy/examples/vm/manifests/example-passthrough.toml` for a commented template.

## Authentication injection

HTTP and OpenAPI tools share `inject_auth` in `core/http.rs`. Priority: `auth_generator` (dynamic credentials from `GenContext` + `AuthCache`) over static keyring.

| `auth_type` | HTTP injection |
|-------------|----------------|
| `none` | No credential |
| `bearer` | `Authorization: Bearer` from `auth_key_name` |
| `header` | Custom header (`auth_header_name`, optional `auth_value_prefix`) |
| `query` | Query param (`auth_query_name`, default `api_key`) |
| `basic` | HTTP Basic from keyring value |
| `oauth2` | Client-credentials token (cached per provider name) |
| `url` | Key embedded in URL via `${key}` at connect time; no header/query inject |

`provider.extra_headers` are applied after auth. Agent-supplied headers matching a deny-list (including `authorization`) are rejected for classified header params.

MCP stdio: secrets via `mcp_env` `${…}`; optional `auth_generator` → `ATI_AUTH_TOKEN` + `extra_env`. CLI: same generator path into subprocess env; static secrets via `cli_env`.

Passthrough: auth frozen at router build from keyring; per-path `forward_authorization_paths` can forward sandbox `Authorization` and skip manifest inject.

<Tip>
Use `auth_generator` when credentials must be minted per call (JWT-bearing generators, short-lived tokens). Use `auth_key_name` + `ati key set` for static API keys.
</Tip>

## End-to-end call sequence

```mermaid
sequenceDiagram
  participant Agent
  participant ATI as ati run / proxy /call
  participant Reg as ManifestRegistry
  participant Exec as Handler backend

  Agent->>ATI: tool_name + args
  ATI->>Reg: get_tool(name)
  alt MCP prefix, not indexed
    ATI->>Exec: discover + register_mcp_tools
  end
  Reg-->>ATI: Provider + Tool
  ATI->>ATI: normalize_arg_keys, scope check
  alt handler mcp
    ATI->>Exec: strip prefix, McpClient.call_tool
  else handler cli
    ATI->>Exec: subprocess + cli_env
  else handler file_manager
    ATI->>Exec: fetch/upload bytes
  else http or openapi
    ATI->>Exec: classify_params, inject_auth, reqwest
  end
  Exec-->>Agent: JSON / formatted text
```

## Operator checklist

<Steps>
<Step title="Pick a handler">
Use `http` for a few fixed endpoints, `openapi` for large REST APIs, `mcp` for MCP servers, `cli` for existing CLIs, `file_manager` for agent binary I/O, `passthrough` only on the proxy for transparent HTTP proxying.
</Step>
<Step title="Author the manifest">
Place `~/.ati/manifests/<name>.toml`. For OpenAPI, run `ati provider import-openapi` so the spec lives under `~/.ati/specs/`.
</Step>
<Step title="Store credentials">
`ati key set <auth_key_name> <secret>` for `${…}` / `@{…}` references and HTTP auth fields.
</Step>
<Step title="Verify discovery">
`ati tool list` (triggers MCP discovery) and `ati tool info <exact_tool_name>` for schema and scope.
</Step>
<Step title="Run scoped">
`ati run <tool_name> --arg value` with a JWT or local scope env that includes `tool:<name>` or a matching wildcard.
</Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause |
|---------|----------------|
| Unknown tool after MCP add | Discovery not run; prefix wrong; typo in `provider:tool` segment |
| `Unknown tool: 'finnhub:foo'` with suggestions | OpenAPI cap/filter excluded operation; spec not loaded (check proxy logs for warn) |
| `hackernews:top_stories` not found | Hand-written tool uses flat name `hackernews_top_stories` |
| MCP auth failures in proxy mode | Keyring empty on proxy host; `mcp_env` key missing |
| CLI credential errors | `@{key}` not in keyring; file permissions under `~/.ati/.creds` |
| Passthrough 404 | `--enable-passthrough` off; no matching `host_match`/`path_prefix` |
| Scope denied | Auto scope `tool:{name}` not in JWT; use wildcard `tool:provider:*` for MCP families |

## Related pages

<CardGroup>
<Card title="Manifest reference" href="/manifest-reference">
Full TOML schema for `[provider]`, `[[tools]]`, handler-specific fields, and load-time validation errors.
</Card>
<Card title="Import OpenAPI" href="/import-openapi">
Download specs, filters, `x-ati-param-location`, and operation caps.
</Card>
<Card title="Add MCP providers" href="/add-mcp-providers">
`ati provider add-mcp`, stdio vs HTTP, env injection, namespaced calls.
</Card>
<Card title="CLI and HTTP manifests" href="/cli-and-http-manifests">
`add-cli`, hand-written HTTP, `${key}` vs `@{key}`, output capture.
</Card>
<Card title="File manager operations" href="/file-manager-operations">
Download/upload tools, SSRF, upload destinations, proxy base64 transfer.
</Card>
<Card title="Deploy proxy server" href="/deploy-proxy-server">
Passthrough flag, sig-verify, route compilation, health checks.
</Card>
</CardGroup>
