Give Claude custom tools changedagent-sdk/custom-tools
Nearest release: v2.1.311, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 26 Oct 2026 18:49 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 26 Oct 2026 19:07 UTC.
Upstream edited
Recorded here
Lines+29added
Lines−1removed
From line
12
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits16to this page, all time
### Require approval for specific tools
The whole hunk
from line 12, old and new numbered
/
from line 12
1212| Make a parameter optional | Declare it optional in the schema and apply the default in the handler. See [Make a parameter optional](#make-a-parameter-optional). |
1313| Register a tool with Claude | Wrap in `create_sdk_mcp_server` / `createSdkMcpServer` and pass to `mcpServers` in `query()`. See [Call a custom tool](#call-a-custom-tool). |
1414| Pre-approve a tool | Add to your allowed tools. See [Configure allowed tools](#configure-allowed-tools). |
15| Require approval for calls to a tool, or block the tool | In the TypeScript SDK, add a `permission_policy` for the tool to the config object that `createSdkMcpServer()` returns. See [Require approval for specific tools](#require-approval-for-specific-tools). |
1516| Remove a built-in tool from Claude's context | Pass a `tools` array listing only the built-ins you want. See [Configure allowed tools](#configure-allowed-tools). |
1617| Let Claude call tools in parallel | Set `readOnlyHint: true` on tools with no side effects. See [Add tool annotations](#add-tool-annotations). |
1718| Control the error message Claude reads | Return `isError: true` to compose the message instead of surfacing the raw exception. See [Handle errors](#handle-errors). |
from line 324
323324| Field | Default | Meaning |
324325| :- | :- | :- |
325326| `readOnlyHint` | `false` | Tool does not modify its environment. Controls whether the tool can be called in parallel with other read-only tools. |
326| `destructiveHint` | `true` | Tool may perform destructive updates. Informational only. |
327| `destructiveHint` | `true` | Tool may perform destructive updates. Set it to `true` explicitly on any tool you [mark `always_ask`](#require-approval-for-specific-tools). |
327328| `idempotentHint` | `false` | Repeated calls with the same arguments have no additional effect. Informational only. |
328329| `openWorldHint` | `true` | Tool reaches systems outside your process. Informational only. |
329330
from line 379
378379| disallowed tools | Both | A bare tool name such as `"Bash"` removes the tool from Claude's context, the same as omitting it from `tools`. A scoped rule such as `"Bash(rm *)"` leaves the tool in context and denies only calls that match [as written](https://code.claude.com/docs/en/permissions.md#bash-rule-limits). |
379380
380381To remove a built-in entirely, omit it from `tools` or list its bare name in `disallowedTools` (Python: `disallowed_tools`); both keep the tool out of context so Claude never attempts it. A scoped `disallowedTools` rule blocks matching calls but leaves the tool visible, so Claude may waste a turn trying it. See [Configure permissions](https://code.claude.com/docs/en/agent-sdk/permissions.md) for the full evaluation order.
382
383### Require approval for specific tools
384
385Set a tool's `permission_policy` to `always_ask`, and `allowedTools` stops approving that tool's calls, so they can reach your [`canUseTool`](https://code.claude.com/docs/en/agent-sdk/user-input) callback even though the tool is listed. Set `always_deny` instead, and [a call to the tool is denied](https://code.claude.com/docs/en/agent-sdk/permissions.md#how-permissions-are-evaluated), but a [mod](https://code.claude.com/docs/en/plugins/mods/overview.md) in a plugin you load [can still approve it](https://code.claude.com/docs/en/permissions.md#precedence-among-rules-hooks-and-mods). The field is TypeScript-only and requires Agent SDK v0.3.301 or later.
386
387Put the policy entry in a `tools` array on the config object `createSdkMcpServer()` returns, not in the `tools` option you pass into that function. This example spreads `weatherServer` from the [weather tool example](#weather-tool-example) and adds a `tools` array giving `get_temperature` the `always_ask` policy:
388
389```typescript TypeScript theme={null}
390options: {
391 mcpServers: {
392 weather: {
393 ...weatherServer,
394 tools: [{ name: "get_temperature", permission_policy: "always_ask" as const }]
395 }
396 },
397 allowedTools: ["mcp__weather__get_temperature"],
398 permissionMode: "default",
399 canUseTool: async (toolName, input) => {
400 console.log(`Approval requested for ${toolName}`);
401 return { behavior: "allow", updatedInput: input };
402 }
403}
404```
405
406To try the policy, replace the `options` in the [Call a custom tool](#call-a-custom-tool) script with this example's `options`, then run that script from your terminal. The script prints `Approval requested for mcp__weather__get_temperature`, followed by Claude's answer about the temperature. For a complete script, see the [TypeScript reference](https://code.claude.com/docs/en/agent-sdk/typescript.md#mcpsdkserverconfigwithinstance).
407
408If you use `auto` or `plan` [permission mode](https://code.claude.com/docs/en/agent-sdk/permissions.md#permission-modes), set `destructiveHint: true` in the tool's [annotations](#add-tool-annotations) so calls to the tool still reach your callback. Otherwise the [auto mode classifier](https://code.claude.com/docs/en/permission-modes.md#eliminate-prompts-with-auto-mode) can approve some `always_ask` calls, under [conditions listed in the TypeScript reference](https://code.claude.com/docs/en/agent-sdk/typescript.md#permission-modes-that-can-approve-an-always_ask-call), and a call it approves doesn't reach your callback. Set the hint explicitly, because an unset `destructiveHint` counts as `false` for `always_ask` calls rather than defaulting to `true`.
381409
382410## Handle errors
383411
No line in this hunk matches that.