# Scopes and tool discovery

> JWT `scope` claims, wildcard patterns (`tool:github:*`), scope-filtered listing, and the three discovery tiers: `ati tool search`, `ati tool info`, and `ati assist`.

- 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/scope.rs`
- `src/core/jwt.rs`
- `src/cli/tools.rs`
- `src/cli/help.rs`
- `src/proxy/server.rs`
- `docs/assist-examples.md`

---

---
title: "Scopes and tool discovery"
description: "JWT `scope` claims, wildcard patterns (`tool:github:*`), scope-filtered listing, and the three discovery tiers: `ati tool search`, `ati tool info`, and `ati assist`."
---

ATI binds every public tool to a canonical scope string (typically `tool:<tool_name>`) and enforces access through the JWT `scope` claim—a space-delimited list parsed into `ScopeConfig` in `src/core/scope.rs`. The same filter drives `ati tool list|search|info`, proxy `GET /tools`, and the tool catalog fed to `ati assist`; `ati run` and proxy `POST /call` call `check_access` before dispatch. Without JWT keys configured locally, discovery runs unrestricted (`ScopeConfig::unrestricted()` with `*`).

## Scope model

### JWT claim → `ScopeConfig`

`TokenClaims.scope` (RFC 9068 §2.2.3) is split on whitespace into individual scope tokens. `ScopeConfig::from_jwt` copies those tokens plus `sub`, `exp`, and optional per-pattern rate limits from the `ati` namespace (`ati.rate` map).

| Field | Source | Role |
|-------|--------|------|
| `scopes` | JWT `scope` | Allowlist patterns for tools, skills, and special tokens |
| `sub` | JWT `sub` | Agent identity |
| `expires_at` | JWT `exp` | `0` = no expiry; expired scopes deny all `is_allowed` checks |
| `rate_config` | `ati.rate` | Optional per-tool-pattern limits at call time |

Load scopes in local mode via `common::load_local_scopes_from_env()`:

- If `ATI_JWT_PUBLIC_KEY` or `ATI_JWT_SECRET` is set, `ATI_SESSION_TOKEN` (or `ATI_SESSION_TOKEN_FILE`, or `/run/ati/session_token`) must validate.
- If JWT validation is not configured, commands use unrestricted dev scopes.

Proxy mode (`ATI_PROXY_URL` set) forwards `ati tool` to `GET /tools` and `GET /tools/:name`; the proxy builds `ScopeConfig` from the Bearer JWT, or unrestricted when `jwt_config` is unset, or empty scopes when JWT is required but missing.

### Tool scope strings on manifests

At manifest load, any public tool without an explicit `scope` field receives `tool:{tool.name}` automatically (`ManifestRegistry` in `src/core/manifest.rs`). OpenAPI-imported tools default to `tool:{prefixed_name}` unless overridden. Internal providers (`internal = true`, e.g. `_llm` for assist) are excluded from `list_public_tools()` and never appear in discovery output.

Hand-written manifests can set `scope` per `[[tools]]` entry—for example, grouping Hacker News endpoints under one scope:

```toml
[[tools]]
name = "hackernews_top"
scope = "tool:hackernews_stories"
```

MCP-discovered tools use the namespaced tool name (`github:search_repositories`) and scope `tool:github:search_repositories`.

### Matching rules

`ScopeConfig::is_allowed(tool_scope)` applies these rules in order:

1. **Expired token** — all denied.
2. **Empty tool scope** — always allowed (tool has no scope requirement).
3. **Pattern match** — each JWT scope token is checked with `matches_wildcard`:
   - Exact: `tool:web_search` matches `tool:web_search`
   - Suffix wildcard: `tool:github:*` matches any scope starting with `tool:github:`
   - Global: `*` matches everything
4. **Legacy underscore alias** — colon-namespaced tool scopes also match underscore forms from older tokens, e.g. JWT `tool:github_*` matches tool scope `tool:github:search_repos`, and JWT `tool:test_api_get_data` matches `tool:test_api:get_data`.

`check_access(tool_name, tool_scope)` returns `ScopeError::AccessDenied` or `ScopeError::Expired` for `ati run` and proxy `/call`.

```mermaid
flowchart LR
  subgraph token [JWT]
    scopeClaim["scope claim"]
  end
  subgraph runtime [ATI runtime]
    SC["ScopeConfig"]
    FT["filter_tools_by_scope"]
    CA["check_access"]
  end
  subgraph surfaces [Surfaces]
    LIST["ati tool list/search"]
    INFO["ati tool info"]
    RUN["ati run / POST /call"]
    ASSIST["ati assist / POST /help"]
  end
  scopeClaim --> SC
  SC --> FT
  FT --> LIST
  FT --> INFO
  FT --> ASSIST
  SC --> CA
  CA --> RUN
