Skip to content
intermediate Phase 13 · Multi-Tool Agents and MCP

Designing a Tool Registry

Catalog tools with schemas, versions, and ownership metadata for discovery.

50m
0 problems
Topic Progress 0%

The Tool Sprawl Problem

An agent with forty tools pasted into every prompt is a common failure pattern. The model picks the wrong tool, a provider changes its response shape and the agent breaks at 2 a.m., and nobody knows who owns the broken integration. Teams rebuild the same API wrapper three times because no catalog records that it already exists.

This is tool sprawl: tools multiply across prompts, notebooks, and team wikis with no shared source of truth. The symptoms are consistent:

  • Wrong selection: the model chooses a near-duplicate tool because nothing distinguishes them.
  • Silent breakage: a provider changes its schema; the agent keeps parsing the old shape.
  • No ownership: a failing tool has no accountable team, so it stays broken.
  • Duplicated wrappers: each team integrates the same API independently.

A tool registry attacks all four symptoms at once. It is a single catalog where every tool is a record carrying its schema, description, owner, version, and rate limits. Agents discover tools from the registry instead of hardcoded prompt lists, and every integration has exactly one owner.

Before continuing, make sure you understand defining tools for LLMs — the registry assumes tools already have names, descriptions, and parameter schemas. The registry adds the operational metadata around them.

Once the registry exists, the next problem is keeping selection accurate as the catalog grows. That is the subject of tool selection under many options.

Anatomy of a Registry Record

Every tool in the registry is one record. The record has to answer four questions: what does the tool do, what does it accept, who owns it, and how often may it be called?

from dataclasses import dataclass, field
from typing import Any

@dataclass
class ToolRecord:
    name: str
    description: str
    input_schema: dict[str, Any]
    owner: str
    version: str
    rate_limit_per_min: int
    domains: list[str] = field(default_factory=list)

The registry itself is a thin store around these records:

class ToolRegistry:
    def __init__(self) -> None:
        self._tools: dict[str, ToolRecord] = {}

    def register(self, record: ToolRecord) -> None:
        if record.name in self._tools:
            raise ValueError(f"{record.name} is already registered")
        self._tools[record.name] = record

    def discover(self, domains: list[str]) -> list[ToolRecord]:
        return [t for t in self._tools.values() if set(t.domains) & set(domains)]

discover is the load-bearing method: an agent asks for the tools in its current domain and gets a scoped subset instead of the full catalog.

Field Purpose Example
name Unique identifier the model sees get_weather
description When to use the tool, written for the model Current conditions for a city
input_schema JSON Schema for arguments {type: object, required: [city]}
owner Team accountable for the tool payments-team
version Semantic version of the schema 1.2.0
rate_limit_per_min Throttle before the provider rejects calls 60
domains Groups for scoped discovery [weather, travel]

Two fields deserve emphasis. description is read by the model on every selection decision, so it carries the same weight as the schema itself — see defining tools for LLMs for what makes one effective. owner is what makes breakage fixable: when a tool fails, the registry routes the incident to a team instead of a wiki page nobody reads.

Versioning Schemas and Grouping by Domain

A schema that changes without a version is a schema that breaks agents silently. Treat the input schema like a public API and version it with semantic versioning.

Change Version bump Example
Add an optional field minor New units parameter with a default
Remove a field the model never used minor Drop a deprecated parameter
Add a required field major Make city required where it was optional
Narrow a type or enum major string becomes enum[metric, imperial]

The check that matters is whether prompts written against the old schema still work against the new one:

def is_backward_compatible(old_schema: dict, new_schema: dict) -> bool:
    old_required = set(old_schema.get("required", []))
    new_required = set(new_schema.get("required", []))
    return new_required <= old_required

Existing prompts supply everything the old schema required. If the new schema requires more, those prompts break — so the new required set must be a subset of the old one. Anything else is a major bump, and the registry should refuse to register it under the same version.

Domain grouping solves a different problem: an agent handling refunds should not see the weather tools. Tag every record with domains and let discover filter, as shown in the previous chapter. This keeps the model's decision space small, which directly improves selection accuracy — the core idea behind tool selection under many options.

