mims-harvard/tooluniverse/tooluniverse-custom-tool

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Add custom local tools to ToolUniverse alongside the 1000+ built-in tools. Covers JSON-config tools (simplest, no code), Python class tools (REST/SOAP/GraphQL APIs, computational logic), and best-practices for return schemas. Use for wrapping new APIs, adding domain-specific computations, or contributing tools to the registry.

Install

npx vskill@latest install mims-harvard/tooluniverse/tooluniverse-custom-tool

SKILL.md

Adding Custom Tools to ToolUniverse

When to create a custom tool: Create one if you need to access an API that ToolUniverse doesn't cover, or if you need a specialized data transformation that no existing tool provides. Start with the JSON config approach (simplest — no Python needed); escalate to a Python class only if you need custom response parsing or stateful logic.

Three ways to add tools — pick the one that fits your needs:

ApproachWhen to use
JSON configREST API with standard request/response — no coding needed
Python class (workspace)Custom logic for local/private use only
Plugin packageReusable tools you want to share or install via pip

Option A — Workspace tools (local use)

Tools in .tooluniverse/tools/ are auto-discovered at startup. No installation needed.

bash
mkdir -p .tooluniverse/tools
JSON config

Create .tooluniverse/tools/my_tools.json:

json
[
{
"name": "MyAPI_search",
"description": "Search my internal database. Returns matching records with id, title, and score.",
"type": "BaseRESTTool",
"fields": {
"endpoint": "https://my-api.example.com/search"
},
"parameter": {
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": ["integer", "null"],
"description": "Max results to return (default 10)"
}
},
"required": ["q"]
}
}
]

One JSON file can define multiple tools — just add more objects to the array.

For the full JSON field reference, see references/json-tool.md.

Python class

Create .tooluniverse/tools/my_tool.py:

python
from tooluniverse.tool_registry import register_tool
@register_tool
class MyAPI_search:
name = "MyAPI_search"
description = "Search my internal database. Returns matching records with id, title, and score."
input_schema = {
"type": "object",
"properties": {
"q": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "description": "Max results (default 10)"}
},
"required": ["q"]
}
def run(self, q: str, limit: int = 10) -> dict:
import requests
resp = requests.get(
"https://my-api.example.com/search",
params={"q": q, "limit": limit},
timeout=30,
)
resp.raise_for_status()
return {"status": "success", "data": resp.json()}

Note: workspace Python tools use run(self, **named_params) — arguments are unpacked as keyword arguments matching the input_schema properties.

For the full Python class reference, see references/python-tool.md.

Test workspace tools
bash
# Uses test_examples from the tool's JSON config — zero config needed
tu test MyAPI_search
# Single ad-hoc call
tu test MyAPI_search '{"q": "test"}'
# Full config with assertions
tu test --config my_tool_tests.json

tu test automatically runs these checks on every call:

  • Result is not None or empty
  • return_schema validation — validates result["data"] against the JSON Schema defined in return_schema (if present)
  • expect_status and expect_keys — only if set in the config file

Gotchas: (1) tu test does NOT verify non-empty results — [] passes schema validation. Use test_examples args that return real data. (2) Verify test_examples manually first with urllib (not curl) to confirm the API returns JSON, not HTML. Use 2-4 broad keywords.

Add test_examples and return_schema to JSON config for best coverage. tu test validates result["data"] against return_schema (match "type": "array" or "type": "object" to your data shape).

Optional my_tool_tests.json for extra assertions (expect_status, expect_keys).

Use with MCP server

Tools in .tooluniverse/tools/ are auto-available via tu serve. Workspace priority: --workspace flag → TOOLUNIVERSE_HOME env → ./.tooluniverse/ → ~/.tooluniverse/.

To use a different tools directory, add sources: [./my-custom-tools/] in .tooluniverse/profile.yaml and start with tooluniverse --load .tooluniverse/profile.yaml.


Option B — Plugin package (shareable, pip-installable)

