CClaude Cert Prep
V810.6% of exam

CCDV-F Domain 8: Tools and MCPs

How to define tools Claude actually calls, run the tool_use/tool_result loop correctly, and choose between built-in tools, custom tools, Skills, and MCP servers — including what changed in the MCP spec's July 2026 revision.

16 min read Reviewed August 24, 2026
On this page

Domain 8 of the Claude Certified Developer – Foundations (CCDV-F) exam covers Tools and MCPs, worth roughly 10.6% of the exam, split across three sub-objectives: Tool Implementation (4.4%) — function calling, description writing, error handling, and the client-side/server-side split; MCP Server Development (2.1%) — authoring, deployment, and the resources/tools/prompts model; and Agentic Customization (4.1%) — knowing when to reach for a built-in tool, a custom tool, a Skill, or an MCP server.

This guide is an independent, unofficial study resource. It is not affiliated with, authorized by, or endorsed by Anthropic, and it reproduces no real exam items. Every fact below is checked against current platform.claude.com and modelcontextprotocol.io documentation as of August 2026.

This is one of the more freshness-sensitive domains on the exam. MCP's specification was revised on 2026-07-28, and several of the changes invert what was true a year earlier — a stateful protocol became stateless, HTTP+SSE was superseded by Streamable HTTP, and Dynamic Client Registration was superseded by Client ID Metadata Documents. A question written against the older spec reads as correct until you check the revision date. Treat every MCP claim in this guide as dated to that revision, and treat "which transport/registration flow is current" as the kind of trap a newly-launched exam is likely to test.

Tool definitions: schema, and why most tools under-trigger

A tool definition has exactly three required parts: name, description, and input_schema. The schema is plain JSON Schema — type: "object" at the root, a properties map describing each parameter, and a required array naming which of those parameters must be present. Claude never executes your tool directly; it emits a structured request for your application to run, so the schema is the entire contract between the model's intent and your code's expectations.

json
{
  "name": "get_current_price",
  "description": "Look up the current market price for a stock ticker. Call this when the user asks about a stock's current, live, or today's price — do not use it for historical prices or general company information, and do not answer price questions from prior knowledge.",
  "input_schema": {
    "type": "object",
    "properties": {
      "ticker": {
        "type": "string",
        "description": "Stock ticker symbol, e.g. AAPL"
      }
    },
    "required": ["ticker"]
  },
  "strict": true
}
A tool definition written for correct triggering, not just correct shape
  • name and description are both required, plain strings — Claude reads the description on every turn it considers using a tool, so treat it as documentation the model will act on, not internal comments.
  • input_schema must be a JSON Schema object with type: "object"; use enum for parameters with a fixed set of legal values rather than describing the constraint in prose.
  • Give every property its own description — Claude uses per-parameter descriptions to decide what value to fill in, separately from the tool-level description that decides whether to call it at all.
  • Add strict: true at the top level of the tool definition (a sibling of name/description/input_schema, not a tool_choice setting) when you need guaranteed schema conformance — the returned input is validated exactly against the schema, which removes an entire class of "my code crashed parsing the model's arguments" bugs.
Description styleExampleWhat happens
Under-described (what only)"Gets weather data."Model may not call the tool at all when it should, especially at points in a conversation where the need is implicit rather than stated
Prescriptive (what + when)"Call this when the user asks about current or forecasted weather for a specific location."Measurably higher correct-call rate — the trigger condition is explicit, not inferred
Over-specified with embedded examplesMulti-turn worked dialogue baked into the descriptionWastes context on every request and constrains exploration; better placed in a Skill or progressive-disclosure doc than in the tool description itself

The tool_use / tool_result loop, and how is_error carries failure back

When Claude decides to call a tool, the response carries stop_reason: "tool_use" and one or more tool_use content blocks — each with an id, a name, and an input object matching your schema. This applies to client-side tools: tools you defined and must execute yourself. Server-side tools such as web search, web fetch, and code execution are resolved by Claude directly and never generate a tool_use block your application has to handle.

