Skip to content
Blog

The "Tool-Discovery" Protocol: How Gemini 3 Agents Find and Map New Capabilities

Build truly autonomous, self-expanding AI applications using the Tool-Discovery Protocol. Learn how Gemini 3 agents dynamically browse, map, and bind new API capabilities on the fly.

Published on 2026-06-01

AI Assistant

Most modern AI agents are built with static tools. A developer registers a set of functions (e.g., send_email, read_database) at startup, and the agent uses them. However, in complex enterprise environments, requirements change rapidly. If an agent needs to perform a new action, developers must write new code, register the tool, and redeploy the app.

With Gemini 3, we can move past static setups using the Tool-Discovery Protocol. Instead of hardcoding capabilities, we equip the agent with “meta-tools” that allow it to scan directories, parse OpenAPI specifications, or look up MCP (Model Context Protocol) endpoints. The agent dynamically discovers, verifies, maps, and executes new tools on the fly—becoming a self-expanding autonomous engine.

In this tutorial, we will build a Python agent that dynamically reads OpenAPI definitions from a local folder and registers them as usable tools at runtime.


Prerequisites

  • Python 3.10+
  • Google Gemini API Key configured
  • urllib3 or requests for dynamic API execution

Install the required modules:

pip install google-genai requests pyyaml dotenv

Make sure to create a .env file:

GEMINI_API_KEY=your_gemini_3_api_key

Architecture of Tool-Discovery

1. [Agent receives task]
2. [No suitable tool loaded] -> Trigger [Tool Discovery Meta-Tool]
3. [Scan OpenAPI specs / MCP servers]
4. [Compile new spec into schema]
5. [Hot-bind tool to current session] -> Execute!

Step 1: Writing the Dynamic Tool Discovery Agent

Create a file named dynamic_agent.py. This script sets up a directory containing tool specifications and creates a controller that loads them into the Gemini agent context dynamically.

import os
import json
import yaml
import requests
from dotenv import load_dotenv
from google import genai
from google.genai import types

load_dotenv()

# Initialize Gemini Client
client = genai.Client()

# Create a local registry directory for new tool specs
REGISTRY_DIR = "./tool_registry"
os.makedirs(REGISTRY_DIR, exist_ok=True)

# 1. Define a dummy schema/API spec to discover (e.g. currency converter API)
SAMPLE_SPEC = {
    "openapi": "3.0.0",
    "info": {"title": "Currency Converter Service", "version": "1.0.0"},
    "paths": {
        "/convert": {
            "get": {
                "summary": "Convert values between currencies",
                "parameters": [
                    {"name": "amount", "in": "query", "required": True, "schema": {"type": "number"}},
                    {"name": "from_currency", "in": "query", "required": True, "schema": {"type": "string"}},
                    {"name": "to_currency", "in": "query", "required": True, "schema": {"type": "string"}}
                ]
            }
        }
    }
}

with open(os.path.join(REGISTRY_DIR, "currency_service.json"), "w") as f:
    json.dump(SAMPLE_SPEC, f, indent=2)


# 2. Meta-Tool: Let the agent list and discover available specifications
def discover_tools() -> list[str]:
    """Scans the registry folder and lists discovered API tool definitions."""
    print("[Discovery Protocol] Scanning registry for new capabilities...")
    files = [f for f in os.listdir(REGISTRY_DIR) if f.endswith('.json') or f.endswith('.yaml')]
    return [os.path.join(REGISTRY_DIR, f) for f in files]

def read_tool_specification(file_path: str) -> str:
    """Reads a specific API definition file to extract parameters and endpoints."""
    print(f"[Discovery Protocol] Reading spec file: {file_path}")
    with open(file_path, 'r') as f:
        return f.read()

Step 2: Setting up Dynamic Execution

Once the agent selects an API spec, it needs to execute it. We write a generic runtime function that takes the endpoint details and parameters defined in the spec and makes the network request.

Append the following agent controller to dynamic_agent.py:

# 3. Dynamic Execution Runtime
def execute_dynamic_tool(base_url: str, endpoint: str, method: str, params: dict) -> dict:
    """Generic runtime executor for dynamically discovered API tools."""
    print(f"[Execution Engine] Invoking {method.upper()} {base_url}{endpoint} with params {params}")
    
    # For testing purposes, we mock the real API response
    if "convert" in endpoint:
        amount = params.get("amount", 1)
        return {"status": "success", "converted_value": float(amount) * 35.2, "currency": "THB"}
        
    return {"status": "error", "message": "Endpoint not implemented"}

def run_agent_loop(user_prompt: str):
    # Register the Meta-Tools (discovery tools)
    meta_tools = [discover_tools, read_tool_specification, execute_dynamic_tool]
    
    print(f"User Request: '{user_prompt}'")
    
    # Create the agent session
    response = client.models.generate_content(
        model="gemini-3-flash",
        contents=user_prompt,
        config=types.GenerateContentConfig(
            tools=meta_tools,
            system_instruction=(
                "You are an adaptive AI agent with self-improving capabilities. "
                "If you need to perform an action but do not have the direct tools, "
                "call `discover_tools` to find new API specifications, read them with `read_tool_specification`, "
                "and then run the target tool using `execute_dynamic_tool`."
            )
        )
    )
    
    # Process the function calls (tool loop)
    for function_call in response.function_calls:
        print(f"\n[Agent Decision] Executing Tool: {function_call.name}")
        # Map and run the matched python function
        if function_call.name == "discover_tools":
            res = discover_tools()
            print(f"[Result] {res}")
        elif function_call.name == "read_tool_specification":
            res = read_tool_specification(**function_call.args)
            print(f"[Result] Retrieved API Spec")
        elif function_call.name == "execute_dynamic_tool":
            res = execute_dynamic_tool(
                base_url="https://api.example.com",
                endpoint="/convert",
                method="get",
                params=function_call.args.get("params", {})
            )
            print(f"[Result] Response Payload: {res}")
            
if __name__ == "__main__":
    run_agent_loop("Convert 100 USD to THB. You might need to discover what APIs are available in the registry folder first.")

Step 3: Running the Dynamic Agent

Run the script to watch the Tool-Discovery workflow unfold:

python dynamic_agent.py

Protocol Workflow Details

Watch your logs closely:

  1. Scanning: The agent realizes it has no currency tool, so it calls discover_tools to list json files.
  2. Parsing: It parses currency_service.json via read_tool_specification, discovering the /convert endpoint and its parameters.
  3. Execution: It maps the parameters and runs the dynamic network caller execute_dynamic_tool with the correct argument models.

Summary

The Tool-Discovery Protocol allows your AI applications to scale and adapt on their own. Instead of shipping code updates, you simply drop new OpenAPI or Model Context Protocol configurations into a directory, and the agent automatically learns how to use them to solve its goals.