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.
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.
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.
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.
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 NotImplementedErrorA 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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