Your application executes the requested client-side tool(s) and sends the result back as a tool_result content block in the next user turn. The tool_use_id on that block must exactly match the id of the originating tool_use block — this is how Claude correlates a result with the call that produced it, and it is the single most common source of "the model ignored my tool result" bugs when it doesn't match.

json
// 1) Claude's response — stop_reason: "tool_use"
{
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "Let me check that." },
    {
      "type": "tool_use",
      "id": "toolu_01A1b2C3",
      "name": "get_current_price",
      "input": { "ticker": "AAPL" }
    }
  ]
}

// 2) Your next user turn — tool_result matched by tool_use_id
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A1b2C3",
      "content": "AAPL: $228.14"
    }
  ]
}
Round trip: Claude requests a tool call, your app returns the result
json
{
  "type": "tool_result",
  "tool_use_id": "toolu_01A1b2C3",
  "content": "Error: ticker 'ZZZZZ' not found. Provide a valid ticker symbol.",
  "is_error": true
}
A failed tool call, surfaced correctly

Parallel tool calls and tool_choice

Parallel tool calls are on by default: a single assistant turn can contain multiple tool_use blocks in one response, and your application is expected to execute them concurrently. Once all results are ready, return every corresponding tool_result block together in one user message — not spread across several follow-up messages.

  • Default behavior: Claude may emit multiple tool_use blocks per turn when it judges the calls to be independent (e.g. looking up weather in three different cities).
  • Your application should execute independent tool calls concurrently, not serially, to avoid unnecessary latency.
  • To force single-tool-per-turn behavior, set disable_parallel_tool_use: true inside tool_choice — this applies regardless of which tool_choice type you're also using.
tool_choice valueBehavior
{"type": "auto"}Default. Claude decides whether to use a tool at all, and which one(s).
{"type": "any"}Claude must use some tool, but can pick which.
{"type": "tool", "name": "..."}Claude must use the exact tool named.
{"type": "none"}Claude is not permitted to use any tool this turn.

tool_choice is set per-request, so it's normal to force {"type": "tool", "name": "..."} on one call — for example, when you already know the user wants a specific action performed — and fall back to "auto" for open-ended turns.

MCP fundamentals: roles, primitives, and transports

The Model Context Protocol (MCP) standardizes how an AI application connects to external context and capabilities. A Host application (Claude Code, Claude Desktop, or your own app) creates one MCP client per connected MCP server; each server exposes context and functionality to the host over that connection.

PrimitiveExposed byControlled byWhat it is
ToolsServerModelActions the model can decide to invoke — the MCP analog of a client-side tool
ResourcesServerApplicationRead-only data the host application surfaces — the app decides when and how to expose it, not the model
PromptsServerUserReusable prompt templates the user selects, not something the model triggers on its own
ElicitationClientServerLets a connected server ask the human user for input mid-interaction

MCP servers support two transports. stdio is for local, single-client servers — the host launches the server as a subprocess and communicates over standard input/output, which is the natural fit for local development tooling. Streamable HTTP is for remote servers that need to support many concurrent clients; it supports SSE-style streaming over a standard HTTP connection.

TransportUse caseConcurrency
stdioLocal/dev MCP servers, single client, subprocess-basedOne client per server process
Streamable HTTPRemote, hosted MCP serversMany concurrent clients, with SSE-style streaming support

Building a minimal MCP server

Everything in the previous section describes MCP from the consuming side — a host connecting to a server someone else already built. The "MCP Server Development" sub-objective is about the other side: writing that server. A minimal MCP server only needs to do three things: instantiate a server object, register at least one tool with a name, a description, and an input schema in the same JSON-Schema shape covered in this guide's first section, and connect a transport so a host can reach it. Nothing else is required to have a working server.

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "example-server", version: "1.0.0" });

