Skip to main content
ツールと拡張機能

Tools

ツールは、モデルがタスク実行中に呼び出せる能力です。Qoder Agent SDK は次の2種類のツールをサポートします。
  • 組み込みツール:Qoder CLI が提供します。ファイル読み取り、検索、コマンド実行、サブ Agent 呼び出しなどがあります。
  • カスタムツール:SDK 利用者が定義し、インプロセス MCP server としてモデルに公開します——TypeScript は tool() + createSdkMcpServer()、Python は @tool() デコレーター + create_sdk_mcp_server() を使います。
このページはカスタムツールに焦点を当てます。MCP server の他の統合方法は MCP 統合、権限システムの完全な説明は 権限制御、組み込みツールの全リストは SDK References を参照してください。

組み込みツール

組み込みツールを使う場合、ツール自体を実装する必要はありません。options で、このセッションが見えるツール、事前承認するツール、禁止するツールを制御するだけです。
query({
  prompt: 'Read this repository and summarize risks in the authentication module. Do not modify files.',
  options: {
    auth: accessTokenFromEnv(),
    cwd: '/path/to/project',
    tools: ['Read', 'Grep', 'Glob'],
    allowedTools: ['Read', 'Grep', 'Glob'],
  },
});
よく使う組み込みツールには ReadEditWriteBashGlobGrepWebFetchWebSearchAgent などがあります。ツール名は基盤の Qoder CLI が決定し、権限設定では CLI がモデルに公開するツール名を使ってください。完全なリスト、名前、入出力構造は SDK References を参照してください。

カスタムツール

モデルに自分の業務能力を呼び出させたい場合は、カスタムツールを定義します。たとえば注文検索、内部ナレッジベース検索、承認システム呼び出し、読み取り専用データベースアクセスなどです。 カスタムツールは通常、次の3ステップで構成します。
  1. ツールを作成:TypeScript は tool()、Python は @tool()async def ハンドラーをデコレートします。
  2. インプロセス MCP server に登録:createSdkMcpServer() / create_sdk_mcp_server()
  3. options の MCP servers 設定で接続し、権限設定で呼び出しを制御します。

カスタムツール接続手順

まず完全な最小例を示し、その後で各ステップの設定項目を説明します。
import {
  accessTokenFromEnv,
  createSdkMcpServer,
  query,
  tool,
} from '@qoder-ai/qoder-agent-sdk';
import { z } from 'zod';

const lookupOrder = tool(
  'lookup_order',
  'Look up an order by order ID.',
  {
    orderId: z.string().describe('Order ID, such as O-1001'),
  },
  async ({ orderId }) => {
    const order = await orders.find(orderId);

    if (!order) {
      return {
        isError: true,
        content: [{ type: 'text', text: `Order not found: ${orderId}` }],
      };
    }

    return {
      content: [{ type: 'text', text: JSON.stringify(order) }],
    };
  },
  { annotations: { readOnlyHint: true } },
);

const orderTools = createSdkMcpServer({
  name: 'orders',
  tools: [lookupOrder],
});

const messages = query({
  prompt: 'Check the status of order O-1001 and summarize it in one sentence.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
  },
});

for await (const message of messages) {
  if (message.type === 'result') {
    console.log(message.result);
  }
}

ステップ1:ツールを作成する

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

パラメーター

TypeScript の tool() は5つ、Python の @tool() デコレーターは4つのパラメーターを取ります:
パラメーター必須意味
nameはい当該 MCP server 内でのツールの一意な識別子
descriptionはいモデル向けのツール説明。いつ使うか、何をするか、何を返すか
inputSchema / input_schemaはいツール入力パラメーターの定義。TypeScript は Zod raw shape、Python はシンプル dict、TypedDict、完全な JSON Schema dict をサポート
handler(TypeScript)はい解析済み引数を受け取り CallToolResult を返す非同期実行関数。Python ではデコレートされた async def がハンドラー
extras.annotations(TypeScript)/ annotations(Python)いいえreadOnlyHintdestructiveHintopenWorldHint などの MCP ツールアノテーション
Python のツールハンドラーは async 関数でなければならず、通常は args dict を1つ受け取ります。ハンドラーが2つ目の位置引数を宣言すると、SDK は ToolInvocationContext を渡します。その signalasyncio.Event で、CLI が実行中のツール呼び出しをキャンセルするとセットされます。長時間タスクの自発的な停止に適しています:
tool(
  'search_docs',
  'Search internal product documentation.',
  { query: z.string().describe('Search keywords') },
  async ({ query }) => ({ content: [{ type: 'text', text: query }] }),
);

