Jinba Tool Registry
Guides

ツール開発ガイド

Python/TypeScriptでツールを開発する方法

概念

Jinba Tool Registryでは、以下の3つの概念でツールを管理します。

Organization
└── ToolSet(ツールのグループ)
    ├── Tool A
    ├── Tool B
    └── Tool C
  • ToolSet: 関連するToolをグループ化するコンテナ。サンドボックス設定(言語、パッケージ、リソース)はToolSet単位で共有されます
  • Tool: 単一の処理単位。Input/Outputスキーマとコードで定義されます
  • Version: ToolSetのimmutableなスナップショット。Publishすることで実行可能になります

Pythonでのツール開発

基本構造

Pydantic BaseModelでInput/Outputを定義し、run()関数を実装します。

from pydantic import BaseModel, Field

class Input(BaseModel):
    name: str = Field(description="挨拶する相手の名前")

class Output(BaseModel):
    message: str = Field(description="挨拶メッセージ")

def run(input: Input) -> Output:
    return Output(message=f"Hello, {input.name}!")

環境変数の利用

ToolSetに設定した環境変数はos.environからアクセスできます。

import os
from pydantic import BaseModel

class Input(BaseModel):
    channel: str

class Output(BaseModel):
    success: bool

def run(input: Input) -> Output:
    api_key = os.environ["SLACK_API_KEY"]
    # api_key を使って処理...
    return Output(success=True)

環境変数の設定方法は環境変数を参照してください。

外部ライブラリの利用

ToolSetのサンドボックス設定でパッケージを指定します。バージョンを指定するとそのバージョンがインストールされます。

import requests
from pydantic import BaseModel

class Input(BaseModel):
    url: str

class Output(BaseModel):
    status_code: int
    body: str

def run(input: Input) -> Output:
    response = requests.get(input.url)
    return Output(status_code=response.status_code, body=response.text[:1000])

エラーハンドリング

例外を送出すると、エラー情報がRunResultに記録されます。

from pydantic import BaseModel

class Input(BaseModel):
    value: int

class Output(BaseModel):
    result: int

def run(input: Input) -> Output:
    if input.value < 0:
        raise ValueError("value must be non-negative")
    return Output(result=input.value * 2)

複雑な型の定義

ネストしたモデルやOptionalフィールドも使用できます。

from typing import Optional
from pydantic import BaseModel, Field

class Address(BaseModel):
    city: str
    country: str = "JP"

class Input(BaseModel):
    name: str
    age: Optional[int] = None
    address: Address
    tags: list[str] = Field(default_factory=list)

class Output(BaseModel):
    summary: str

def run(input: Input) -> Output:
    parts = [f"Name: {input.name}"]
    if input.age:
        parts.append(f"Age: {input.age}")
    parts.append(f"Location: {input.address.city}, {input.address.country}")
    return Output(summary=", ".join(parts))

TypeScriptでのツール開発

基本構造

Zodでスキーマを定義し、run()関数をエクスポートします。

import { z } from "zod";

export const input = z.object({
  name: z.string().describe("挨拶する相手の名前"),
});

export const output = z.object({
  message: z.string().describe("挨拶メッセージ"),
});

export async function run(data: z.infer<typeof input>) {
  return { message: `Hello, ${data.name}!` };
}

環境変数の利用

ToolSetに設定した環境変数はprocess.envからアクセスできます。

import { z } from "zod";

export const input = z.object({
  channel: z.string(),
});

export const output = z.object({
  success: z.boolean(),
});

export async function run(data: z.infer<typeof input>) {
  const apiKey = process.env.SLACK_API_KEY;
  // apiKey を使って処理...
  return { success: true };
}

外部パッケージの利用

ToolSetのサンドボックス設定でパッケージを指定します。

import { z } from "zod";
import axios from "axios";

export const input = z.object({
  url: z.string().url(),
});

export const output = z.object({
  status: z.number(),
  data: z.unknown(),
});

export async function run(data: z.infer<typeof input>) {
  const response = await axios.get(data.url);
  return { status: response.status, data: response.data };
}

エラーハンドリング

import { z } from "zod";

export const input = z.object({
  value: z.number(),
});

export const output = z.object({
  result: z.number(),
});

export async function run(data: z.infer<typeof input>) {
  if (data.value < 0) {
    throw new Error("value must be non-negative");
  }
  return { result: data.value * 2 };
}

高度なパターン

HTTP APIの呼び出し

import os
import requests
from pydantic import BaseModel

class Input(BaseModel):
    query: str

class Output(BaseModel):
    results: list[dict]

def run(input: Input) -> Output:
    response = requests.get(
        "https://api.example.com/search",
        headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
        params={"q": input.query},
    )
    response.raise_for_status()
    return Output(results=response.json()["results"])

ファイル処理

import csv
import io
from pydantic import BaseModel

class Input(BaseModel):
    csv_content: str

class Output(BaseModel):
    rows: int
    headers: list[str]

def run(input: Input) -> Output:
    reader = csv.reader(io.StringIO(input.csv_content))
    headers = next(reader)
    rows = sum(1 for _ in reader)
    return Output(rows=rows, headers=headers)

テスト

Web UIでテスト

ToolSetの詳細画面で各Toolの「Test」ボタンをクリックし、入力値を指定してテスト実行できます。Publishせずにドラフト状態のコードをテストできます。

SDKでテスト

const result = await client.test("my-toolset", "my-tool", {
  name: "World",
});
console.log(result.output); // { message: "Hello, World!" }
console.log(result.logs);   // { stdout: [...], stderr: [...] }

REST APIでテスト

curl -X POST https://tool-registry-api.jinba.io/v1/orgs/{orgId}/toolsets/{slug}/tools/{toolSlug}/test \
  -H "Authorization: Bearer $JINBA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"name": "World"}}'

ベストプラクティス

スキーマにdescriptionを付与する

スキーマフィールドにdescriptionを付与すると、MCPクライアントやドキュメントで表示されます。

class Input(BaseModel):
    query: str = Field(description="検索クエリ")
    limit: int = Field(default=10, description="最大取得件数")
export const input = z.object({
  query: z.string().describe("検索クエリ"),
  limit: z.number().default(10).describe("最大取得件数"),
});

entrypointのカスタマイズ

デフォルトのエントリポイントはrunですが、Toolの設定で変更できます。

def execute(input: Input) -> Output:  # entrypoint: "execute"
    return Output(result=input.value)

ToolSetの粒度設計

  • 同じサンドボックス設定(言語、パッケージ)を共有するToolをまとめる
  • 関連する機能(例: Slack操作、データ変換)をグループ化する
  • 独立したライフサイクルを持つToolは別のToolSetに分ける

On this page