mcpField
D
devtu-create-tool

devtu-create-tool

github.com/mims-harvard開発1.8k388 回インストール2 日前に更新
リポジトリ

devtu-create-tool について

ToolUniverse で科学ツールを実装・登録するためのスキルです。クラス、JSON ラッパー、設定、テストの手順を示し、検証や MCP 互換性に関する注意点も扱います。

主な機能

  1. 01ツールクラスとラッパーの二段階設計
  2. 02三段階のツール登録
  3. 03パラメーターとスキーマの指針
  4. 04多段階テストのチェックリスト
  5. 05API キー設定の指針

活用シーン

  1. 01ToolUniverse への科学 API ツール追加
  2. 02リリース前の登録・スキーマ確認
  3. 03代替識別子を使うパラメーター設計

MCP 対応

MCP 互換性の問題を避けるため、ツール名を 55 文字以内にするよう推奨しています。`tu.load_tools()` 実行時にラッパーが生成されます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mims-harvard/tooluniverse --skill devtu-create-tool

インストール後、Claude Code で/devtu-create-toolを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
advanced-patterns.md
implementation-guide.md
quick-reference.md
testing-guide.md
tool-improvement-checklist.md
templates/
api_tool_template.py
api_tools_config.json
simple_tool_template.py
simple_tools_config.json
test_template.py

詳細

ToolUniverse ツール作成者

確立されたパターンに従って、新しい科学ツールを作成します。

失敗の原因となる上位7つのミス(失敗の90%)

  1. default_config.py エントリーの不足 — ツールが読み込まれず、何も通知されません
  2. 相互排他的なパラメーターを null 非許容にする — 検証エラー(2026年の最大の問題)
  3. 偽の test_examples — テストが失敗し、エージェントに不適切な例が渡されます
  4. 単一レベルのテスト — 登録に関するバグを見逃します
  5. test_new_tools.py の実行を省略する — スキーマ/API の問題を見逃します
  6. ツール名が55文字を超える — MCP との互換性が損なわれます
  7. 例外を発生させる — 代わりにエラーディクショナリーを返す必要があります

2段階アーキテクチャ

Stage 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()

1つのクラスで複数の操作を処理します。個々のラッパーは JSON で定義します。両方が必要です。

3段階の登録

ステップ1:@register_tool("MyAPITool") によるクラスの登録

ステップ2(最も見落とされやすい項目):default_config.py での設定の登録:

TOOLS_CONFIGS = {
    "my_category": os.path.join(current_dir, "data", "my_category_tools.json"),
}

ステップ3:tu.load_tools() の実行時にラッパーを自動生成


実装ガイド

作成するファイル
  • src/tooluniverse/my_api_tool.py — 実装
  • src/tooluniverse/data/my_api_tools.json — ツール定義
  • tests/tools/test_my_api_tool.py — テスト
Python ツールクラス(複数操作パターン)
from 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)}
JSON 設定
[
  {
    "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}]
  }
]
重要な要件
  • return_schema には必ず oneOf を含めます:成功スキーマとエラースキーマ
  • test_examples には必ず実在する ID を使用します:「TEST」、「DUMMY」、「PLACEHOLDER」は使用しません
  • ツール名は55文字以内にします:{API}_{action}_{target} のテンプレート
  • 説明は150~250文字にします:用途、形式、例、注意事項
  • run() で例外を発生させてはいけません:{"status": "error", "error": "..."} を返します
  • すべての HTTP リクエストにタイムアウトを設定します(30秒)
  • 標準レスポンス:{"status": "success|error", "data": {...}}

パラメーター設計

相互排他的なパラメーター(重要 — 最大の問題)

ツールが id または name のいずれかを受け取る場合、両方とも null を許容する必要があります。

{
  "id": {"type": ["integer", "null"], "description": "Numeric ID"},
  "name": {"type": ["string", "null"], "description": "Name (alternative to id)"}
}

"null" がないと、ユーザーが一方のパラメーターだけを指定したときに検証に失敗します。

よくあるケース:id または name、gene_id または gene_symbol、任意のフィルター。

API キーの設定

任意のキー(キーがなくてもツールは動作しますが、あるほうが便利です):

{"optional_api_keys": ["NCBI_API_KEY"]}
self.api_key = os.environ.get("NCBI_API_KEY", "")  # Read from env only

必須のキー(キーがないとツールは動作しません):

{"required_api_keys": ["NVIDIA_API_KEY"]}

ルール:任意のキーでは、api_key をツールのパラメーターとして追加してはいけません。環境変数のみを使用します。


テスト(必須)

詳しいガイド:references/testing-guide.md

テストの簡易チェックリスト
  1. レベル1 — クラスを直接テスト:クラスをインポートし、run() を呼び出して、レスポンスを確認します
  2. レベル2 — ToolUniverse をテスト:tu.tools.YourTool_op1(...) を呼び出し、登録を確認します
  3. レベル3 — 実際の API をテスト:実在する ID を使用し、実際のレスポンスを確認します
  4. 必須 — python scripts/test_new_tools.py your_tool -v を実行 → 失敗が0件
検証スクリプト
# 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 test

参考資料

コメント