入力パラメータの設定

2つの SDK は入力 schema の書き方が異なりますが、最終的にはどちらも MCP プロトコルの JSON Schema に正規化されます。 TypeScript:Zod raw shape——つまりフィールドオブジェクトを渡します。z.object(...) ではありません:
{
  query: z.string().describe('Search keywords'),
  maxResults: z.number().int().min(1).max(10).optional()
    .describe('Maximum number of snippets to return'),
  source: z.enum(['docs', 'tickets', 'wiki']).default('docs')
    .describe('Where to search'),
}
ニーズTypeScript の書き方
必須文字列z.string().describe('...')
任意パラメータz.string().optional().describe('...')
デフォルト値z.number().default(5)
列挙値z.enum(['docs', 'tickets'])
数値範囲z.number().min(1).max(10)
Python:3つの書き方をサポートします。 書き方1:シンプル dict。少数の単純なパラメーター向けです。key はパラメーター名、value は Python の型で、この書き方ではすべての key が required です。typing.Annotated でフィールドに説明を追加できます:
from typing import Annotated


input_schema = {
    "query": Annotated[str, "Search keywords"],
    "max_results": Annotated[int, "Maximum number of snippets to return"],
}
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 3.11+ は typing から直接インポート可能、3.10 は typing_extensions が必要):
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})"}]}
書き方3:完全な JSON Schema dict。enum、数値範囲、文字列フォーマット制約、ネストされたオブジェクトが必要な場合に使います:
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"],
}

ツールメタ情報の設定

annotations は MCP ツールアノテーションを渡します。SDK はこれを MCP tool 定義に載せ、CLI はスケジューリング、権限、状態表示に利用できます。
tool(
  'search_docs',
  'Search internal product documentation.',
  { query: z.string().describe('Search keywords') },
  async ({ query }) => ({ content: [{ type: 'text', text: query }] }),
  {
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
  },
);
よく使うフィールド:
フィールド意味
titlestringツールの人間可読なタイトル
readOnlyHintbooleanツールが読み取り専用で、状態を変更しないことを示す
destructiveHintbooleanツールがデータを変更・削除する可能性を示す
openWorldHintbooleanツールが外部システムやネットワークにアクセスすることを示す
maxResultSizeChars(Python)intPython SDK が _meta["anthropic/maxResultSizeChars"] 経由で CLI に渡し、ツール戻り値の長さ制限を緩和
注意:これらのフィールドは権限設定の代わりにはなりません。ツールの実行可否は引き続き tools、allow/deny ルール、権限モード、権限コールバック、hooks が決めます。MCP status にエコーされる annotations のフィールド名は、MCP 本来の *Hint 名ではなく、CLI が投影した readOnlydestructiveopenWorld になる場合があります。ホスト UI で表示が必要なら、自前のツール定義側でマッピングを保持してください。

ステップ2:MCP server に登録する

createSdkMcpServer() / create_sdk_mcp_server() は1つ以上のツールを同一プロセスの MCP server として登録します。server 名は完全ツール名の一部になるため、短く安定した名前を推奨します。
const kbTools = createSdkMcpServer({
  name: 'kb',
  version: '1.0.0',
  tools: [searchDocs],
});
フィールド設定例説明
namekborders などserver 名。mcp__{name}__{tool} の完全ツール名を構成します
version'1.0.0' など情報用バージョン。省略できます
tools[searchDocs, lookupOrder]この server に登録するツール一覧
Python の create_sdk_mcp_server() は同期バリデーションを行います:server 名 / tool 名 / tool 説明は空でない文字列であること、同一 server 内で tool 名が重複しないこと。

ステップ3:query() に接続する

server を MCP servers 設定に入れると、CLI がその中のツールを発見し、モデルが必要とするときに SDK 経由でハンドラーをコールバックします。
query({
  prompt: 'Search docs for the refund policy and summarize it.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { kb: kbTools },
    allowedTools: ['mcp__kb__search_docs'],
  },
});
カスタムツールの完全名形式は次のとおりです。
mcp__{serverName}__{toolName}
たとえば server 名が orders、tool 名が lookup_order なら、完全ツール名は mcp__orders__lookup_order です。この完全名はツールの許可/拒否リスト、権限コールバック、hooks matcher、サブ Agent の tools 設定で使われます。 Python の 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)

