Industrial‑Grade Dynamic Tool Registration, Discovery, and Injection for AI Agents

The article presents an industrial‑level architecture for AI agents that enables tools to be registered, discovered, and injected dynamically, covering both local plugins and remote MCP services, with a unified registry, multi‑mode injection strategies, fault‑tolerant discovery mechanisms, and detailed code examples.

Tech Freedom Circle
Tech Freedom Circle
Tech Freedom Circle
Industrial‑Grade Dynamic Tool Registration, Discovery, and Injection for AI Agents

Top‑Level Architecture and Core Design Goals

The system introduces a three‑way mechanism—dynamic registration, dynamic discovery, and dynamic injection—managed by a unified ToolRegisterCenter. It supports local file‑based tools and remote MCP (Model Context Protocol) tools, eliminating hard‑coded tool lists and enabling zero‑code, no‑restart tool updates.

Core Design Goals

(1) Dynamic Registration : support file‑based addition, API‑push registration, and MCP service configuration without modifying Agent core code.

(2) Dynamic Discovery : automatic scan at startup, periodic polling, and event‑driven updates for new, removed, or changed tools.

(3) Dynamic Injection : avoid injecting the entire tool set into LLM context; provide global persistent, intent‑driven temporary, permission‑filtered, and session‑specified injection modes.

(4) Unified Tool Object : both local and MCP tools are wrapped as BaseTool objects, making the Agent decision layer agnostic to the tool source.

(5) Full Lifecycle Management : enable/disable, gray‑release, permission binding, circuit‑break, version rollback, and audit capabilities.

Architecture diagram
Architecture diagram

Five Core Modules

ToolRegisterCenter : in‑memory primary registry with persistent storage, holding metadata, schema, source type, group tags, permission whitelist, and status flags.

LocalToolPluginLoader : scans the plugin directory, parses annotations, instantiates tools, and registers them.

MCPClientPool : maintains long‑lived connections to remote MCP servers, pulls tool lists via RPC, and registers them.

ToolOrchestrator (Function Call Gateway) : validates parameters, routes calls to local or MCP implementations, and standardises responses.

AgentInjector : selects a subset of tools per session based on global persistence, intent matching, user role, or explicit specification.

Module diagram
Module diagram

Global Flow

Tool addition → registration center stores metadata → discovery module updates in‑memory index → injector selects tools for the current session → Function‑Calling schema is sent to the LLM → Agent invokes the gateway → gateway routes to local executor or MCP client → result is standardised and returned → temporary tools are reclaimed after the session ends.

Each step includes fault‑tolerance: missing tools return a standard error, MCP timeouts trigger retries, and any single tool failure does not affect the whole Agent.

Flow diagram
Flow diagram

Local Tool Dynamic Registration, Discovery, and Injection

Tools are placed under a fixed plugin root (e.g., ./tools/plugins/). Developers create a Python file, inherit BaseLocalTool, and add the @register_tool decorator with group and permission metadata. No changes to the main codebase are required.

# base_tool.py – core parent class
from pydantic import BaseModel
from functools import wraps

class ToolRegisterCenter:
    _instance = None
    def __new__(cls):
        if not cls._instance:
            cls._instance = super().__new__(cls)
            cls.tool_registry: dict[str, ToolMeta] = {}
            cls.group_index: dict[str, list[str]] = {}
            cls.version: int = 0
        return cls._instance
    def register_tool(self, meta: ToolMeta):
        if meta.name in self.tool_registry:
            raise Exception(f"工具{meta.name}重复注册")
        self.tool_registry[meta.name] = meta
        self.group_index.setdefault(meta.group, []).append(meta.name)
        self.version += 1
    def unregister_tool(self, name: str):
        if name in self.tool_registry:
            meta = self.tool_registry.pop(name)
            self.group_index[meta.group].remove(name)
            self.version += 1

class ToolMeta(BaseModel):
    name: str
    description: str
    schema: dict
    tool_cls: type
    source: str = "local"
    group: str
    enable: bool = True
    permission: list[str]
    meta_config: dict

