> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ツール

ツールは、モデルがタスクを実行する際に呼び出すことができる能力です。Qoder Agent SDK Python 版では次の 2 種類のツールをサポートしています。

* **組み込みツール**：Qoder CLI が提供します。ファイルの読み取り、検索、コマンド実行、サブエージェントの呼び出しなどがあります。
* **カスタムツール**：SDK の利用者が `@tool()` と `create_sdk_mcp_server()` を使って Python 関数を定義し、インプロセス MCP サーバーとしてモデルに公開します。

このドキュメントではカスタムツールの定義方法を中心に説明します。MCP サーバーのその他の接続方式については [MCP 統合](/ja/cli/sdk/python/mcp) を、権限体系の完全な説明については [権限制御](/ja/cli/sdk/python/permissions) を参照してください。

<div id="組み込みツール" />

## 組み込みツール

組み込みツールを使う場合、自分でツールを実装する必要はありません。`QoderAgentOptions` で、このセッションが利用できるツール、事前承認するツール、禁止するツールを制御するだけです。

```python theme={null}
import asyncio

from qoder_agent_sdk import QoderAgentOptions, qodercli_auth, query


async def main():
    options = QoderAgentOptions(
        auth=qodercli_auth(),
        cwd="/path/to/project",
        tools=["Read", "Grep", "Glob"],
        allowed_tools=["Read", "Grep", "Glob"],
    )

    async for message in query(
        prompt=(
            "Read this repository and summarize risks in the authentication "
            "module. Do not modify files."
        ),
        options=options,
    ):
        print(message)


asyncio.run(main())
```

