メインコンテンツへスキップ

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 統合 を、権限体系の完全な説明については 権限制御 を参照してください。

組み込みツール

組み込みツールを使う場合、自分でツールを実装する必要はありません。QoderAgentOptions で、このセッションが利用できるツール、事前承認するツール、禁止するツールを制御するだけです。
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())
よく使われる組み込みツールには ReadEditWriteBashGlobGrepWebFetchWebSearchAgent などがあります。ツール名は底層の Qoder CLI によって決定されます。権限設定では CLI がモデルに公開しているツール名(例:ReadWriteBash)を使用してください。組み込みツールの完全な一覧は Tools Reference - 組み込みツール一覧 を参照してください。

カスタムツール

モデルに自分の業務能力を呼び出させたい場合は、カスタムツールを定義します。たとえば注文検索、内部ナレッジベース検索、承認システム呼び出し、読み取り専用データベースアクセスなどです。 Python 版のカスタムツールは通常、次の 3 ステップで構成します。
  1. @tool()async def の handler を装飾する。
  2. create_sdk_mcp_server() で 1 つ以上のツールをインプロセス MCP サーバーに登録する。
  3. QoderAgentOptionsmcp_servers に組み込み、権限設定で呼び出しを制御する。

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

まず完全な最小例を示し、その後で各ステップで設定可能な内容を説明します。
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())

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

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

@tool() の引数

@tool() はデコレーターです。4 つの引数を持ちます。
def tool(
    name: str,
    description: str,
    input_schema: type | dict[str, Any],
    annotations: ToolAnnotations | None = None,
): ...
引数必須意味
namestrはい現在の MCP サーバー内で一意なツール識別子
descriptionstrはいモデル向けのツール説明。いつ使うか、何をするか、何を返すかを説明します
input_schematype | dict[str, Any]はいツールの入力パラメータを定義。シンプルな dict、TypedDict、完全な JSON Schema dict をサポート
annotationsToolAnnotations | NoneいいえMCP ツールアノテーション。例:readOnlyHintdestructiveHintopenWorldHint
完全な API シグネチャと戻り値の SdkMcpTool 型については Tools Reference - tool() を参照してください。 ツール handler は async 関数である必要があり、通常は args dict を 1 つ受け取ります。
@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 を渡します。その中の signalasyncio.Event で、CLI が実行中のツール呼び出しをキャンセルするとセットされます。長時間タスクで能動的に停止する場合に適しています。
@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"}]}

入力パラメータの設定

Python 版の input_schema は 3 種類の書き方をサポートします。これらは最終的に MCP プロトコルの JSON Schema に正規化されます。 書き方 1:シンプルな dict いくつかの単純なパラメータの場合に適しています。dict のキーがパラメータ名、値が Python 型です。この書き方ではすべてのキーが required になります。
input_schema = {
    "query": str,
    "max_results": int,
    "include_archived": bool,
}
typing.Annotated を使ってフィールドに説明を追加できます。
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 を使います。
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 を使用します。
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 リストを優先して使用してください。

ツールメタ情報の設定

annotations には mcp.types.ToolAnnotations を使用します。SDK はこれを MCP tool 定義に格納し、CLI がスケジューリング、権限、状態表示に利用できます。
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"]}]}
よく使うフィールド:
フィールド意味
titlestrツールの人間可読タイトル
readOnlyHintboolツールが読み取り専用で、状態を変更しないことを示します
destructiveHintboolツールがデータを変更または削除する可能性があることを示します
openWorldHintboolツールが外部システムやネットワークにアクセスすることを示します
maxResultSizeCharsintPython SDK は _meta["anthropic/maxResultSizeChars"] を介して CLI に渡し、ツール戻り値の長さ制限を緩和するために使用
注意:annotations は権限設定の代替ではありません。ツール呼び出しを許可するかどうかは、引き続き toolsallowed_toolsdisallowed_toolspermission_modecan_use_tool、hooks によって決まります。get_mcp_status() / MCP status で返される annotations のフィールド名は CLI で投影された readOnlydestructiveopenWorld であり、MCP オリジナルの *Hint 名ではない可能性があります。

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

create_sdk_mcp_server() は 1 つ以上のツールを同一プロセスの MCP サーバーとして登録します。サーバー名は完全なツール名に組み込まれるため、短く安定した名前を推奨します。
kb_tools = create_sdk_mcp_server(
    name="kb",
    version="1.0.0",
    tools=[search_docs],
)
フィールド設定例説明
namekborders などサーバー名。完全なツール名 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() を参照してください。 create_sdk_mcp_server() は同期的に検証を行います。
  • サーバー名は空でない文字列でなければなりません。
  • ツール名は空でない文字列でなければなりません。
  • ツール説明は空でない文字列でなければなりません。
  • 同じサーバー内にツール名の重複があってはなりません。