server.tool(
  "get_weather",
  "Look up the current weather for a city. Call this when the user asks about current conditions.",
  { city: z.string().describe("City name, e.g. 'Austin'") },
  async ({ city }) => {
    const data = await fetchWeather(city); // your own implementation
    return { content: [{ type: "text", text: JSON.stringify(data) }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
A minimal MCP server: one tool, stdio transport, using the official @modelcontextprotocol/sdk package

McpServer is the high-level, ergonomic API the TypeScript SDK provides specifically so you don't hand-roll the underlying JSON-RPC request handlers yourself — server.tool(...) registers a tool's name, description, and parameter shape in one call, and the SDK derives the JSON-Schema Claude sees from that shape and validates incoming calls against it before your handler runs. TypeScript and Python are the two officially-supported languages for both the Claude SDK and the MCP SDK, so this is the shape worth being able to read even if your own server ends up in Python. SDK APIs evolve, so check the current @modelcontextprotocol/sdk documentation for the exact method signature before shipping — the structure above (instantiate, register, connect) is what to hold onto.

ScenarioTransportWhere it runs
Personal, single-client use — e.g. your own Claude Code integrationstdioA local subprocess your host launches; no hosting or deployment needed
Reusable across multiple remote clients/hosts — the scenario that justifies MCP over a custom tool in the first placeStreamable HTTP (see the spec-revision section below)Real infrastructure: a container or small server that stays up and reachable, not just a script

What changed in MCP's 2026-07-28 spec revision

  • Stateless protocol. MCP is now explicitly stateless — capability and version information is carried per-request via _meta rather than negotiated once and held in session state.
  • Mandatory `server/discover` handshake. A new required handshake replaces the older assumption of a single up-front initialize exchange whose results persisted for the connection's lifetime.
  • Sampling and Logging primitives are deprecated. Don't teach or expect these as current best practice — use direct LLM-provider APIs in place of the old Sampling primitive, and OpenTelemetry in place of the old Logging primitive.
  • Notifications are opt-in. Subscribe via subscriptions/listen; the old push-by-default notification model is gone.

Transport and auth also changed. Streamable HTTP has fully superseded the older, separate "HTTP+SSE" transport — don't present HTTP+SSE as the current recommendation for remote MCP servers; it's the pattern Streamable HTTP replaced.

Agentic customization: built-in tools vs. custom tools vs. Skills vs. MCP servers

A real decision developers face on every project: given a capability the agent needs, which mechanism should implement it? The right answer trades off implementation effort, how much control you need over execution, and how reusable the capability needs to be across different tools and hosts.

MechanismWho runs itEffortControlBest for
Built-in tools (web_search, code_execution, etc.)Anthropic-hostedLowest — no server to runLeast — you can't change how it executesCapabilities Anthropic already hosts and you don't need to customize
Custom client-side toolsYour applicationYou own implementation and hostingFull control over execution, gating, and side effectsActions specific to your app's data or systems, especially ones needing approval gates or auditing
Skills (.claude/skills/*/SKILL.md)Loaded on-demand inside Claude CodeLow — a folder with a SKILL.mdInstructional, not executional — packages guidance/workflow, not a new capabilityRepeatable, on-demand instructions specific to one Claude Code project or workflow
MCP serversYou (or a third party) host; any MCP host connectsModerate — a server to author and deployFull control, reusable across hostsThe same tool/resource needs to work across multiple different AI applications, not just one Claude Code instance

One Anthropic-specific detail worth knowing at the API level: the Messages API supports an mcp_servers / mcp_toolset parameter (the "MCP connector," currently in beta) that lets Claude call a remote MCP server directly, server-side, without your application acting as the MCP client itself. This sits alongside — not instead of — the standard client-side approach of connecting to an MCP server from your own host application.

Key takeaways

  • 01A tool definition needs name, description, and input_schema; add strict: true when you need guaranteed schema conformance on the returned input.
  • 02Write tool descriptions to be prescriptive about when to call the tool, not just what it does — under-description, not over-description, is the more common real-world failure mode on current models.
  • 03Claude signals a client-side tool call with stop_reason: "tool_use"; your tool_result's tool_use_id must exactly match the originating tool_use.id.
  • 04is_error: true on a failed tool_result is the primary mechanism for telling Claude a tool call failed — always return a result, even on failure.
  • 05Parallel tool calls are on by default: execute concurrent tool_use blocks concurrently and return all tool_result blocks together in one user message.
  • 06MCP's 2026-07-28 spec revision made the protocol stateless, added a mandatory server/discover handshake, deprecated Sampling and Logging, made notifications opt-in, finalized Streamable HTTP over the older HTTP+SSE transport, and superseded Dynamic Client Registration with CIMD.
  • 07A minimal MCP server needs only three things — instantiate a server (e.g. McpServer from @modelcontextprotocol/sdk), register a tool with a name/description/JSON-Schema input, and connect a transport (stdio for local use, Streamable HTTP for remote/multi-client); test it directly before wiring it into an agent, and prefer several narrow tools over one broad one.
  • 08Choose built-in tools for lowest effort, custom tools for full execution control, Skills for on-demand instructions inside one Claude Code project, and MCP servers when the capability must be reusable across multiple different AI hosts.

Common mistakes

Writing a tool description that only states what the tool does ("Gets weather data") with no trigger condition.

State explicitly when to call it — e.g. "Call this when the user asks about current or forecasted weather for a specific location" — since current models under-trigger more often than they over-trigger.

Returning a failed tool call as a normal tool_result, or skipping the tool_result entirely when a call fails.

Always return a tool_result for every tool_use block received; set "is_error": true with a clear message so Claude can adapt instead of treating the failure as valid data.

Answering parallel tool_use blocks with separate follow-up messages instead of one message containing all the tool_result blocks.

Execute concurrent tool calls concurrently and return every corresponding tool_result together in a single user turn — splitting them discourages future parallel calls.

Treating Dynamic Client Registration or the HTTP+SSE transport as current MCP best practice because they still technically work.

Recognize both as superseded by the 2026-07-28 spec revision — CIMD is now the recommended client-registration flow, and Streamable HTTP is the recommended remote transport.

Frequently asked

Is is_error required, or can I just describe the failure in the tool_result content text?

Set is_error explicitly whenever the call failed. It's a structured signal Claude uses to decide whether to retry, try a different approach, or surface the failure to the user — relying on Claude to infer failure from prose in the content field is less reliable than the dedicated flag.

Do I need to disable parallel tool calls to keep my implementation simple?

Not usually — parallel tool calls are the default because they reduce round trips and latency. If your execution environment genuinely can't handle concurrent calls safely, set disable_parallel_tool_use: true inside tool_choice, but treat that as the exception, not the default posture.

When should I build an MCP server instead of a custom client-side tool?

When the same capability needs to be reusable across multiple different AI applications or hosts — not just the one you're currently building. If the tool only ever needs to serve one Claude Code instance or one app, a custom client-side tool is simpler to build, deploy, and control.

Is Dynamic Client Registration (DCR) still usable for MCP auth?

Yes, DCR still works for backward compatibility, but it is no longer the recommended registration flow as of the 2026-07-28 MCP spec revision. Client ID Metadata Documents (CIMD) is the current recommendation — expect exam questions to test this distinction specifically.

Independent, unofficial study material. Not affiliated with, endorsed by, or authorized by Anthropic. Every example is original and written to teach the public exam objectives — no real exam questions are reproduced. Technical details reflect Claude, the Anthropic API, Claude Code, and MCP as of August 24, 2026; always confirm specifics against current official documentation.

Test yourself

Turn what you just read into answers you can check.

Take the mock exam

Keep studying

All guides

These guides are free and never paywalled. Keep Claude Cert Prep free ♥