よく使われる組み込みツールには `Read`、`Edit`、`Write`、`Bash`、`Glob`、`Grep`、`WebFetch`、`WebSearch`、`Agent` などがあります。ツール名は底層の Qoder CLI によって決定されます。権限設定では CLI がモデルに公開しているツール名（例：`Read`、`Write`、`Bash`）を使用してください。組み込みツールの完全な一覧は [Tools Reference - 組み込みツール一覧](/ja/cli/sdk/python/references#built-in-tool-list) を参照してください。

<div id="カスタムツール" />

## カスタムツール

モデルに自分の業務能力を呼び出させたい場合は、カスタムツールを定義します。たとえば注文検索、内部ナレッジベース検索、承認システム呼び出し、読み取り専用データベースアクセスなどです。

Python 版のカスタムツールは通常、次の 3 ステップで構成します。

1. `@tool()` で `async def` の handler を装飾する。
2. `create_sdk_mcp_server()` で 1 つ以上のツールをインプロセス MCP サーバーに登録する。
3. `QoderAgentOptions` の `mcp_servers` に組み込み、権限設定で呼び出しを制御する。

<div id="カスタムツールの接続手順" />

## カスタムツールの接続手順

まず完全な最小例を示し、その後で各ステップで設定可能な内容を説明します。

```python theme={null}
import asyncio
import json

from mcp.types import ToolAnnotations

from qoder_agent_sdk import (
    QoderAgentOptions,
    create_sdk_mcp_server,
    qodercli_auth,
    query,
    tool,
)


orders = {
    "O-1001": {"order_id": "O-1001", "status": "shipped", "eta": "2026-05-20"},
}


@tool(
    "lookup_order",
    "Look up an order by order ID and return its status as JSON.",
    {"order_id": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)
async def lookup_order(args):
    order_id = args["order_id"]
    order = orders.get(order_id)

    if order is None:
        return {
            "is_error": True,
            "content": [{"type": "text", "text": f"Order not found: {order_id}"}],
        }

    return {"content": [{"type": "text", "text": json.dumps(order)}]}


order_tools = create_sdk_mcp_server(
    name="orders",
    tools=[lookup_order],
)


async def main():
    options = QoderAgentOptions(
        auth=qodercli_auth(),
        mcp_servers={"orders": order_tools},
        allowed_tools=["mcp__orders__lookup_order"],
    )

    async for message in query(
        prompt="Check the status of order O-1001 and summarize it in one sentence.",
        options=options,
    ):
        print(message)


asyncio.run(main())
```

<div id="ステップ-1tool-でツールを作成する" />

<div id="ステップ1toolでツールを作成する" />

### ステップ 1：`@tool()` でツールを作成する

このステップでは、ツール本体を定義します。ツール名、説明、入力パラメータ、実行ロジック、ツールメタ情報が含まれます。

<div id="tool-の引数" />

<div id="toolの引数" />

#### `@tool()` の引数

`@tool()` はデコレーターです。4 つの引数を持ちます。

```python theme={null}
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any],
    annotations: ToolAnnotations | None = None,
): ...
```

| 引数             | 型                         | 必須  | 意味                                                                |
| -------------- | ------------------------- | --- | ----------------------------------------------------------------- |
| `name`         | `str`                     | はい  | 現在の MCP サーバー内で一意なツール識別子                                           |
| `description`  | `str`                     | はい  | モデル向けのツール説明。いつ使うか、何をするか、何を返すかを説明します                               |
| `input_schema` | `type \| dict[str, Any]`  | はい  | ツールの入力パラメータを定義。シンプルな dict、`TypedDict`、完全な JSON Schema dict をサポート  |
| `annotations`  | `ToolAnnotations \| None` | いいえ | MCP ツールアノテーション。例：`readOnlyHint`、`destructiveHint`、`openWorldHint` |

完全な API シグネチャと戻り値の `SdkMcpTool` 型については [Tools Reference - `tool()`](/ja/cli/sdk/python/references#tool) を参照してください。

ツール handler は async 関数である必要があり、通常は `args` dict を 1 つ受け取ります。

```python theme={null}
@tool("search_docs", "Search internal product documentation.", {"query": str})
async def search_docs(args):
    return {"content": [{"type": "text", "text": f"Searching: {args['query']}"}]}
```

handler が第 2 位置引数を宣言している場合、SDK は `ToolInvocationContext` を渡します。その中の `signal` は `asyncio.Event` で、CLI が実行中のツール呼び出しをキャンセルするとセットされます。長時間タスクで能動的に停止する場合に適しています。

```python theme={null}
@tool("watch", "Watch a counter until max.", {"max": int})
async def watch(args, extra):
    for i in range(args["max"]):
        if extra.signal.is_set():
            return {"content": [{"type": "text", "text": f"aborted at {i}"}]}
        await asyncio.sleep(0.01)

    return {"content": [{"type": "text", "text": "done"}]}
```

<div id="入力パラメータの設定" />

#### 入力パラメータの設定

Python 版の `input_schema` は 3 種類の書き方をサポートします。これらは最終的に MCP プロトコルの JSON Schema に正規化されます。

**書き方 1：シンプルな dict**

いくつかの単純なパラメータの場合に適しています。dict のキーがパラメータ名、値が Python 型です。この書き方ではすべてのキーが required になります。

```python theme={null}
input_schema = {
    "query": str,
    "max_results": int,
    "include_archived": bool,
}
```

`typing.Annotated` を使ってフィールドに説明を追加できます。

```python theme={null}
from typing import Annotated


input_schema = {
    "query": Annotated[str, "Search keywords"],
    "max_results": Annotated[int, "Maximum number of snippets to return"],
}
```

よくある型は JSON Schema に変換されます。

| Python の書き方           | JSON Schema の意味              |
| --------------------- | ---------------------------- |
| `str`                 | `{"type": "string"}`         |
| `int`                 | `{"type": "integer"}`        |
| `float`               | `{"type": "number"}`         |
| `bool`                | `{"type": "boolean"}`        |
| `list[str]`           | 文字列配列                        |
| `dict`                | オブジェクト                       |
| `Annotated[T, "..."]` | `T` のスキーマに `description` を追加 |

**書き方 2：`TypedDict`**

フィールド数が多い場合、オプションフィールドが必要な場合、または型定義を再利用したい場合に適しています。オプションフィールドには `NotRequired` を使います。

```python theme={null}
from typing import Annotated, TypedDict

from typing_extensions import NotRequired


class SearchInput(TypedDict):
    query: Annotated[str, "Search keywords"]
    max_results: NotRequired[Annotated[int, "Maximum snippets to return"]]


@tool("search_docs", "Search internal product documentation.", SearchInput)
async def search_docs(args):
    limit = args.get("max_results", 5)
    return {"content": [{"type": "text", "text": f"{args['query']} ({limit})"}]}
```

Python 3.11 以上では `typing` から直接 `NotRequired` をインポートできます。Python 3.10 では `typing_extensions` からインポートする必要があります。

**書き方 3：完全な JSON Schema dict**

enum、数値範囲、文字列フォーマット制約、ネストされたオブジェクトが必要な場合は、完全な JSON Schema を使用します。

```python theme={null}
input_schema = {
    "type": "object",
    "properties": {
        "query": {"type": "string", "description": "Search keywords"},
        "source": {
            "type": "string",
            "enum": ["docs", "tickets", "wiki"],
            "description": "Where to search",
        },
        "max_results": {"type": "integer", "minimum": 1, "maximum": 10},
    },
    "required": ["query"],
}
```

オプションパラメータについて：シンプルな dict の書き方ではすべてのフィールドが required です。オプションフィールドを表現するには、`TypedDict + NotRequired` または完全な JSON Schema の `required` リストを優先して使用してください。

<div id="ツールメタ情報の設定" />

#### ツールメタ情報の設定

`annotations` には `mcp.types.ToolAnnotations` を使用します。SDK はこれを MCP tool 定義に格納し、CLI がスケジューリング、権限、状態表示に利用できます。

```python theme={null}
from mcp.types import ToolAnnotations


@tool(
    "search_docs",
    "Search internal product documentation.",
    {"query": str},
    annotations=ToolAnnotations(
        title="Search docs",
        readOnlyHint=True,
        destructiveHint=False,
        openWorldHint=False,
    ),
)
async def search_docs(args):
    return {"content": [{"type": "text", "text": args["query"]}]}
```

よく使うフィールド：

| フィールド                | 型      | 意味                                                                                      |
| -------------------- | ------ | --------------------------------------------------------------------------------------- |
| `title`              | `str`  | ツールの人間可読タイトル                                                                            |
| `readOnlyHint`       | `bool` | ツールが読み取り専用で、状態を変更しないことを示します                                                             |
| `destructiveHint`    | `bool` | ツールがデータを変更または削除する可能性があることを示します                                                          |
| `openWorldHint`      | `bool` | ツールが外部システムやネットワークにアクセスすることを示します                                                         |
| `maxResultSizeChars` | `int`  | Python SDK は `_meta["anthropic/maxResultSizeChars"]` を介して CLI に渡し、ツール戻り値の長さ制限を緩和するために使用 |

注意：annotations は権限設定の代替ではありません。ツール呼び出しを許可するかどうかは、引き続き `tools`、`allowed_tools`、`disallowed_tools`、`permission_mode`、`can_use_tool`、hooks によって決まります。`get_mcp_status()` / MCP status で返される annotations のフィールド名は CLI で投影された `readOnly`、`destructive`、`openWorld` であり、MCP オリジナルの `*Hint` 名ではない可能性があります。

<div id="ステップ-2mcp-サーバーへの登録" />

<div id="ステップ2mcpサーバーへの登録" />

### ステップ 2：MCP サーバーへの登録

`create_sdk_mcp_server()` は 1 つ以上のツールを同一プロセスの MCP サーバーとして登録します。サーバー名は完全なツール名に組み込まれるため、短く安定した名前を推奨します。

```python theme={null}
kb_tools = create_sdk_mcp_server(
    name="kb",
    version="1.0.0",
    tools=[search_docs],
)
```

| フィールド     | 設定例                           | 説明                                         |
| --------- | ----------------------------- | ------------------------------------------ |
| `name`    | `kb`、`orders` など              | サーバー名。完全なツール名 `mcp__{name}__{tool}` を構成します |
| `version` | `"1.0.0"` など                  | 情報用バージョン番号。デフォルトは `"1.0.0"`                |
| `tools`   | `[search_docs, lookup_order]` | このサーバーに登録するツール一覧                           |

戻り値は `McpSdkServerConfig` で、そのまま `QoderAgentOptions.mcp_servers` に渡せます。

完全な戻り値の型は [Tools Reference - `create_sdk_mcp_server()`](/ja/cli/sdk/python/references#create_sdk_mcp_server) を参照してください。

`create_sdk_mcp_server()` は同期的に検証を行います。

* サーバー名は空でない文字列でなければなりません。
* ツール名は空でない文字列でなければなりません。
* ツール説明は空でない文字列でなければなりません。
* 同じサーバー内にツール名の重複があってはなりません。

<div id="ステップ-3query-への接続" />

<div id="ステップ3queryへの接続" />

### ステップ 3：`query()` への接続

サーバーを `options.mcp_servers` に入れると、CLI がその中のツールを発見し、モデルが必要としたときに SDK 経由で handler を呼び戻します。

```python theme={null}
options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"kb": kb_tools},
    allowed_tools=["mcp__kb__search_docs"],
)

async for message in query(
    prompt="Search docs for the refund policy and summarize it.",
    options=options,
):
    print(message)
```

カスタムツールの完全名形式は次のとおりです。

```text theme={null}
mcp__{serverName}__{toolName}
```

たとえばサーバー名が `orders`、ツール名が `lookup_order` の場合、完全名は `mcp__orders__lookup_order` です。この完全名は `allowed_tools`、`disallowed_tools`、`can_use_tool`、hooks matcher、サブエージェントの `tools` 設定で使用します。

`QoderSDKClient` のマルチターン会話でも同じ `mcp_servers` 設定を使用します。

```python theme={null}
from qoder_agent_sdk import QoderSDKClient


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"kb": kb_tools},
    allowed_tools=["mcp__kb__search_docs"],
)

async with QoderSDKClient(options=options) as client:
    await client.query("Search docs for the refund policy.")

    async for message in client.receive_response():
        print(message)
```

<div id="ツール権限の制御" />

## ツール権限の制御

モデルがツールを呼び出すとき、SDK は複数層の権限制御を提供します。次のことを決められます。

* このセッションで提供するツール。
* デフォルトで許可するツール。
* 明示的に禁止するツール。
* 各ツール呼び出し前にホストアプリケーションが動的判断を行うかどうか。

<div id="権限制御方式の概要" />

### 権限制御方式の概要

| 方式                                   | 作用                            | 粒度    | 適した場面                               |
| ------------------------------------ | ----------------------------- | ----- | ----------------------------------- |
| `tools`                              | このセッションに見えるツール集合を制限           | セッション | モデルに見えるツールを元から絞りたい                  |
| `allowed_tools` / `disallowed_tools` | 指定ツールを事前承認または禁止               | ツール   | 許可または禁止したいツールが明確                    |
| `permission_mode`                    | セッション全体のデフォルト権限ポリシーを設定        | 全体    | 計画モード、編集自動承認、権限スキップなどを素早く切り替えたい     |
| `can_use_tool`                       | 各呼び出し前にカスタム判断ロジックを実行          | 呼び出し  | パラメータ内容に基づいて動的に判断したい                |
| `hooks["PreToolUse"]`                | hooks ライフサイクルでツール呼び出しをインターセプト | 呼び出し  | hooks 体系をすでに使用しており、監査やインターセプトを統一したい |

これらは組み合わせ可能です。よくある構成は、まず `tools` で見えるツール集合を絞り、`allowed_tools` / `disallowed_tools` で静的ルールを設定し、最後に `can_use_tool` でパラメータレベルの判断を行う形です。

<div id="方式-1toolsallowed_toolsdisallowed_tools" />

<div id="方式1toolsallowed_toolsdisallowed_tools" />

### 方式 1：`tools`、`allowed_tools`、`disallowed_tools`

`tools` はこのセッションに見えるツール集合を制御します。`allowed_tools` と `disallowed_tools` は権限ルールを制御します。カスタム MCP ツールには完全名を使用します。

```python theme={null}
# Only expose read/search tools to this session.
QoderAgentOptions(
    tools=["Read", "Glob", "Grep"],
    allowed_tools=["Read", "Glob", "Grep"],
)

# Explicitly deny high-risk tools.
QoderAgentOptions(
    disallowed_tools=["Bash", "Write", "Edit"],
)

# Use full names for custom MCP tools.
QoderAgentOptions(
    mcp_servers={"orders": order_tools},
    allowed_tools=["mcp__orders__lookup_order"],
)

# Disable all tools. The model can only answer from its context.
QoderAgentOptions(tools=[])
```

同じツールが許可ルールと禁止ルールの両方に一致した場合、禁止ルールが優先されます。

<div id="方式-2permission_mode" />

<div id="方式2permission_mode" />

### 方式 2：`permission_mode`

`permission_mode` は 1 行の設定でセッション全体のデフォルト権限挙動を設定します。

```python theme={null}
QoderAgentOptions(
    permission_mode="acceptEdits",
)
```

| モード                   | 効果                                                                                        |
| --------------------- | ----------------------------------------------------------------------------------------- |
| `"default"`           | 標準権限挙動。機密操作はルールまたは実行時ポリシーで処理されます                                                          |
| `"acceptEdits"`       | ファイル編集系操作を自動承認します。その他の機密操作は引き続き権限ポリシーに従います                                                |
| `"bypassPermissions"` | 権限チェックをスキップします。`allow_dangerously_skip_permissions=True` を同時に設定する必要があります                  |
| `"yolo"`              | `"bypassPermissions"` の互換エイリアス。同じく `allow_dangerously_skip_permissions=True` を設定する必要があります |
| `"plan"`              | 計画モード。まずモデルに方針を出させる場面に適しています                                                              |
| `"dontAsk"`           | 対話的に質問しません。事前承認されていない、またはルールで許可されていない操作は拒否されます                                            |
| `"auto"`              | 実行時の能力に基づいて allow または deny を自動判断します                                                       |

<div id="方式-3can_use_tool" />

<div id="方式3can_use_tool" />

### 方式 3：`can_use_tool`

`can_use_tool` はツール呼び出し前に実行されます。ツール名、パラメータ内容、承認コンテキストに基づいて許可または拒否を返すことができます。

```python theme={null}
from typing import Any

from qoder_agent_sdk import (
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


async def can_use_tool(
    tool_name: str,
    input_data: dict[str, Any],
    context: ToolPermissionContext,
):
    if tool_name != "mcp__orders__lookup_order":
        return PermissionResultDeny(
            message="Only order lookup is allowed in this workflow.",
        )

    return PermissionResultAllow(updated_input=input_data)


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"orders": order_tools},
    allowed_tools=["mcp__orders__lookup_order"],
    can_use_tool=can_use_tool,
)
```

よく使う戻り値：

| 戻り値                                                      | 効果                       |
| -------------------------------------------------------- | ------------------------ |
| `PermissionResultAllow()`                                | 元のパラメータで実行を許可            |
| `PermissionResultAllow(updated_input={...})`             | 実行を許可し、ツールパラメータを置き換え     |
| `PermissionResultDeny(message="reason")`                 | 実行を拒否。モデルは理由を見て他の方法を試せます |
| `PermissionResultDeny(message="reason", interrupt=True)` | 拒否し、現在の agent loop を中断   |

`ToolPermissionContext` のよく使うフィールドには `tool_use_id`、`agent_id`、`signal`、`title`、`display_name`、`description`、`suggestions`、`blocked_path`、`decision_reason` などがあります。より完全な権限戦略は [権限制御](/ja/cli/sdk/python/permissions) を参照してください。

サブエージェント内でカスタムツールを使用する場合も、完全ツール名を使用します。

```python theme={null}
from qoder_agent_sdk import AgentDefinition


options = QoderAgentOptions(
    auth=qodercli_auth(),
    mcp_servers={"orders": order_tools},
    allowed_tools=["Agent"],
    agents={
        "order-support": AgentDefinition(
            description="Handles order lookup and explains order status.",
            prompt="Use order tools to answer order status questions clearly.",
            tools=["mcp__orders__lookup_order"],
        ),
    },
)
```

<div id="方式-4hookspretooluse" />

<div id="方式4hookspretooluse" />

### 方式 4：`hooks["PreToolUse"]`

すでに hooks 体系を使用している場合、`PreToolUse` でツール呼び出しを統一的にインターセプトまたは監査できます。

```python theme={null}
from qoder_agent_sdk import HookMatcher


async def block_dangerous_bash(inp, tool_use_id, context):
    command = inp.get("tool_input", {}).get("command", "")
    if "rm -rf" in command:
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": "rm -rf is not allowed",
            }
        }

    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "allow",
        }
    }


options = QoderAgentOptions(
    allowed_tools=["Bash"],
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[block_dangerous_bash]),
        ],
    },
)
```

`PreToolUse` の `permissionDecision` は `"allow"`、`"deny"`、`"ask"`、`"defer"` のいずれかです。

<div id="sdk-が-tool-戻り値のエラーを処理する方法" />

<div id="sdkがtool戻り値のエラーを処理する方法" />

## SDK が tool 戻り値のエラーを処理する方法

ツール handler には 3 種類のエラー経路があります。

<div id="業務上の失敗is_error-true-を返す" />

<div id="業務上の失敗is_errortrueを返す" />

### 業務上の失敗：`is_error: True` を返す

予期できる業務上の失敗には `is_error: True` を返すことを推奨します。SDK はこの結果を MCP の `CallToolResult` に変換して CLI に渡します。モデルは失敗内容を見て、再試行したり別の方法を選んだりできます。

```python theme={null}
return {
    "is_error": True,
    "content": [
        {
            "type": "text",
            "text": json.dumps(
                {
                    "error": "VALIDATION_ERROR",
                    "message": "Only SELECT statements are allowed.",
                }
            ),
        }
    ],
}
```

`is_error: True` が適している場面：

* パラメータは正しいが、業務上の結果がない。たとえば注文が存在しない場合。
* セキュリティポリシーが実行を拒否する。たとえば `SELECT` クエリだけを許可する場合。
* 外部サービスが理解可能な業務エラーを返す場合。

<div id="予期しない例外handler-が-throw-する" />

<div id="予期しない例外handlerがthrowする" />

### 予期しない例外：handler が throw する

handler が例外を投げると、MCP 層は例外をエラー結果に変換するため、通常のツール例外だけで agent loop が直接クラッシュすることはありません。ただし、モデルが見られるのは通常例外メッセージだけで、明示的に `is_error: True` を返す場合ほど形式や内容を制御できません。

```python theme={null}
@tool("fetch_user", "Fetch a user by ID.", {"user_id": str})
async def fetch_user(args):
    response = await user_service.fetch(args["user_id"])
    if not response.ok:
        raise RuntimeError("User service failed")

    return {"content": [{"type": "text", "text": await response.text()}]}
```

推奨：業務上予期できる失敗には `is_error: True` を使い、本当に予期しない例外だけを throw してください。

<div id="不正な戻り値sdk-がエラーにラップする" />

<div id="不正な戻り値sdkがエラーにラップする" />

### 不正な戻り値：SDK がエラーにラップする

Python SDK はランタイムで handler の戻り値をフォールバックチェックします。

* `None` を返す：handler は `"content"` を含む dict を返す必要があるという内容のエラーテキストに変換されます。
* 非 dict（文字列、数値、リストなど）を返す：テキスト content に変換され、`isError=True` がマークされます。
* `"content"` を持たない dict を返す：実際のキー一覧を含むエラーテキストに変換されます。
* サポートされていない content 型を返す：そのコンテンツブロックはスキップされ、warning ログが記録されます。

これらのフォールバックはモデルが空の成功結果を見ることを防ぎますが、ドキュメントや業務コードでは常に標準構造を返すべきです。

<div id="tool-戻り値" />

<div id="tool戻り値" />

## Tool 戻り値

ツール handler は dict を返し、SDK がそれを MCP の `CallToolResult` に変換します。最もよく使うのはテキスト content です。

```python theme={null}
return {
    "content": [{"type": "text", "text": "done"}],
}
```

構造化された JSON 文字列を返すこともできます。モデルが理解して処理を続けやすくなります。

```python theme={null}
return {
    "content": [
        {
            "type": "text",
            "text": json.dumps(
                {
                    "order_id": "O-1001",
                    "status": "shipped",
                    "eta": "2026-05-20",
                }
            ),
        }
    ],
}
```

よく使うコンテンツブロック：

| 種類           | 形状                                                                             | Python SDK の挙動                                            |
| ------------ | ------------------------------------------------------------------------------ | --------------------------------------------------------- |
| テキスト         | `{"type": "text", "text": "..."}`                                              | `TextContent` に変換                                         |
| 画像           | `{"type": "image", "data": "...", "mimeType": "image/png"}`                    | `ImageContent` に変換。`data` は base64                        |
| リソースリンク      | `{"type": "resource_link", "uri": "...", "name": "...", "description": "..."}` | テキストにフォールバックされ、`name` / `uri` / `description` を結合してモデルに渡す |
| 埋め込みテキストリソース | `{"type": "resource", "resource": {"text": "..."}}`                            | `TextContent` に変換                                         |

Python 版にはさらに 2 つ注意すべき結果の差異があります。

* handler が返す dict のトップレベル `_meta` は `CallToolResult` に透過されません。
* handler がエラーマークを返す際は Python のフィールド名 `"is_error": True` を使用します。MCP/TypeScript 風の `isError` ではありません。SDK 内部で MCP の結果にマッピングされます。

<div id="よくある落とし穴" />

## よくある落とし穴

* 権限設定でカスタムツールを書くときは、`mcp__server__tool` の完全名を使ってください。
* Python のシンプル dict スキーマではすべてのフィールドが required です。オプションフィールドが必要な場合は `TypedDict + NotRequired` または完全な JSON Schema を使ってください。
* enum、数値範囲、ネストされたオブジェクト、文字列の pattern/format が必要な場合は完全な JSON Schema dict を使用してください。
* handler は `async def` でなければならず、`"content"` リストを含む dict を返す必要があります。
* ツール説明には「いつ使うか、何をするか、何を返すか」を書いてください。`query`、`helper` のような曖昧な説明は避けてください。
* `readOnlyHint` はツールメタ情報およびスケジューリング/権限ヒントであり、権限スイッチではありません。実行を許可するかどうかは権限設定で決まります。
* 大きすぎる万能の業務入口を 1 つのツールに詰め込まないでください。1 つのツールは明確な一種類のアクションを完了するのが理想です。

<div id="続きを読む" />

## 続きを読む

* [MCP 統合](/ja/cli/sdk/python/mcp)：インプロセス、stdio、SSE、HTTP、OAuth などの MCP サーバー接続方式。
* [権限制御](/ja/cli/sdk/python/permissions)：`permission_mode`、`allowed_tools`、`can_use_tool`、hooks、権限ルール更新。
* [Hooks](/ja/cli/sdk/python/hooks)：`PreToolUse`、`PostToolUse`、`PermissionRequest` などのライフサイクル拡張。
* [サブエージェント使用ガイド](/ja/cli/sdk/python/agents)：異なる Agent に異なるツール集合を使わせる方法。