tool 権限の制御

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

権限制御方式の概要

方式(TypeScript / Python)役割粒度適した場面
toolsこのセッションに見えるツール集合を制限セッションモデルに見えるツールを元から絞りたい
allowedTools / allowed_toolsdisallowedTools / disallowed_tools特定ツールの事前承認または禁止ツール単位許可・禁止するツールが明確な場合
permissionMode / permission_modeセッション全体のデフォルト権限ポリシーを設定グローバルplan モード、編集自動承認、権限スキップなどの素早い切り替え
canUseTool / can_use_toolツールにユーザー確認が必要なときアプリケーションのコードを呼び出す呼び出し単位ツール入力から判断する、または AskUserQuestion を処理する
hooks.PreToolUsehooks ライフサイクルでツール呼び出しをインターセプト呼び出しhooks 体系を使っており、監査やブロックを統一したい
これらの方式は組み合わせられます。よくあるパターン:まず tools で可視ツールセットを絞り、allow/deny ルールで静的ルールを設定し、最後に権限コールバックで引数レベルの判断を行います。

方式1:ツールセットと allow/deny ルール

tools はセッションの可視ツールセットを制御し、allow/deny ルールは権限を制御します。カスタム MCP ツールは完全ツール名を使ってください。
// Only expose read/search tools to this session.
query({
  prompt: 'Analyze the repository without editing files.',
  options: {
    tools: ['Read', 'Glob', 'Grep'],
    allowedTools: ['Read', 'Glob', 'Grep'],
  },
});

// Explicitly deny high-risk tools.
query({
  prompt: 'Review the project and report issues.',
  options: {
    disallowedTools: ['Bash', 'Write', 'Edit'],
  },
});

// Use full names for custom MCP tools.
query({
  prompt: 'Check order O-1001.',
  options: {
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
  },
});

// Disable all tools. The model can only answer from its context.
query({
  prompt: 'Explain what this SDK does at a high level.',
  options: { tools: [] },
});
同じツールが許可ルールと禁止ルールの両方に一致した場合、禁止ルールが優先されます。

方式2:権限モード

権限モードは1行の設定でセッション全体のデフォルト権限動作を設定します。
query({
  prompt: 'Refactor the code.',
  options: {
    permissionMode: 'acceptEdits',
  },
});
モード効果
default標準の権限動作。機密性の高い操作はルールまたはランタイムポリシーに従って処理
acceptEditsファイル編集系の操作を自動承認。他の機密操作は引き続き権限ポリシーに従う
bypassPermissions権限チェックをスキップ。明示的なスキップ確認も同時に必要
yolobypassPermissions の互換エイリアス。同じく明示的な確認が必要
plan計画モード。まずモデルに方案を出させる用途に適する
dontAsk対話的確認なし。事前承認もルール許可もない操作は拒否される
autoランタイムが allow / deny を自動判断

方式3:権限コールバック