```

## Scope grammar

| Token pattern | Example | Effect |
|---------------|---------|--------|
| `tool:<name>` | `tool:web_search` | Allows one tool whose manifest scope equals the token |
| `tool:<provider>:*` | `tool:github:*` | Allows all tools under that provider prefix |
| `tool:<provider>_<tool>` | `tool:github_search_repositories` | Legacy alias for `tool:github:search_repositories` |
| `skill:<name>` | `skill:research-general` | Skill visibility and resolution (not tool listing) |
| `help` | `help` | Sets `help_enabled` on `ScopeConfig` (reported by `ati auth status`) |
| `*` | `*` | Unrestricted tools; `help_enabled` true |

<Note>
`ati auth status` reports `help_enabled` when `help` or `*` is present. Include `help` in issued tokens when agents should use assist-heavy workflows (`ati init --proxy` examples use `"tool:* help"`). Tool listing and assist context are always filtered by **tool** scopes regardless of the `help` flag.
</Note>

### Issue a scoped token

```bash
ati token issue \
  --sub agent-sandbox-1 \
  --scope "tool:web_search tool:github:* help" \
  --ttl 3600 \
  --secret "$ATI_JWT_SECRET"

export ATI_SESSION_TOKEN="<printed token>"
ati auth status
```

Inspect without verification: `ati token inspect <token>`. Validate signature: `ati token validate <token>`.

## Scope-filtered listing

`filter_tools_by_scope` keeps tools where `tool.scope` is absent or `scopes.is_allowed(scope)`; wildcard `*` skips filtering. Both CLI and proxy discovery call this on `registry.list_public_tools()` (non-internal providers only).

**Proxy without JWT:** if `jwt_config` is configured and no Bearer token is sent, `scopes_for_request` returns empty scopes—list/info return no tools and `/call` returns 403. With no `jwt_config`, proxy runs unrestricted.

**Local without JWT keys:** unrestricted dev mode—all public tools visible.

Optional provider filter on list: `ati tool list --provider finnhub`.

## Three discovery tiers

Discovery is deliberately tiered: cheap deterministic search first, schema detail second, LLM synthesis last.

```text
  Agent question
       │
       ├─► Tier 1: ati tool search "<terms>"     fuzzy rank, top 20, scope-filtered
       │
       ├─► Tier 2: ati tool info <name>          schema, tags, skills, usage line
       │
       └─► Tier 3: ati assist "<question>"       LLM + scoped tool catalog (+ skills)
```

### Tier 1 — `ati tool search`

```bash
ati tool search "stock price quote"
ati tool search "github pull request" --output json
```

**Behavior (local):**

1. Load manifests, discover MCP tools (`discover_mcp_tools`).
2. `filter_tools_by_scope`.
3. Score each tool with `score_tool_match` over name (weight 10/5), provider (3), category (3), tags (4), description (2), hint (1.5).
4. Fuzzy match uses Jaro–Winkler ≥ 0.85 for terms ≥ 4 characters; stop words stripped.
5. Require at least half of content terms to match; sort by score; **truncate to 20** results.

**Proxy mode:** `GET /tools?search=<query>` applies scope filtering then **substring** match on name, description, and tags (no fuzzy rank). Prefer local search when you need ranked fuzzy results behind a proxy.

### Tier 2 — `ati tool info`

```bash
ati tool info finnhub__quote
ati tool info gh --output json
```

Resolves the tool only if in scope; otherwise: `Unknown tool: '<name>'. Run 'ati tool list' to see available tools.`

**Output includes:** provider, handler, endpoint or MCP/CLI details, `scope`, tags, hint, bound skills (manifest + `SkillRegistry`), `input_schema`, examples, and a generated `ati run` usage line. CLI tools without `input_schema` capture live `--help` (≤ 3000 chars) via `capture_cli_help`.

Proxy equivalent: `GET /tools/:name` with the same scope gate (404 if not allowed).

### Tier 3 — `ati assist`

```bash
# Unscoped — natural-language discovery across visible tools
ati assist "do we have a tool for stock prices?"