ステップ 3:query() への接続

サーバーを options.mcp_servers に入れると、CLI がその中のツールを発見し、モデルが必要としたときに SDK 経由で handler を呼び戻します。
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)
カスタムツールの完全名形式は次のとおりです。
mcp__{serverName}__{toolName}
たとえばサーバー名が orders、ツール名が lookup_order の場合、完全名は mcp__orders__lookup_order です。この完全名は allowed_toolsdisallowed_toolscan_use_tool、hooks matcher、サブエージェントの tools 設定で使用します。 QoderSDKClient のマルチターン会話でも同じ mcp_servers 設定を使用します。
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)

ツール権限の制御

モデルがツールを呼び出すとき、SDK は複数層の権限制御を提供します。次のことを決められます。
  • このセッションで提供するツール。
  • デフォルトで許可するツール。
  • 明示的に禁止するツール。
  • 各ツール呼び出し前にホストアプリケーションが動的判断を行うかどうか。

権限制御方式の概要

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

方式 1:toolsallowed_toolsdisallowed_tools

tools はこのセッションに見えるツール集合を制御します。allowed_toolsdisallowed_tools は権限ルールを制御します。カスタム MCP ツールには完全名を使用します。
# 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=[])
同じツールが許可ルールと禁止ルールの両方に一致した場合、禁止ルールが優先されます。

方式 2:permission_mode

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

方式 3:can_use_tool

can_use_tool はツール呼び出し前に実行されます。ツール名、パラメータ内容、承認コンテキストに基づいて許可または拒否を返すことができます。
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_idagent_idsignaltitledisplay_namedescriptionsuggestionsblocked_pathdecision_reason などがあります。より完全な権限戦略は 権限制御 を参照してください。 サブエージェント内でカスタムツールを使用する場合も、完全ツール名を使用します。
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"],
        ),
    },
)

方式 4:hooks["PreToolUse"]

すでに hooks 体系を使用している場合、PreToolUse でツール呼び出しを統一的にインターセプトまたは監査できます。
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]),
        ],
    },
)
PreToolUsepermissionDecision"allow""deny""ask""defer" のいずれかです。

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

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

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

予期できる業務上の失敗には is_error: True を返すことを推奨します。SDK はこの結果を MCP の CallToolResult に変換して CLI に渡します。モデルは失敗内容を見て、再試行したり別の方法を選んだりできます。
return {
    "is_error": True,
    "content": [
        {
            "type": "text",
            "text": json.dumps(
                {
                    "error": "VALIDATION_ERROR",
                    "message": "Only SELECT statements are allowed.",
                }
            ),
        }
    ],
}
is_error: True が適している場面:
  • パラメータは正しいが、業務上の結果がない。たとえば注文が存在しない場合。
  • セキュリティポリシーが実行を拒否する。たとえば SELECT クエリだけを許可する場合。
  • 外部サービスが理解可能な業務エラーを返す場合。

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

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

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

Python SDK はランタイムで handler の戻り値をフォールバックチェックします。
  • None を返す:handler は "content" を含む dict を返す必要があるという内容のエラーテキストに変換されます。
  • 非 dict(文字列、数値、リストなど)を返す:テキスト content に変換され、isError=True がマークされます。
  • "content" を持たない dict を返す:実際のキー一覧を含むエラーテキストに変換されます。
  • サポートされていない content 型を返す:そのコンテンツブロックはスキップされ、warning ログが記録されます。
これらのフォールバックはモデルが空の成功結果を見ることを防ぎますが、ドキュメントや業務コードでは常に標準構造を返すべきです。

Tool 戻り値

ツール handler は dict を返し、SDK がそれを MCP の CallToolResult に変換します。最もよく使うのはテキスト content です。
return {
    "content": [{"type": "text", "text": "done"}],
}
構造化された JSON 文字列を返すこともできます。モデルが理解して処理を続けやすくなります。
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 のトップレベル _metaCallToolResult に透過されません。
  • handler がエラーマークを返す際は Python のフィールド名 "is_error": True を使用します。MCP/TypeScript 風の isError ではありません。SDK 内部で MCP の結果にマッピングされます。

よくある落とし穴

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

続きを読む

  • MCP 統合:インプロセス、stdio、SSE、HTTP、OAuth などの MCP サーバー接続方式。
  • 権限制御permission_modeallowed_toolscan_use_tool、hooks、権限ルール更新。
  • HooksPreToolUsePostToolUsePermissionRequest などのライフサイクル拡張。
  • サブエージェント使用ガイド:異なる Agent に異なるツール集合を使わせる方法。