通常のツールでは、ユーザー承認が必要な場合だけ SDK が canUseTool / can_use_tool を呼び出します。すでに許可・拒否されている、または権限モードで自動処理された通常のツールは呼び出しません。AskUserQuestion は例外です。コールバック設定後、ユーザー操作が明示的に禁止されていなければ、実際の回答を得るために引き続き呼び出されます。完全な規則と回答形式は承認とユーザー入力を参照してください。
query({
  prompt: 'Check order O-1001.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['mcp__orders__lookup_order'],
    async canUseTool(toolName, input, options) {
      if (toolName !== 'mcp__orders__lookup_order') {
        return {
          behavior: 'deny',
          message: 'Only order lookup is allowed in this workflow.',
          toolUseID: options.toolUseID,
        };
      }

      return {
        behavior: 'allow',
        updatedInput: input,
        toolUseID: options.toolUseID,
      };
    },
  },
});
よく使う戻り値:
戻り値効果
allow元の引数で実行を許可
allow + 更新後の入力ツール引数を置き換えて実行を許可
deny + message実行を拒否。モデルは理由を見て別の方法を試せる
deny + message + interrupt: true拒否し、現在の agent loop を中断
サブ Agent 内でカスタムツールを使う場合も完全ツール名を使います。
query({
  prompt: 'Use the order-support agent to check order O-1001.',
  options: {
    auth: accessTokenFromEnv(),
    mcpServers: { orders: orderTools },
    allowedTools: ['Agent'],
    agents: {
      'order-support': {
        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 でツール呼び出しを統一的にインターセプトまたは監査できます。
query({
  prompt: 'Run the test command.',
  options: {
    allowedTools: ['Bash'],
    hooks: {
      PreToolUse: [
        {
          matcher: 'Bash',
          hooks: [
            async (input) => {
              const command = (input.tool_input as { command: string }).command;
              if (command.includes('rm -rf')) {
                return {
                  hookSpecificOutput: {
                    hookEventName: 'PreToolUse',
                    permissionDecision: 'deny',
                    permissionDecisionReason: 'rm -rf is not allowed',
                  },
                };
              }

              return {
                hookSpecificOutput: {
                  hookEventName: 'PreToolUse',
                  permissionDecision: 'allow',
                },
              };
            },
          ],
        },
      ],
    },
  },
});
PreToolUsepermissionDecisionallowdenyaskdefer のいずれかです。より完全な権限戦略は 権限制御 を参照してください。

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

ツールハンドラーにはいくつかのエラーパスがあります。

ビジネス上の失敗:明示的なエラーフラグを返す

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

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

ハンドラーが例外を投げた場合、MCP レイヤーが例外をエラー結果に変換するため、通常のツール例外で agent loop が直接クラッシュすることはありません。ただしモデルには通常例外メッセージしか見えず、明示的なエラーフラグ返却に比べてフォーマットと内容の制御性が劣ります。
const toolThatMayThrow = tool(
  'fetch_user',
  'Fetch a user by ID.',
  { userId: z.string() },
  async ({ userId }) => {
    const response = await userService.fetch(userId);
    if (!response.ok) {
      throw new Error('User service failed');
    }
    return { content: [{ type: 'text', text: await response.text() }] };
  },
);
推奨:ビジネス上予期可能な失敗にはエラーフラグを使い、本当に予期しない例外のみ throw してください。

不正な戻り値:SDK がエラーにラップ(Python)

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

Tool 戻り値

ツールハンドラーは MCP の CallToolResult を返します(Python では dict を返し、SDK が変換)。最もよく使うのはテキストコンテンツです:
return {
  content: [{ type: 'text', text: 'done' }],
};
構造化 JSON 文字列を返すこともできます。モデルが理解し、処理を続けやすくなります。
return {
  content: [{
    type: 'text',
    text: JSON.stringify({
      orderId: 'O-1001',
      status: 'shipped',
      eta: '2026-05-20',
    }),
  }],
};
よく使うコンテンツブロック:
種類形状説明
テキスト{ type: 'text', text }最も一般的。自然言語または JSON 文字列に適しています
画像{ type: 'image', data, mimeType }data は base64
音声{ type: 'audio', data, mimeType }data は base64(TypeScript)
リソースリンク{ type: 'resource_link', uri, name?, description?, mimeType? }参照可能なリソースを返す。Python ではテキストに降格し、name / uri / description を連結してモデルに渡す
埋め込みリソース{ type: 'resource', resource }テキストまたはバイナリのリソース内容を返す。Python では埋め込みテキストリソースを TextContent に変換
Python 版にはさらに 2 つ注意すべき結果の差異があります。
  • handler が返す dict のトップレベル _metaCallToolResult に透過されません。
  • handler がエラーマークを返す際は Python のフィールド名 "is_error": True を使用します。MCP/TypeScript 風の isError ではありません。SDK 内部で MCP の結果にマッピングされます。

よくある落とし穴

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

続きを読む

  • Tools Reference:組み込みツールリスト、ツール作成 API、CallToolResult、組み込みツールの入出力型。
  • MCP 統合:インプロセス、stdio、SSE、HTTP、OAuth などの MCP サーバー接続方式。
  • 権限制御:権限モード、ツール許可リスト、権限コールバック、権限ルール更新。
  • サブ Agent 使用ガイド:異なる Agent に異なるツール集合を使わせる方法。