tooluniverse-custom-tool について
ToolUniverse にカスタムツールを追加する開発者向けのガイドです。JSON 設定、Python クラス、インストール可能なプラグインの作成方法に加え、テストと MCP 経由での提供方法を説明します。
主な機能
- 01JSON 設定による REST ツール定義
- 02Python クラスによる独自ロジックの実装
- 03プラグインのパッケージ化と配布
- 04tu test によるツール検証
- 05tu serve によるツール提供
活用シーン
- 01ToolUniverse 未対応の API への接続
- 02非公開プロジェクト用のローカルツール作成
- 03pip で導入可能なツールパッケージの作成
MCP 対応
ワークスペースのツールは tu serve を通じて MCP クライアントに提供できます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mims-harvard/ tooluniverse --skill tooluniverse-custom-toolインストール後、Claude Code で/tooluniverse-custom-toolを入力して読み込まれたか確認します。
同梱ファイルreferences/json-tool.mdreferences/python-tool.md
詳細
ToolUniverseへのカスタムツールの追加
カスタムツールを作成するタイミング: ToolUniverseでカバーされていないAPIにアクセスする必要がある場合や、既存のツールでは提供されていない専用のデータ変換が必要な場合に作成します。まずはJSON設定の方法(最も簡単で、Pythonは不要です)から始め、カスタムのレスポンス解析や状態を保持するロジックが必要な場合にのみ、Pythonクラスに移行してください。
ツールを追加する方法は3つあります。ニーズに合ったものを選んでください。
| 方法 | 使用する場面 |
|---|---|
| JSON設定 | 標準的なリクエスト/レスポンスを使うREST API。コーディングは不要です |
| Pythonクラス(ワークスペース) | ローカルまたはプライベートな用途に限ったカスタムロジック |
| プラグインパッケージ | 共有したり、pip経由でインストールしたりする再利用可能なツール |
オプションA — ワークスペースツール(ローカルでの使用)
.tooluniverse/tools/ 内のツールは起動時に自動検出されます。インストールは不要です。
mkdir -p .tooluniverse/tools
JSON設定
.tooluniverse/tools/my_tools.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"]
}
}
]
1つのJSONファイルで複数のツールを定義できます。配列にオブジェクトを追加してください。
JSONフィールドの完全なリファレンスについては、references/json-tool.md を参照してください。
Pythonクラス
.tooluniverse/tools/my_tool.py を作成します。
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()}
注:ワークスペースのPythonツールでは run(self, **named_params) を使用します。引数は展開され、input_schema のプロパティに対応するキーワード引数として渡されます。
Pythonクラスの完全なリファレンスについては、references/python-tool.md を参照してください。
ワークスペースツールをテストする
# 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 は呼び出しのたびに、以下のチェックを自動的に実行します。
- 結果がNoneでも空でもないこと
return_schemaの検証 —return_schemaが存在する場合、result["data"]がそこで定義されたJSON Schemaに適合するかを検証しますexpect_statusとexpect_keys— 設定ファイルで指定されている場合のみ
注意点: (1)tu test は空でない結果かどうかを検証しません。[] でもスキーマ検証に合格します。実データを返す test_examples の引数を使用してください。(2)まず urllib(curl ではありません)を使って test_examples を手動で検証し、APIがHTMLではなくJSONを返すことを確認してください。幅広いキーワードを2~4個使用してください。
最も幅広く検証できるように、JSON設定に test_examples と return_schema を追加してください。tu test は result["data"] を return_schema に照らして検証します。データの形状に合わせて "type": "array" または "type": "object" を指定してください。
追加のアサーション(expect_status、expect_keys)には、任意で my_tool_tests.json を使用できます。
MCP サーバーで使用する
.tooluniverse/tools/ 内のツールは tu serve から自動的に利用できます。ワークスペースの優先順位は、--workspace フラグ → TOOLUNIVERSE_HOME 環境変数 → ./.tooluniverse/ → ~/.tooluniverse/ です。
別のツールディレクトリを使用するには、.tooluniverse/profile.yaml に sources: [./my-custom-tools/] を追加し、tooluniverse --load .tooluniverse/profile.yaml を指定して起動します。
オプションB — プラグインパッケージ(共有可能、pipでインストール可能)
ツールを再利用可能なPythonパッケージとして配布し、他のユーザーが pip install でインストールできるようにする場合に使用します。プラグインパッケージのディレクトリ構成はワークスペースと同じですが、エントリーポイントを宣言する pyproject.toml が必要です。
パッケージの構成
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設定ファイルは、data/ とパッケージのルートディレクトリの両方から検出されます。慣例では data/ を使用します。
pyproject.toml のエントリーポイント
[project.entry-points."tooluniverse.plugins"]
my-tools = "my_tools_package"
値(my_tools_package)には、インポート可能なPythonパッケージ名を指定する必要があります。
プラグインパッケージ内のPythonクラス
プラグインパッケージのツールは BaseTool を使用し、すべての引数を単一の Dict として受け取ります。
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)}
ワークスペースのパターンとの主な違い:
BaseTool(tooluniverse.base_toolからインポート)を継承します@register_tool("ClassName")には、クラス名を文字列の引数として渡しますrun(self, arguments: Dict)はすべての引数を単一の辞書で受け取ります。.get()を使って値を取り出します__init__はtool_config辞書を受け取ります。最初にsuper().__init__(tool_config)を呼び出します
プラグインパッケージ内のJSON設定
設定ファイルを data/my_api_tools.json に配置します。"type" フィールドには、@register_tool(...) に渡した文字列を指定する必要があります。
[
{
"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
最小限にし、docstring だけを記述します。プラグインシステムは _discover_entry_point_plugins() を介してすべての .py ファイルを自動インポートするため、@register_tool デコレーターは自動的に実行されます。任意で、IDE のサポート用に from . import my_api_tool を追加できます(冪等です)。ここに登録ロジックや JSON の読み込みを追加しないでください。
インストールと検証
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"}'
最初に必ず pip install -e を実行してください。プラグインのリポジトリディレクトリから tu test を実行します(ワークスペースの自動検出には .tooluniverse/ が必要です)。設定不要のテストを行うには、JSON 設定に test_examples を追加してください。tu info MyAPI_search を使って、ツールが読み込まれたことを確認します。
オフライン/純粋な計算を行うツール
Calculator ツール(HTTP を使用しないツール)は、プラグインパッケージのパターンに従いますが、HTTP 層は省略します。主な設計パターンは次のとおりです。
- プリセット検索テーブル:モジュールレベルで
Dict[str, float]を定義します。解決の優先順位は、明示的な値 → プリセット名 → デフォルトです。見つけやすいように、プリセットをmetadataに含めます。 - 双方向の方程式:1 つのツール内で、別々の
operation値として公開します。JSON 設定では"fields": {"operation": "default_op"}を使用します。 - 物理定数:モジュールレベルで定義します(
_MU0 = 4*pi*1e-7など)。材料固有の値は名前付き辞書として定義します。 - 複数の出力:複数回の呼び出しを強制するのではなく、関連するすべての結果(例:温度、ヘッドルーム、合否)を
dataで返します。
パターンの詳細は、references/python-tool.md を参照してください。