What you will be able to do
- Decide whether a task needs anything beyond an agent's built-in tools
- Recognise when a custom tool is worth building, and how the Agent SDK registers one
- Choose an MCP server when the capability lives in an external system
Key concept
Name the gap, then add the smallest fix — Each way of extending an agent fills a different gap. Built-in tools are actions the agent already has. A custom tool adds a function you write. MCP connects an outside system. A Skill adds knowledge or a workflow, not a new action. To choose well, first name what is actually missing.
1.Start from the tools the agent already has
Every agentic customization decision starts with the same step: work out what the agent can't do yet, then add the smallest thing that fixes it. There are four options. Built-in tools are actions the agent already has. A custom tool is a function you write. An MCP server connects an outside system. A Skill adds knowledge or a workflow instead of a new action. This page covers the first three. The cheapest option is always the one you don't have to build.
Claude Code and the Agent SDK come with a working set of tools. Before you design anything, check whether that set already covers the job.
| Tool | What it does | Permission required |
|---|---|---|
| Read | Reads the contents of files | No |
| Edit | Makes targeted edits to specific files | Yes |
| Write | Creates or overwrites files | Yes |
| Bash | Executes shell commands in your environment | Yes |
| Grep | Searches for patterns in file contents | No |
| WebFetch | Fetches content from a specified URL | Yes |
| Skill | Executes a skill within the main conversation | Yes |
| ListMcpResourcesTool | Lists resources exposed by connected MCP servers | No |
Reading files, editing them, running shell commands and searching code are all built in. A refactor that stays inside the local repository needs no customization. Start with the built-ins and a clear prompt. Every layer you add is something else to maintain.
A fintech startup is building an internal support agent with the Claude Agent SDK. The agent must look up account balances from a proprietary ledger service reachable only through an internal REST gateway with no existing MCP server or public SDK. Which approach best fits this requirement?
Correct answer: A — Define a custom tool that calls the internal REST gateway and register it through an in-process MCP server
- A. Correct: a custom tool wraps the handler logic for calling the proprietary gateway and is registered via create_sdk_mcp_server/createSdkMcpServer, giving Claude a purpose-built function for this internal system.
- B. WebFetch is a general-purpose built-in tool for parsing public web pages; it is not designed for authenticated internal gateway calls with structured request/response handling.
- C. A Skill supplies knowledge or workflow instructions, not a live connection to an external system, so Claude would have no way to actually retrieve real balance data.
- D. No public integration exists for this proprietary internal service, so a community MCP server would not have the credentials or schema to reach it.
On the Messages API the same tradeoff appears in a different form: who writes the tool's schema, and who runs its code?
| Tool category | Who writes the schema | Where the call executes |
|---|---|---|
| User-defined tools | You | Your application |
| Anthropic-defined schemas, such as bash and text_editor | Anthropic, which trains Claude on it | Your application, which returns the tool_result |
| Server tools, such as web_search, web_fetch, code_execution, tool_search | Anthropic | Anthropic's infrastructure |
The middle row is the one people get wrong. Anthropic-defined tools such as bash and text_editor come with a schema Claude was trained on, so you don't have to design one. The call still goes to your application to run. Server tools are the only kind you neither define nor run.
2.Write a custom tool when the logic is yours
Build a custom tool when only you can supply the logic. Examples are a call to an internal API, a domain-specific calculation, or a lookup against data the agent can't reach with Bash or Read. The cost is that you now own the whole contract: you write the schema, and your application runs every call.
In the Agent SDK a tool has four parts: a name Claude calls it by, a description, an input schema and an async handler. The description matters most, because it is the text Claude reads when deciding whether the tool fits a request.
// Define a tool: name, description, input schema, handler
const getTemperature = tool(
"get_temperature",
"Get the current temperature at a location",
{
latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
longitude: z.number().describe("Longitude coordinate")
},You can't pass the SDK a bare tool. You register it by wrapping it in an in-process MCP server and passing that server to query(). The tool then gets a name prefixed with the server's name, and that prefixed name is also what you pre-approve:
// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [getTemperature]
});for await (const message of query({
prompt: "What's the temperature in San Francisco?",
options: {
mcpServers: { weather: weatherServer },
allowedTools: ["mcp__weather__get_temperature"]
}
})) {The SDK exposes the tool through an in-process MCP server named weather, created with createSdkMcpServer (create_sdk_mcp_server in Python). As the example shows, the tool is then called mcp__weather__get_temperature, so the server name is part of the name you pre-approve.
Claude reads whatever the handler returns. The result must contain a content array, and each block's type must be text, image, audio, resource or resource_link. It can also include structuredContent for machine-readable data. Setting isError: true lets you write the failure message Claude sees, rather than passing along the raw exception.
Two more settings control how your tool sits alongside the built-ins. Setting readOnlyHint: true on a tool with no side effects lets Claude call it in parallel. Passing a tools array that lists only the built-ins you want removes the others from Claude's context.
A platform team keeps pasting the same multi-step production deployment checklist into chat every time someone ships a release, and the steps sometimes get skipped when typed manually. What is the most appropriate way to address this in Claude Code?
Correct answer: A — Capture the checklist as a Skill invoked with a slash command like /deploy
- A. Correct: a repeated multi-step procedure pasted into chat is the canonical trigger for turning it into a Skill, which loads on demand via /deploy instead of being retyped.
- B. CLAUDE.md is meant for always-on conventions loaded every session; a deployment procedure is a workflow rather than a persistent fact, so it belongs in a Skill instead.
- C. MCP servers connect Claude to external systems and expose tools or resources from those systems; building one just to store static checklist text adds unnecessary infrastructure.
- D. A custom tool wraps a function Claude calls to perform an action or fetch data; simply printing static text is not a tool's purpose and adds needless complexity for no benefit.
3.Connect an MCP server when the system lives elsewhere
When the capability lives in someone else's system, the answer is usually MCP, the protocol Claude Code uses to connect to external services. The features guide gives Slack, database queries and browser control as examples. Its rule of thumb is concrete: if you keep copying data out of a browser tab Claude can't see, connect that system as an MCP server.
If a service already publishes an MCP server, connecting it in Claude Code takes one command:
# Real example: Connect to Notion
claude mcp add --transport http notion https://mcp.notion.com/mcpClaude then has the service's tools and you have written no handlers. Typical uses are building features from issue trackers, analysing monitoring data and querying databases. A server can even act as a channel that pushes messages into your session, so Claude responds to outside events. On the Messages API, the MCP connector reaches remote MCP servers without you building a separate MCP client. If the same server name is configured at more than one scope, only one applies: local beats project, and project beats user.
| Option | Pick it when | What you take on |
|---|---|---|
| Built-in tools | The job is files, shell, search or web on the agent's own machine | Nothing to build. Narrow the set with a tools array if needed |
| Custom tool | The logic is yours: internal APIs, domain calculations | Name, description, schema and handler, wrapped with createSdkMcpServer |
| MCP server | The capability lives in an external service | Connect it (for example with claude mcp add). Configure scope and auth |
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Tools with Anthropic-defined schemas, such as bash and text_editor, run on Anthropic's servers the way web_search does.Why is that wrong?
They are client tools. Anthropic supplies the schema, but your application still runs every call and returns the result.
Covered in Start from the tools the agent already has
2.An Agent SDK custom tool is a separate mechanism from MCP, so building one means you are not using MCP.Why is that wrong?
The SDK registers custom tools by wrapping them in an in-process MCP server that you pass to query().
Covered in Write a custom tool when the logic is yours
3.Claude decides when to call a custom tool from its input schema.Why is that wrong?
The schema defines the arguments. Claude reads the description to decide when to call the tool.
Covered in Write a custom tool when the logic is yours
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://code.claude.com/docs/en/tools-referenceOfficial docs
“Executes shell commands in your environment.”
↩︎ Start from the tools the agent already has - 2.
“Tools differ primarily by where the code executes.”
↩︎ Start from the tools the agent already has“Client tools (including user-defined tools and tools with Anthropic-defined schemas, such as bash and text_editor) run in your application.”
↩︎ Start from the tools the agent already has“For tools you define, you write the schema and your application executes each call.”
↩︎ Write a custom tool when the logic is yours“Connect to remote MCP servers from the Messages API without a separate MCP client.”
↩︎ Connect an MCP server when the system lives elsewhere“Anthropic publishes the schema and trains Claude on it. Your application still executes each call and returns the tool_result.”
↩︎ Exam trap 1 - 3.
“Use @tool (Python) or tool() (TypeScript) with a name, description, schema, and handler.”
↩︎ Write a custom tool when the logic is yours“Return isError: true to compose the message instead of surfacing the raw exception.”
↩︎ Write a custom tool when the logic is yours“Set readOnlyHint: true on tools with no side effects.”
↩︎ Write a custom tool when the logic is yours“Pass a tools array listing only the built-ins you want.”
↩︎ Write a custom tool when the logic is yours“Wrap in create_sdk_mcp_server / createSdkMcpServer and pass to mcpServers in query().”
↩︎ Exam trap 2“Description: what the tool does. Claude reads this to decide when to call it.”
↩︎ Exam trap 3 - 4.https://code.claude.com/docs/en/features-overviewOfficial docs
“Protocol for connecting to external services”
↩︎ Connect an MCP server when the system lives elsewhere“Connect that system as an MCP server”
↩︎ Connect an MCP server when the system lives elsewhere“MCP servers override by name: local > project > user.”
↩︎ Connect an MCP server when the system lives elsewhere“MCP connects Claude to external services and tools”
↩︎ Key concept - 5.https://code.claude.com/docs/en/mcpOfficial docs
“an MCP server can also act as a channel that pushes messages into your session”
↩︎ Connect an MCP server when the system lives elsewhere