---
title: "TDS resources"
url: "/reference/xds/tds-resources/"
kind: "reference"
summary: "Tool Discovery Service: the xDS extension resources that add, update and remove MCP tools, OpenAPI sources, dynamic servers and toolkits at runtime."
---

> Arion site index: [/llms.txt](/llms.txt)

# TDS resources

Tool Discovery Service: the xDS extension resources that add, update and remove MCP tools, OpenAPI sources, dynamic servers and toolkits at runtime.

TDS delivers MCP tools, OpenAPI tool sources, dynamic MCP servers and code-mode toolkits to a running [MCP gateway](@/reference/filters/http/mcp-gateway.md) as xDS extension resources. Tool catalogs change without replacing listeners, routes or TLS.

## Enabling TDS on a gateway

TDS rides the same delta ADS stream as every other resource, so the bootstrap must configure [`dynamic_resources`](@/reference/xds/resource-types.md). On the gateway itself, set `tds.config_name` in the MCP gateway filter:

```yaml,arion=fragment
typed_config:
  "@type": type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.McpGateway
  server_info:
    name: tenant-a-mcp
    version: "1.0.0"
  tools_mode: {}
  tds:
    config_name: main
  tools: []           # static tools may coexist with TDS-delivered ones
```

Arion subscribes to all four TDS type URLs with a wildcard when the first TDS-enabled gateway loads. Resources delivered over TDS land in that gateway's registry next to any statically configured tools, OpenAPI sources and dynamic servers.

## Scopes and resource IDs

Every TDS resource is addressed to one gateway scope:

| Piece | Value |
|---|---|
| Scope | `{server_info.name}/{tds.config_name}`, for example `tenant-a-mcp/main` |
| Resource ID | `{server_info.name}/{tds.config_name}/{resource_name}`, for example `tenant-a-mcp/main/weather.get_forecast` |

`server_info.name` and `tds.config_name` must not contain `/`; the resource name may. The resource name is canonical for every type. If the payload's own name or namespace differs, Arion uses the resource name, so a later remove targets the same key.

| Type | Resource name |
|---|---|
| `Tool` | `namespace.name`. A name that does not parse as a qualified name is NACKed. |
| `OpenApiToolSource` | The source's `namespace` |
| `DynamicMcpServer` | The server's `namespace` |
| `Toolkit` | The toolkit's `name` |

Two gateways are isolated as long as their scopes differ: the same `config_name` under different `server_info.name` values never shares resources. For TDS-enabled gateways, Arion rejects duplicate `server_info.name` values in one runtime, which prevents accidental fan-out.

## Resource payloads

### Tool

Type URL: `type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.Tool`

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `namespace` | string | yes | — | Tool namespace; overridden by the resource name if they differ. |
| `name` | string | yes | — | Local tool name; overridden by the resource name if they differ. |
| `description` | string | yes | — | Description shown to MCP clients and used by tool search. |
| `input_schema` | DataSource | no | empty object | JSON Schema for the tool's arguments. |
| `output_schema` | DataSource | no | unset | JSON Schema for the result. |
| `rest_backend` | RestBackend | one of | — | Execute by calling a REST upstream. |
| `mcp_server_backend` | McpServerBackend | one of | — | Proxy the call to an upstream MCP server (`transport`: `Sse` or `StreamableHttp`, plus `url`). |
| `function_graph_backend` | object | one of | — | Reserved backend kind; carries no fields. |
| `rbac` | ToolRbac | no | unset | Per-tool JWT access rules. |
| `embedding` | list of float | no | `[]` | Precomputed embedding vector for search. When it is omitted and remote embeddings are configured, Arion embeds the tool on the next search. |
| `disclosure_mode` | enum | no | `PROGRESSIVE` | `EAGER` lists the tool in `tools/list`; `PROGRESSIVE` leaves it to search. |

RestBackend fields: `method`, `path`, `query_params` (a list of `name`/`source`), `cluster`, `body_template` (DataSource) or `body_json` (string), and `upstream_policy` (`authority`, `timeout`, `retry_policy`).

