> ## Documentation Index
> Fetch the complete documentation index at: https://noorle.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# tools/list

> Discover a Noorle MCP gateway's tools, with cursor pagination, cache freshness metadata, autonomy annotations, and per-caller tool withholding

List the tools a gateway exposes to you.

## Request

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}
```

Pass `params.cursor` to fetch a later page. Pages hold **50 tools**; the cursor
is an opaque offset returned as `nextCursor`.

## Response

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "web_search",
        "description": "Search the web",
        "inputSchema": {
          "type": "object",
          "properties": { "query": { "type": "string" } },
          "required": ["query"]
        },
        "annotations": { "readOnlyHint": true },
        "_meta": { "dev.noorle/autonomy": { "outcome": "allow" } }
      }
    ],
    "nextCursor": "50",
    "ttlMs": 300000,
    "cacheScope": "private"
  }
}
```

| Field                                  | Description                                   |
| -------------------------------------- | --------------------------------------------- |
| `tools[].name`                         | The namespaced wire name                      |
| `tools[].description`                  | Human-readable description                    |
| `tools[].inputSchema`                  | JSON Schema for the arguments                 |
| `tools[].annotations.readOnlyHint`     | `true` when the tool is read-tier             |
| `tools[]._meta["dev.noorle/autonomy"]` | Static autonomy outcome — see below           |
| `nextCursor`                           | Present when more pages remain                |
| `ttlMs`                                | How long the response may be treated as fresh |
| `cacheScope`                           | Always `"private"`                            |

## Tool names

The wire name is `{namespace}_{tool}` — one underscore, no double underscore.
The namespace comes from the capability's `namespace` field, which is unique per
account.

| Capability                 | Example                                          |
| -------------------------- | ------------------------------------------------ |
| Files built-in             | `file_read`, `file_write`, `file_ls`             |
| Web Search built-in        | `web_search`                                     |
| Code Runner built-in       | `run_python`, `run_javascript`                   |
| Memory built-in            | `memory_store`, `memory_recall`, `memory_forget` |
| A plugin named `my-plugin` | `my_plugin_{tool}`                               |
| A connector                | `{connector_namespace}_{operation}`              |

Do not assume a namespace from the capability's display name — read it from
[`GET /v1/capabilities`](/docs/reference/rest/capabilities-list).

## Caching

`cacheScope` is **`private` on every gateway, always** — never `public`.

The response is a function of the *calling principal*, not just the gateway. Two
callers on one gateway legitimately receive different lists: autonomy
annotations are computed per caller, and since workspace-anchor withholding
landed, so is the *membership* of the list. A shared cache could serve an
authenticated caller's fuller tool list to an anonymous one.

`ttlMs` is **300000** (5 minutes). It is a freshness hint, not a snapshot
guarantee — the list may change before it expires, and pages carry no cross-page
consistency guarantee. Because
[no `list_changed` notification is ever sent](/docs/reference/mcp/overview), this TTL
is the only staleness signal a client gets.

Staleness never grants authority. A stale `allow` annotation is a hint that the
gate re-evaluates at dispatch and can still deny.

Clients on protocol versions before `2026-07-28` receive both fields and will
simply ignore them.

## Withheld tools

Some tools cannot function without a workspace anchor. On an MCP gateway, an
anonymous caller has no anchor, so those tools are **removed from the list
entirely** rather than advertised and then failed at call time. They are also
removed from the discovery corpus, so they are not callable by name either.

| Built-in        | Requires an anchor                   |
| --------------- | ------------------------------------ |
| Files           | All tools                            |
| Sandbox         | All tools                            |
| Browser         | `screenshot`, `pdf`, `snapshot` only |
| Everything else | No                                   |

Browser's `markdown`, `content`, `extract_json`, `scrape`, and `links` work
sessionlessly and stay listed.

Guessing a withheld tool's name and calling it returns an invalid-tool-name
error — the same shape as a tool that does not exist, so the response does not
confirm it is there.

## Autonomy annotations

Each capability tool carries its static autonomy verdict:

```json theme={null}
"_meta": { "dev.noorle/autonomy": { "outcome": "deny" } }
```

| `outcome`          | Meaning                                                         |
| ------------------ | --------------------------------------------------------------- |
| `allow`            | Will execute autonomously under the current policy              |
| `deny`             | Will deterministically deny — do not offer this tool to a model |
| `may_deny_on_args` | An argument-predicated rule applies; no static verdict exists   |
| `ungated`          | The gateway has no policy; calls ride the pre-policy path       |

Two properties to design around:

* **Denied tools stay listed.** Omitting them would churn the surface on every
  policy edit and hide deliberate "exposed but never autonomous" intent.
* **No `reason` is on the wire.** `tools/list` is reachable unauthenticated on
  public gateways, and the reason would name the account's policy internals.

Meta-tools (the discovery and unified surfaces) are not namespaced capability
tools and pass through unannotated.

If the gateway cannot read the policy, the capability list, or the deny ledger,
tools come back **unannotated** rather than annotated from partial inputs. Treat
a missing `_meta` entry as "unknown", never as "allowed".

## Presentation modes

A gateway renders its tools in one of four modes, set per gateway:

| Mode          | Behavior                                                                                       |
| ------------- | ---------------------------------------------------------------------------------------------- |
| **Direct**    | Every bound tool appears flat in the list                                                      |
| **Discovery** | Three meta-tools (`discover_tools`, `get_tool_schema`, `execute_tool`) stand in for the corpus |
| **Unified**   | One `unified_tool` stands in for the corpus                                                    |
| **Adaptive**  | Direct below 30 bound tools, Discovery at or above                                             |

Adaptive is the platform default. In Discovery and Unified modes the underlying
tools are **not directly callable** — reaching them goes through the meta-tool.

## Errors

| Code   | Cause                                                                             |
| ------ | --------------------------------------------------------------------------------- |
| -32029 | Rate limit exhausted — this method shares the 600/minute budget with `tools/call` |

Missing or invalid credentials fail during transport authentication, before this
method runs — see [Authentication](/docs/reference/authentication).

## Related

* [tools/call](/docs/reference/mcp/tools-call)
* [MCP overview](/docs/reference/mcp/overview)
* [List Capabilities](/docs/reference/rest/capabilities-list)
