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())
よく使われる組み込みツールには Read、Edit、Write、Bash、Glob、Grep、WebFetch、WebSearch、Agent などがあります。ツール名は底層の Qoder CLI によって決定されます。権限設定では CLI がモデルに公開しているツール名(例:Read、Write、Bash)を使用してください。組み込みツールの完全な一覧は Tools Reference - 組み込みツール一覧 を参照してください。
カスタムツール
モデルに自分の業務能力を呼び出させたい場合は、カスタムツールを定義します。たとえば注文検索、内部ナレッジベース検索、承認システム呼び出し、読み取り専用データベースアクセスなどです。
Python 版のカスタムツールは通常、次の 3 ステップで構成します。
@tool() で async def の handler を装飾する。
create_sdk_mcp_server() で 1 つ以上のツールをインプロセス MCP サーバーに登録する。
QoderAgentOptions の mcp_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())
このステップでは、ツール本体を定義します。ツール名、説明、入力パラメータ、実行ロジック、ツールメタ情報が含まれます。
@tool() はデコレーターです。4 つの引数を持ちます。
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() を参照してください。
ツール 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 を渡します。その中の signal は asyncio.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"]}]}
よく使うフィールド:
| フィールド | 型 | 意味 |
|---|
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 名ではない可能性があります。
ステップ 2:MCP サーバーへの登録
create_sdk_mcp_server() は 1 つ以上のツールを同一プロセスの MCP サーバーとして登録します。サーバー名は完全なツール名に組み込まれるため、短く安定した名前を推奨します。
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() を参照してください。
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_tools、disallowed_tools、can_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 でパラメータレベルの判断を行う形です。
tools はこのセッションに見えるツール集合を制御します。allowed_tools と disallowed_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 を自動判断します |
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_id、agent_id、signal、title、display_name、description、suggestions、blocked_path、decision_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"],
),
},
)
すでに 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]),
],
},
)
PreToolUse の permissionDecision は "allow"、"deny"、"ask"、"defer" のいずれかです。
ツール 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 ログが記録されます。
これらのフォールバックはモデルが空の成功結果を見ることを防ぎますが、ドキュメントや業務コードでは常に標準構造を返すべきです。
ツール 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 のトップレベル
_meta は CallToolResult に透過されません。
- 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 を返す必要があります。
- ツール説明には「いつ使うか、何をするか、何を返すか」を書いてください。
query、helper のような曖昧な説明は避けてください。
readOnlyHint はツールメタ情報およびスケジューリング/権限ヒントであり、権限スイッチではありません。実行を許可するかどうかは権限設定で決まります。
- 大きすぎる万能の業務入口を 1 つのツールに詰め込まないでください。1 つのツールは明確な一種類のアクションを完了するのが理想です。
続きを読む
- MCP 統合:インプロセス、stdio、SSE、HTTP、OAuth などの MCP サーバー接続方式。
- 権限制御:
permission_mode、allowed_tools、can_use_tool、hooks、権限ルール更新。
- Hooks:
PreToolUse、PostToolUse、PermissionRequest などのライフサイクル拡張。
- サブエージェント使用ガイド:異なる Agent に異なるツール集合を使わせる方法。