def register_tool(group: str, permission: list):
    def decorator(cls):
        @wraps(cls)
        def wrapper(*args, **kwargs):
            return cls(*args, **kwargs)
        tool_schema = cls.build_schema()
        tool_meta = ToolMeta(
            name=cls.tool_name,
            description=cls.tool_desc,
            schema=tool_schema,
            tool_cls=cls,
            group=group,
            permission=permission,
        )
        ToolRegisterCenter().register_tool(tool_meta)
        return wrapper
    return decorator

class BaseLocalTool:
    tool_name: str
    tool_desc: str
    @classmethod
    def build_schema(cls) -> dict:
        raise NotImplementedError
    def run(self, params: dict) -> dict:
        raise NotImplementedError

A sample tool implementation:

# ./tools/plugins/text/text_summary.py
from base_tool import BaseLocalTool, register_tool

@register_tool(group="local_text", permission=["common_user", "admin"])
class TextSummaryTool(BaseLocalTool):
    tool_name = "text_extract_summary"
    tool_desc = "对长文本进行精简摘要,仅用于文本压缩,不可用于数据库、文件、网络操作;闲聊场景禁止调用"
    @classmethod
    def build_schema(cls):
        return {
            "type": "function",
            "function": {
                "name": cls.tool_name,
                "description": cls.tool_desc,
                "strict": True,
                "parameters": {
                    "type": "object",
                    "required": ["content"],
                    "additionalProperties": False,
                    "properties": {
                        "content": {"type": "string", "maxLength": 5000, "description": "待压缩原文"}
                    }
                }
            }
        }
    def run(self, params: dict):
        raw_text = params["content"]
        summary = raw_text[:300] + "......"
        return {"ok": True, "data": {"summary": summary}}

During startup, LocalToolPluginLoader recursively imports all .py files under the plugin root, triggering the decorator to auto‑register each tool. Runtime hot‑updates are handled by watchdog watching the directory: new files are imported, modified files are re‑loaded (clearing sys.modules cache), and deleted files are marked enable=False in the registry.

Injection Strategies

Mode 1 – Global Persistent Injection : tools in group local_core are loaded once at Agent init and attached to every conversation (e.g., time retrieval, text cleaning).

Mode 2 – Intent‑Driven Temporary Injection : a lightweight intent classifier (few‑shot prompt or rule‑based BERT) predicts intent tags, fetches matching groups, merges with global tools, and injects the resulting schema for the current turn only.

Mode 3 – Permission‑Filtered Injection : before injection, the user’s role is checked against each tool’s whitelist; unauthorized tools are excluded.

Mode 4 – Session‑Specified Injection : front‑end or orchestration layer explicitly lists tool names; the injector retrieves exactly those schemas, useful for multi‑Agent collaboration.

# Example of intent‑driven injection
class AgentInjector:
    def __init__(self):
        self.global_fixed_tools = []
        self.center = ToolRegisterCenter()
        fixed_names = self.center.group_index.get("local_core", [])
        for name in fixed_names:
            meta = self.center.tool_registry[name]
            if meta.enable:
                self.global_fixed_tools.append(meta.schema)
    def inject_by_intent(self, user_query: str, user_role: str) -> list[dict]:
        classifier = LightweightIntentClassifier()
        intent_tags = classifier.predict(user_query)
        inject_schemas = self.global_fixed_tools.copy()
        for tag in intent_tags:
            for name in self.center.group_index.get(tag, []):
                meta = self.center.tool_registry[name]
                if meta.enable and user_role in meta.permission:
                    inject_schemas.append(meta.schema)
        return deduplicate_schema(inject_schemas)

Remote MCP Tool Dynamic Registration, Discovery, and Injection

MCP services expose tools via a JSON‑RPC list_tools method. The Agent’s MCPClientPool reads a .mcp_servers.json configuration, establishes long‑lived connections, and registers each remote tool as a ToolMeta with a source prefix mcp:{server_id}. The same registration center is used, so injection logic is identical for local and remote tools.

