Skip to content

Tool discovery over xDS (TDS)

Deliver MCP tools, OpenAPI sources, dynamic servers and code-mode toolkits from a control plane: scopes, resource types, and update and remove rules.

TDS, the Tool Discovery Service, lets a control plane add, update and remove MCP tools, OpenAPI tool sources, dynamic MCP servers and code-mode toolkits over xDS. The listener, routes, TLS and the rest of the filter chain stay untouched. Use it for long-lived gateways whose tool catalogs change far more often than their network topology.

Enable it

Bind the gateway to a TDS stream by setting tds.config_name in the filter's typed_config. The proxy must be running with an xDS control plane:

server_info:
  name: tenant-a-mcp
  version: "1.0.0"
tools_mode: {}
tds:
  config_name: main

Static tools, open_api_tool_sources and dynamic_mcp_servers can coexist with TDS; delivered resources join them in the same registry.

Scopes and resource IDs

Each TDS-enabled gateway consumes resources for one scope, formed as <server_info.name>/<tds.config_name>, here tenant-a-mcp/main. Neither part may contain /. A resource ID appends the resource name, which is the resource's identity:

Resource typeResource nameExample ID
ToolThe qualified name, namespace.nametenant-a-mcp/main/weather.get_forecast
OpenApiToolSourceThe source's namespacetenant-a-mcp/main/petstore
DynamicMcpServerThe server's namespacetenant-a-mcp/main/github
ToolkitThe toolkit name (code mode only)tenant-a-mcp/main/resolve_support_request

TDS uses incremental (delta) xDS with four extension resource types:

type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.Tool
type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.OpenApiToolSource
type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.DynamicMcpServer
type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.Toolkit

The resource name always wins. If a payload's own name or namespace differs from it, Arion uses the resource name, and logs a warning for tools and sources, so a later remove targets the same key.

Update and remove semantics

  • Tool update. It passes the same validation as static config, then adds or replaces the tool. It can replace a static tool with the same qualified name. It is rejected if an OpenAPI source or a dynamic server owns the namespace.
  • Tool remove. It deletes the tool, and a static tool it replaced does not come back. Tools derived from an OpenAPI source or discovered from a dynamic server cannot be removed this way.
  • OpenApiToolSource update. The document is expanded when the resource arrives. The namespace's tools are replaced with the new set, and an invalid document is NACKed with the previous tools still serving.
  • OpenApiToolSource remove. It evicts every tool derived from the source.
  • DynamicMcpServer update. It adds or replaces the server and fetches its tools/list at once, materializing the tools under the server's namespace. If that fetch fails, the update is NACKed.
  • DynamicMcpServer remove. It evicts the server and everything discovered from it.
  • Toolkit update and remove. These add, replace or delete a code mode toolkit. Gateways in tools mode ignore toolkits and ACK them, so one control plane can serve both kinds of listener.
  • Rejection. Malformed resource IDs and invalid payloads are NACKed.
  • Early publishing. Updates for scopes no gateway consumes are ACKed as no-ops, so a control plane can publish resources before the matching listener exists.
  • Embeddings. If remote tool search embeddings are configured, a new tool without a supplied vector is embedded on the next search. An embedding failure never rejects an update.

Scope isolation

Two listeners may share a config_name as long as their server names differ. tenant-a-mcp/main/crm.lookup updates tenant A only, and tenant B never sees it:

# listener A's gateway
server_info:
  name: tenant-a-mcp
  version: "1.0.0"
tools_mode: {}
tds:
  config_name: main

# listener B's gateway
server_info:
  name: tenant-b-mcp
  version: "1.0.0"
code_mode: {}
tds:
  config_name: main

To keep scopes unambiguous, Arion rejects duplicate server_info.name values across TDS-enabled gateways in the same runtime.

Verify

After the control plane publishes a Tool resource named tenant-a-mcp/main/weather.get_forecast, search for it on that listener:

curl -s http://127.0.0.1:8080/mcp/search \
  -H 'content-type: application/json' \
  -d '{"query":"forecast","namespace":"weather"}'

The answer now includes weather.get_forecast, and removing the resource drops it again. On a code-mode listener, run catalog.tools.list({ namespace: "weather" }) through execute instead.

Limits

  • TDS is delta xDS only, and it carries only the four MCP resource types. Listener and route changes still go through their own xDS types.
  • Resource names may contain /; server_info.name and config_name may not.
  • Each namespace has one owner. A Tool cannot be added to a namespace that belongs to an OpenAPI source or a dynamic server.

Payload fields for every resource type are listed in the TDS resource reference.

Continue

Start typing to search the site.

↑ ↓ to move Enter to open Esc to close