# Scoped to one tool or whole provider (first arg must match registry)
ati assist finnhub "which endpoint gives real-time quotes?"
ati assist gh "how do I create a pull request?"
```

**Routing:**

| Environment | Path |
|-------------|------|
| `ATI_PROXY_URL` set | `POST /help` with `{ query, tool? }` |
| Local | Internal `_llm` provider + keyring API key (or `--local` for Ollama) |

**Scope handling:**

1. `filter_tools_by_scope` on all public tools.
2. Unscoped: `prefilter_tools_by_query` keeps up to **50** tools using the same scorer as search; builds LLM system prompt from that subset plus resolved skills.
3. Scoped: first arg matching a tool name or provider name narrows context; if not visible, error (`'X' is not visible in your current scopes` locally; HTTP 403 on proxy).

Proxy `/help` also resolves skills via `skill::resolve_skills` and optional remote SkillATI sections. Responses are concise prose with embedded `ati run` examples; JSON output adds `tools_referenced`. See `docs/assist-examples.md` for sample shapes.

<Warning>
Assist uses the internal `_llm` manifest. It does not appear in `ati tool list`. Provision an LLM API key in the keyring for local assist; the proxy requires `_llm.toml` and a keyring entry for `/help`.
</Warning>

### Tier comparison

| Tier | Command | Cost | Best for |
|------|---------|------|----------|
| 1 | `ati tool search` | No LLM | Keyword discovery, scanning candidates |
| 2 | `ati tool info` | No LLM | Parameters, auth shape, exact `ati run` syntax |
| 3 | `ati assist` | LLM call | Multi-step workflows, “which tool should I use?” |

## Execution vs discovery

Discovery filters are **read-only**. Calling a tool still requires scope at execution:

```bash
# Discovery sees only scoped tools
ati tool list

# Execution enforces the same scope
ati run finnhub__quote --symbol AAPL
```

`ati run` loads scopes and calls `scopes.check_access(tool_name, tool.scope)` before dispatch. Proxy `/call` returns 403 with `Access denied: '<tool>' is not in your scopes` when the JWT lacks the tool’s scope pattern.

Rate limits from `ati.rate` in the JWT apply at call time (`core/rate.rs`), not during listing.

## Verify scopes and discovery

<Steps>
<Step title="Check session scopes">

```bash
ati auth status
```

Confirm tool count, raw `scope` string, expiry, and `help_enabled`.

</Step>
<Step title="List and search within scope">

```bash
ati tool list
ati tool search "quote"
```

Empty output usually means over-narrow scopes or missing manifests/MCP discovery.

</Step>
<Step title="Inspect one tool">

```bash
ati tool info <tool_name>
```

Confirm `scope` field matches a token you can issue (e.g. `tool:github:*` for `tool:github:search_repositories`).

</Step>
<Step title="Optional assist smoke test">

```bash
ati assist "what tools can fetch stock quotes?"
```

Requires LLM key (local) or proxy `/help` setup.

</Step>
</Steps>

## Common failure modes

| Symptom | Likely cause |
|---------|----------------|
| `ati tool list` empty | Token scopes omit needed `tool:` patterns; proxy JWT required but missing; MCP discovery failed |
| `Unknown tool` on `ati tool info` | Tool exists but scope not allowed; typo in namespaced MCP name |
| `Access denied` on `ati run` | Token valid for discovery mismatch—check exact `tool.scope` on manifest |
| `ATI_SESSION_TOKEN is required` locally | JWT validation configured without token |
| Assist 503 / no LLM | Missing `_llm` manifest or API key in keyring |
| Scoped assist forbidden | `ati assist <scope>` where scope not in filtered visible set |

<Tip>
For provider-wide sandboxes, prefer `tool:<provider>:*` over enumerating each MCP tool. For single-tool agents, issue `tool:<exact_tool_name>` matching the manifest `scope` field from `ati tool info`.
</Tip>

## Related pages

<CardGroup>
<Card title="Configure JWT and keys" href="/configure-jwt-and-keys">
Issue and validate session tokens, keygen, and orchestrator patterns.
</Card>
<Card title="JWT and scopes reference" href="/jwt-scopes-reference">
Full claims grammar, validation errors, and scope token reference.
</Card>
<Card title="Assist and plan reference" href="/assist-and-plan-reference">
`--plan`, `--save`, `--local`, and saved plan execution.
</Card>
<Card title="Proxy API reference" href="/proxy-api-reference">
`GET /tools`, `POST /help`, and `/call` request shapes.
</Card>
<Card title="Agent harness sandbox" href="/agent-harness-sandbox">
Provision sandboxes with scoped `ATI_SESSION_TOKEN` injection.
</Card>
</CardGroup>