# Sample MCP server config
{
  "mcp_servers": {
    "file_operator": {
      "type": "stdio",
      "command": ["python", "./mcp_server/file_server.py"],
      "group": "mcp_file",
      "permission": ["admin"],
      "auto_reconnect": true,
      "heartbeat_interval": 10
    },
    "order_query": {
      "type": "http",
      "url": "http://127.0.0.1:8002/mcp/v1",
      "group": "mcp_order",
      "permission": ["user", "admin"],
      "timeout": 5000
    }
  }
}

Discovery mechanisms:

Heartbeat every 10 s; on reconnection the client re‑issues list_tools and syncs the registry.

Periodic polling (default every 60 s) to detect added, removed, or changed tools.

Configuration hot‑reload via Nacos; new server entries trigger async connection creation and registration.

Injection reuses the same AgentInjector logic; the only difference is the routing branch in ToolOrchestrator that extracts the server ID from meta.source and forwards the call via the appropriate MCP client.

# MCP routing branch (simplified)
elif meta.source.startswith("mcp:"):
    server_id = meta.source.split(":")[1]
    mcp_client = self.mcp_client_pool.client_map.get(server_id)
    if not mcp_client:
        return {"ok": False, "error": "mcp_server_offline"}
    try:
        resp = await mcp_client.rpc_call("call_tool", {
            "name": meta.name.split(f"{server_id}_")[1],
            "arguments": params
        })
        return {"ok": True, "source": meta.source, "data": resp, "error": ""}
    except Exception as e:
        return {"ok": False, "error": "rpc_call_failed", "msg": str(e)}

Hallucination Suppression and Safety Controls

Hard limit of 15 tools per injection; excess tools are trimmed by intent‑match confidence.

Tools marked enable=False are filtered out immediately, enabling emergency circuit‑break.

Permission whitelist ensures only authorized roles can invoke privileged tools (e.g., file deletion).

Each tool schema must contain a description of prohibited usage, which is fed to the LLM prompt to reduce unwanted calls.

Injection logs record the tool list, injection reason, and session ID for post‑mortem analysis.

Negative‑sample injection (explicit "do not call these tools") further reduces hallucinations.

Full‑Link Fault Tolerance

Duplicate tool names raise an exception during registration, preventing silent overwrites.

If an MCP server becomes unreachable, its tools are excluded from injection and a standard hint is returned to the LLM.

Local plugin syntax errors are caught; the offending file is skipped and an alert is emitted without crashing the Agent.

Parameter validation against the JSON schema occurs before any remote call, preventing malformed requests from consuming RPC resources.

Temporary injection lifecycles are cleared after the conversation ends to avoid memory leaks.

Execution timeouts (500 ms for local tools, 3000 ms for MCP tools) trigger circuit‑break and return a timeout error.

Fault tolerance diagram
Fault tolerance diagram

Interview‑Focused Structured Answer Outline

The architecture can be explained in four parts during an interview:

Overall top‑level design: unified registry, local loader, MCP client pool, function‑call gateway, and injector.

Local tool flow: directory scanning → annotation‑driven registration → watchdog‑based hot‑update.

Remote MCP flow: handshake → list_tools → incremental sync via heartbeat, polling, and config hot‑reload.

Injection & safety: four injection modes, hard limits, permission checks, and logging for hallucination mitigation.

This concise structure lets candidates demonstrate both architectural thinking and concrete implementation details.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ArchitectureMCPAI AgentFunction CallingDynamic Tool RegistrationTool Injection
Tech Freedom Circle
Written by

Tech Freedom Circle

Crazy Maker Circle (Tech Freedom Architecture Circle): a community of tech enthusiasts, experts, and high‑performance fans. Many top‑level masters, architects, and hobbyists have achieved tech freedom; another wave of go‑getters are hustling hard toward tech freedom.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.