### OpenApiToolSource

Type URL: `type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.OpenApiToolSource`

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `spec` | DataSource | yes | — | OpenAPI 3.x document, JSON or YAML. A `filename` is read on the Arion host when the resource arrives. |
| `namespace` | string | yes | — | Namespace of every derived tool; overridden by the resource name if they differ. |
| `cluster` | string | yes | — | Cluster every derived tool calls. |
| `upstream_policy` | HttpUpstreamPolicy | no | unset | Applied to every derived tool. |
| `rbac` | ToolRbac | no | unset | Applied to every derived tool. |
| `tool_disclosure_mode` | enum | no | `PROGRESSIVE` | Disclosure mode of every derived tool. |
| `path_prefix` | string | no | path of `servers[0].url` | Base path for every operation. |
| `skip_unsupported_operations` | bool | no | `false` | Skip operations that cannot become tools instead of rejecting the resource. |
| `include_operations`, `exclude_operations` | list of string | no | empty | Glob filters on `operationId` and `METHOD /path`; see [OpenAPI tools](@/docs/mcp-gateway/openapi-tools.md#choose-operations). |

### DynamicMcpServer

Type URL: `type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.DynamicMcpServer`

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `namespace` | string | yes | — | Namespace of the discovered tools, registered as `{namespace}.{upstream_tool}`. Overridden by the resource name if they differ. |
| `description` | string | no | empty | Description of the server. |
| `transport` | enum | no | `Sse` | `Sse` or `StreamableHttp`; only `StreamableHttp` is implemented. |
| `url` | string | yes | — | Absolute `http` or `https` URL of the upstream MCP endpoint. |
| `cache_duration` | duration | no | unset | Minimum interval between refreshes. When unset, a successful first fetch is cached indefinitely. |
| `rbac` | ToolRbac | no | unset | Access rules applied to every tool materialized from this server. |
| `tool_disclosure_mode` | enum | no | `PROGRESSIVE` | Disclosure mode of every tool materialized from this server. |

### Toolkit

Type URL: `type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.Toolkit`

Consumed only by gateways in [code mode](@/docs/mcp-gateway/code-mode.md); tools-mode gateways ACK and ignore it.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Toolkit name; always replaced by the resource name. |
| `description` | string | yes | — | The kind of task the toolkit is for. |
| `instructions` | string | yes | — | Guidance on composing the tools. |
| `tools` | list of `{namespace, name}` | yes | — | At least two distinct tool references. |

ToolRbac fields: `action` (`ALLOW` or `DENY`) and `permissions`, a list where each entry is a `jwt_header` or `jwt_claim` matcher with `field` and `value`. See [tool authorization](@/docs/mcp-gateway/tool-authorization.md).

## Update and remove semantics

| Event | Effect |
|---|---|
| `Tool` update | Validates the tool through the same path as static config, then adds or replaces it in every registry of the scope. It can replace a static tool with the same qualified name. It is NACKed if an OpenAPI source or a dynamic server owns the namespace. An embedding failure does not fail the update. |
| `Tool` remove | Removes the static or TDS-delivered tool with that qualified name. A static definition does not come back until the configuration reloads. Tools derived from an OpenAPI source or a dynamic server are not touched. |
| `OpenApiToolSource` update | Expands the document and replaces every tool in the namespace with the result. An invalid document is NACKed, and the registry keeps its previous tools. |
| `OpenApiToolSource` remove | Evicts every tool derived from the source. |
| `DynamicMcpServer` update | Adds or replaces the server, fetches its `tools/list` straight away, and materializes the tools. Replacing a server evicts its previous tools. A failed fetch NACKs the update. |
| `DynamicMcpServer` remove | Removes the server and evicts every tool materialized from it. |
| `Toolkit` update | Adds or replaces the toolkit in every code-mode registry of the scope. |
| `Toolkit` remove | Removes the toolkit. |

Malformed resource IDs and undecodable or invalid payloads are NACKed. An update or remove for a scope with no live gateway is ACKed as a no-op, so a control plane can publish resources before the matching listener exists.