Use this when you want to distribute tools as a reusable Python package that other users can install with pip install. The plugin package has the same directory layout as a workspace, plus a pyproject.toml that declares the entry point.

Package layout
my_project_root/ # directory containing pyproject.toml
pyproject.toml
my_tools_package/ # importable Python package (matches entry-point value)
__init__.py # minimal — one-line docstring, no registration code
my_api_tool.py # tool class(es) with @register_tool
data/
my_api_tools.json # JSON tool configs (type must match registered class name)
profile.yaml # optional: name, description, required_env

JSON config files are discovered from both data/ and the package root directory. The convention is data/.

pyproject.toml entry point
toml
[project.entry-points."tooluniverse.plugins"]
my-tools = "my_tools_package"

The value (my_tools_package) must be the importable Python package name.

Python class in a plugin package

Plugin package tools use BaseTool and receive all arguments as a single Dict:

python
import requests
from typing import Dict, Any
from tooluniverse.base_tool import BaseTool
from tooluniverse.tool_registry import register_tool
@register_tool("MyAPITool")
class MyAPITool(BaseTool):
"""Tool description here."""
def __init__(self, tool_config: Dict[str, Any]):
super().__init__(tool_config)
self.timeout = tool_config.get("timeout", 30)
fields = tool_config.get("fields", {})
self.operation = fields.get("operation", "search")
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
query = arguments.get("query", "")
if not query:
return {"error": "query parameter is required"}
try:
resp = requests.get(
"https://my-api.example.com/search",
params={"q": query},
timeout=self.timeout,
)
resp.raise_for_status()
return {"status": "success", "data": resp.json()}
except requests.exceptions.RequestException as e:
return {"error": str(e)}

Key differences from the workspace pattern:

  • Inherit from BaseTool (from tooluniverse.base_tool)
  • @register_tool("ClassName") takes the class name as a string argument
  • run(self, arguments: Dict) receives all arguments in a single dict — extract them with .get()
  • __init__ receives tool_config dict; call super().__init__(tool_config) first
JSON config in a plugin package

Place configs in data/my_api_tools.json. The "type" field must match the string passed to @register_tool(...):

json
[
{
"name": "MyAPI_search",
"description": "Search my API. Returns matching records.",
"type": "MyAPITool",
"fields": { "operation": "search" },
"parameter": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search query" },
"limit": { "type": ["integer", "null"], "description": "Max results" }
},
"required": ["query"]
}
}
]
__init__.py

Keep minimal — just a docstring. The plugin system auto-imports all .py files via _discover_entry_point_plugins(), so @register_tool decorators fire automatically. Optional: add from . import my_api_tool for IDE support (idempotent). Do NOT add registration logic or JSON loading here.

Install and verify
bash
pip install -e /path/to/my_project_root
cd /path/to/my_project_root # MUST run from plugin repo directory
tu test MyAPI_search '{"query": "test"}'

Must pip install -e first. Run tu test from plugin repo dir (workspace auto-detection needs .tooluniverse/). Add test_examples to JSON config for zero-config testing. Use tu info MyAPI_search to confirm the tool loaded.


Offline / pure-computation tools

Calculator tools (no HTTP) follow the plugin-package pattern but skip the HTTP layer. Key design patterns:

  • Preset lookup tables: Define Dict[str, float] at module level. Resolution priority: explicit value → preset name → default. Include presets in metadata for discoverability.
  • Bidirectional equations: Expose as separate operation values in a single tool. Use "fields": {"operation": "default_op"} in JSON config.
  • Physical constants: Define at module level (_MU0 = 4*pi*1e-7, etc.). Material-specific values as named dicts.
  • Multi-output: Return all related results in data (e.g., temperature + headroom + pass/fail) rather than forcing multiple calls.

For complete patterns, see references/python-tool.md.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
1,312
Repository forks
200
7-day trend score
36

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 3 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/mims-harvard/tooluniverse/tooluniverse-custom-tool/badge)](https://verified-skill.com/skills/mims-harvard/tooluniverse/tooluniverse-custom-tool)

Something wrong with this skill or its page? Tell us.