Create new scientific tools for ToolUniverse framework with proper structure, validation, and testing. Use when users need to add tools to ToolUniverse, implement new API integrations, create tool wrappers for scientific databases/services, expand ToolUniverse capabilities, or follow ToolUniverse contribution guidelines. Supports creating tool classes, JSON configurations, validation, error handling, and test examples.
79
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Create new scientific tools following established patterns.
default_config.py Entry — tools silently won't loadtest_new_tools.py — misses schema/API issuesStage 1: Tool Class Stage 2: Wrappers (Auto-Generated)
@register_tool("MyTool") MyAPI_list_items()
class MyTool(BaseTool): MyAPI_search()
def run(arguments): MyAPI_get_details()One class handles multiple operations. JSON defines individual wrappers. Need BOTH.
Step 1: Class registration via @register_tool("MyAPITool")
Step 2 (MOST COMMONLY MISSED): Config registration in default_config.py:
TOOLS_CONFIGS = {
"my_category": os.path.join(current_dir, "data", "my_category_tools.json"),
}Step 3: Automatic wrapper generation on tu.load_tools()
src/tooluniverse/my_api_tool.py — implementationsrc/tooluniverse/data/my_api_tools.json — tool definitionstests/tools/test_my_api_tool.py — testsfrom typing import Dict, Any
from tooluniverse.tool import BaseTool
from tooluniverse.tool_utils import register_tool
import requests
@register_tool("MyAPITool")
class MyAPITool(BaseTool):
BASE_URL = "https://api.example.com/v1"
def __init__(self, tool_config):
super().__init__(tool_config)
self.parameter = tool_config.get("parameter", {})
self.required = self.parameter.get("required", [])
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
operation = arguments.get("operation")
if not operation:
return {"status": "error", "error": "Missing: operation"}
if operation == "search":
return self._search(arguments)
return {"status": "error", "error": f"Unknown: {operation}"}
def _search(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
query = arguments.get("query")
if not query:
return {"status": "error", "error": "Missing: query"}
try:
response = requests.get(
f"{self.BASE_URL}/search",
params={"q": query}, timeout=30
)
response.raise_for_status()
data = response.json()
return {"status": "success", "data": data.get("results", [])}
except requests.exceptions.Timeout:
return {"status": "error", "error": "Timeout after 30s"}
except requests.exceptions.HTTPError as e:
return {"status": "error", "error": f"HTTP {e.response.status_code}"}
except Exception as e:
return {"status": "error", "error": str(e)}[
{
"name": "MyAPI_search",
"class": "MyAPITool",
"description": "Search items. Returns array of results. Supports Boolean operators. Example: 'protein AND membrane'.",
"parameter": {
"type": "object",
"required": ["operation", "query"],
"properties": {
"operation": {"const": "search", "description": "Operation (fixed)"},
"query": {"type": "string", "description": "Search term"},
"limit": {"type": ["integer", "null"], "description": "Max results (1-100)"}
}
},
"return_schema": {
"oneOf": [
{"type": "object", "properties": {"data": {"type": "array"}}},
{"type": "object", "properties": {"error": {"type": "string"}}, "required": ["error"]}
]
},
"test_examples": [{"operation": "search", "query": "protein", "limit": 10}]
}
]{API}_{action}_{target} template{"status": "error", "error": "..."}{"status": "success|error", "data": {...}}When tool accepts EITHER id OR name, BOTH must be nullable:
{
"id": {"type": ["integer", "null"], "description": "Numeric ID"},
"name": {"type": ["string", "null"], "description": "Name (alternative to id)"}
}Without "null", validation fails when user provides only one parameter.
Common cases: id OR name, gene_id OR gene_symbol, any optional filters.
Optional keys (tool works without, better with):
{"optional_api_keys": ["NCBI_API_KEY"]}self.api_key = os.environ.get("NCBI_API_KEY", "") # Read from env onlyRequired keys (tool won't work without):
{"required_api_keys": ["NVIDIA_API_KEY"]}Rules: Never add api_key as tool parameter for optional keys. Use env vars only.
Full guide: references/testing-guide.md
run(), check responsetu.tools.YourTool_op1(...), check registrationpython scripts/test_new_tools.py your_tool -v → 0 failures# Check all 3 registration steps
python3 -c "
import sys; sys.path.insert(0, 'src')
from tooluniverse.tool_registry import get_tool_registry
import tooluniverse.your_tool_module
assert 'YourToolClass' in get_tool_registry(), 'Step 1 FAILED'
from tooluniverse.default_config import TOOLS_CONFIGS
assert 'your_category' in TOOLS_CONFIGS, 'Step 2 FAILED'
from tooluniverse import ToolUniverse
tu = ToolUniverse(); tu.load_tools()
assert hasattr(tu.tools, 'YourCategory_op1'), 'Step 3 FAILED'
print('All 3 steps verified!')
"python3 -m json.tool src/tooluniverse/data/your_tools.json # Validate JSON
python3 -m py_compile src/tooluniverse/your_tool.py # Check syntax
grep "your_category" src/tooluniverse/default_config.py # Verify config
python scripts/test_new_tools.py your_tool -v # MANDATORY test089eb8e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.