- Built-in tools: Provided by Qoder CLI, such as reading files, searching, executing commands, and invoking subagents.
- Custom tools: Defined by SDK users through
tool()andcreateSdkMcpServer(), then exposed to the model.
Built-in Tools
When using built-in tools, you do not implement the tools yourself. You only control which tools are available, which tools are pre-authorized, and which tools are denied for the current session throughquery() options.
Read, Edit, Write, Bash, Glob, Grep, WebFetch, WebSearch, and Agent. The complete list and names are defined by Tools Reference - Built-in Tool List. Input and output structures are documented in Built-in Tool Input and Output Types, such as FileReadInput / FileReadOutput, BashInput / BashOutput, and AgentInput / AgentOutput. If you need TypeScript-level unified representations, see ToolInputSchemas and ToolOutputSchemas.
Custom Tools
Define a custom tool when you want the model to call your own business capability, such as order lookup, internal knowledge base search, approval system calls, or read-only database access. Custom tools usually involve three steps:- Create a tool with
tool(). - Register the tool in an MCP server with
createSdkMcpServer(). - Attach the server through
mcpServersinquery({ options }), and control calls with permission settings.
Custom Tool Integration Steps
First, here is a complete minimal example. The following sections then explain each step.Step 1: Create a Tool with tool()
This step defines the tool itself: its name, description, input parameters, execution logic, and metadata.
tool() Arguments
tool() defines a tool. It has five arguments. See tool() for the complete type.
| Argument | Type | Required | Meaning |
|---|---|---|---|
name | string | Yes | Unique tool identifier within the current MCP server |
description | string | Yes | Tool description for the model; explain when to use it, what it does, and what it returns |
inputSchema | Schema extends AnyZodRawShape | Yes | Zod raw shape that defines tool input parameters |
handler | Function | Yes | Async execution function that receives parsed parameters and returns CallToolResult |
extras | ToolExtras | No | Extra tool metadata, currently used for annotations |
Configure Input Parameters
inputSchema takes a Zod raw shape, meaning a field object, not z.object(...). See AnyZodRawShape for the type and InferShape for handler parameter inference.
| Need | Pattern |
|---|---|
| Required string | z.string().describe('...') |
| Optional parameter | z.string().optional().describe('...') |
| Default value | z.number().default(5) |
| Enum | z.enum(['docs', 'tickets']) |
| Numeric range | z.number().min(1).max(10) |
Configure Tool Metadata
extras.annotations passes MCP tool annotations. The SDK keeps them on the tool definition and forwards them when registering the tool with the MCP server. See ToolExtras and ToolAnnotations for the complete fields.
| Field | Type | Optional | Meaning |
|---|---|---|---|
title | string | Yes | Human-readable title for the tool |
readOnlyHint | boolean | Yes | Marks the tool as read-only and not modifying state |
destructiveHint | boolean | Yes | Marks that the tool may modify or delete data |
openWorldHint | boolean | Yes | Marks that the tool accesses external systems or networks |
tools, allowedTools, disallowedTools, permissionMode, canUseTool, and hooks. Current mcpServerStatus().tools[] does not echo annotations back to the host application. If your host UI needs to show this information, keep your own mapping next to the tool definition.
Example:
Step 2: Register with an MCP Server
createSdkMcpServer() registers one or more tools as an in-process MCP server. The server name becomes part of the full tool name, so keep it short and stable.
| Field | How to set it | Description |
|---|---|---|
name | For example kb, orders | Server name; forms full tool names like mcp__{name}__{tool} |
version | For example '1.0.0' | Informational version, optional |
tools | [searchDocs, lookupOrder] | Tools registered to this server |
CreateSdkMcpServerOptions for the full option type and createSdkMcpServer() return value for the return value.
Step 3: Attach to query()
After you put the server in options.mcpServers, the CLI discovers its tools and calls back into your handler through the SDK when the model needs them.
orders and the tool name is lookup_order, the full name is mcp__orders__lookup_order. Use this full name in allowedTools, disallowedTools, canUseTool, hook matchers, and subagent tools configuration.
Controlling Tool Permissions
When the model calls tools, the SDK provides multiple permission layers. You can decide:- Which tools are provided to the current session.
- Which tools are allowed by default.
- Which tools are explicitly denied.
- Whether the host application should make a dynamic decision before each tool call.
Permission Control Overview
| Method | Purpose | Granularity | Use case |
|---|---|---|---|
tools | Limits the visible tool set for this session | Session | Narrow the tools the model can see at the source |
allowedTools / disallowedTools | Pre-authorizes or denies specific tools | Tool | You know exactly which tools to allow or deny |
permissionMode | Sets the default permission policy for the whole session | Global | Quickly switch plan mode, accept edits, or skip permissions |
canUseTool | Runs custom logic before each call | Call | Decide dynamically based on arguments |
hooks.PreToolUse | Intercepts tool calls through the hooks lifecycle | Call | You already use hooks and want shared auditing or blocking |
tools to narrow the visible set, allowedTools / disallowedTools for static rules, and canUseTool for argument-level decisions.
Method 1: tools, allowedTools, disallowedTools
tools controls the tools visible to this session. allowedTools and disallowedTools control permission rules. Custom MCP tools must use full names.
Method 2: permissionMode
permissionMode sets the default permission behavior for the whole session with one option.
| Mode | Effect |
|---|---|
'default' | Standard permission behavior; sensitive operations are handled by rules or runtime policy |
'acceptEdits' | Automatically accepts file edit operations; other sensitive operations still follow the permission policy |
'bypassPermissions' | Skips permission checks; must also set allowDangerouslySkipPermissions: true |
'yolo' | Compatibility alias for 'bypassPermissions'; must also set allowDangerouslySkipPermissions: true |
'plan' | Plan mode, suitable for asking the model to produce a plan first |
'dontAsk' | Does not ask interactively; operations that are not pre-authorized or allowed by rules are denied |
'auto' | Runtime capability decides allow or deny automatically |
Method 3: canUseTool
canUseTool runs before a tool call. You can allow or deny based on the tool name, arguments, and approval context.
| Return | Effect |
|---|---|
{ behavior: 'allow' } | Allows execution with the original arguments |
{ behavior: 'allow', updatedInput: {...} } | Allows execution and replaces tool arguments |
{ behavior: 'deny', message: 'reason' } | Denies execution; the model can see the reason and try another way |
{ behavior: 'deny', message: 'reason', interrupt: true } | Denies and interrupts the current agent loop |
Method 4: hooks.PreToolUse
If you already use the hooks system, use PreToolUse to intercept or audit tool calls in one place.
canUseTool parameter structure, see CanUseToolOptions. For return structure, see PermissionResult. For a more complete permission strategy, see Permissions.
How the SDK Handles Tool Errors
Tool handlers have two error paths.Business Failure: Return isError: true
For expected business failures, return isError: true. The SDK passes this CallToolResult to the CLI. The model can see the failure content and may retry or choose another path.
isError: true:
- Arguments are valid, but no business result exists, such as an order not found.
- A security policy rejects execution, such as only allowing
SELECTqueries. - An external service returns a business error that can be explained.
Unexpected Exception: Handler Throws
If a handler throws, the MCP layer converts the exception into an error result, and the agent loop does not crash just because a normal tool exception occurred. However, the model usually only sees the exception message, which is less controllable than explicitly returningisError: true.
isError: true for predictable business failures, and throw only for truly unexpected exceptions.
Tool Return Values
Tool handlers return MCPCallToolResult. Text content is the most common:
McpToolResultContent for the complete union type:
| Type | Shape | Description |
|---|---|---|
| Text | { type: 'text', text } | Most common; suitable for natural language or JSON strings |
| Image | { type: 'image', data, mimeType } | data is base64 |
| Audio | { type: 'audio', data, mimeType } | data is base64 |
| Resource link | { type: 'resource_link', uri, name?, description?, mimeType? } | Returns a referenceable resource |
| Embedded resource | { type: 'resource', resource } | Returns text or binary resource content |
Common Pitfalls
- When writing permission configuration for custom tools, use the full
mcp__server__toolname. - Pass a Zod raw shape as the third argument to
tool(), notz.object(...). - Tool descriptions should explain when to use the tool, what it does, and what it returns. Avoid vague descriptions like
queryorhelper. readOnlyHintis tool metadata and a scheduling hint, not a permission switch. Whether execution is allowed is still determined by permission configuration.- Avoid putting a huge all-purpose business entry point into one universal tool. A tool should complete one clear class of action.
Continue Reading
- Tools Reference: Built-in tool list,
tool(),createSdkMcpServer(),CallToolResult, and built-in tool input/output types. - MCP Integration: stdio, SSE, HTTP, OAuth, and other MCP server integration methods.
- Permissions:
permissionMode,allowedTools,canUseTool, and permission rule updates. - Subagent Guide: Let different agents use different tool sets.