Validating at the Boundary

Providers drift. A tool that returned temp_c last quarter returns temperature this quarter, and the agent parses None until someone notices. The registry's last job is to catch that drift at the boundary, before it reaches the model.

Declare an output schema alongside the input schema, then validate every response against it:

from pydantic import BaseModel, ValidationError

class WeatherOutput(BaseModel):
    temperature: float
    conditions: str

def validate_response(tool_name: str, payload: dict) -> bool:
    output_model = REGISTRY.output_schemas[tool_name]
    try:
        output_model(**payload)
        return True
    except ValidationError:
        return False

When validation fails, the agent receives a structured error instead of garbage, and the tool's owner gets an alert. Input validation matters for the same reason — see JSON handling and Pydantic models for the validation patterns.

Failure modes this boundary check prevents:

  • Silent shape drift: the provider renames a field; the agent keeps running on stale parsing logic.
  • Partial responses: an error page returns HTTP 200 with an HTML body; without schema validation the agent treats it as data.
  • Cascading retries: the model retries a call that was never valid, burning tokens on a response that cannot parse.

A registry without boundary validation is a catalog that lies. The schema is a contract; enforcing it on the way out is what makes the contract real.

Quiz

1. What failure mode does a tool registry primarily prevent?

Question 1 options

2. Which registry field tells the model when to use a tool?

Question 2 options

3. A tool adds a new required parameter. Which version bump is correct?

Question 3 options

4. Why validate tool responses against the declared schema at the boundary?

Question 4 options

Flashcards

Question

What problem does a tool registry solve?

Answer

Tool sprawl: unowned, unversioned tools that break agents silently. A registry catalogs each tool's schema, owner, version, and limits in one place.

Question

What fields does a registry record hold?

Answer

Name, description, input schema, owner, semantic version, rate limit, and domain tags for scoped discovery.

Question

When is a schema change backward compatible?

Answer

When it only removes or relaxes requirements. Adding a required field or narrowing a type is a breaking change that needs a major version bump.

Question

Why validate tool responses at the boundary?

Answer

Providers drift. Validating responses against the declared schema catches shape changes before they corrupt agent logic.

Question

How do domain tags help agents?

Answer

They let an agent load only the tool subset relevant to its current task instead of the full catalog, keeping the decision space small.

Revision Notes

Key Takeaways

  • 1. A registry gives every tool a schema, owner, version, and rate limits so breakage is attributable and preventable
  • 2. The description field is read by the model on every selection decision — it carries the same weight as the schema
  • 3. Schema changes follow semantic versioning: adding a required field is a major bump, not a minor one
  • 4. Validating responses against the declared output schema catches provider drift before it reaches the model

Interview Tips

  • • Open with the symptom: agents breaking silently because tools have no owner and no version
  • • Explain the versioning rule concretely: adding a required field breaks old prompts, so it is a major bump
  • • Mention boundary validation as the mechanism that turns a schema into an enforced contract
  • • Connect the registry to selection accuracy: domain tags shrink the model's decision space

Cheat Sheet

Tool Registry Cheat Sheet

What it is: one catalog where every tool is a record with schema, description, owner, version, rate limits, and domain tags.

Registry record fields:

Field Purpose
name Unique id the model sees
description When to use the tool (read by the model)
input_schema JSON Schema for arguments
owner Accountable team
version Semantic version of the schema
rate_limit_per_min Throttle before provider 429s
domains Scoped discovery groups

Versioning rules:

  • Add optional field → minor
  • Remove unused field → minor
  • Add required field → major (breaking)
  • Narrow a type/enum → major (breaking)
  • Compatible iff new required set ⊆ old required set

Boundary validation:

  • Validate every response against the declared output schema
  • Catch silent shape drift, HTML error pages with HTTP 200, cascading retries
  • Alert the tool owner on validation failure

Failure modes prevented: wrong selection, silent breakage, no ownership, duplicated wrappers

Continue Learning